Yup. Autoboxing mixed with type erasure means the Numbers.asAnyRef conversion 
can't tell the difference between a JLong and a Maybe[JLong]. Both are, or are 
silently converted to AnyRef.


We should be able to live without Numbers.asAnyRef. It's supposed to be for 
numbers also, so it ought to verify that the incoming thing is one of the 
acceptable number types. Perhaps we can define a flock of overloads like

def asAnyRef(i: Int) = new JInt(i)

def asAnyRef(l: Long) = new JLong(l)

and also

def asAnyRef(ji: JInt) = ji


(half baked admittedly)


Related: You can pass a Maybe object or any AnyVal object and it will auto-box 
it into an AnyRef.

All the efficiency of passing an AnyVal is of course lost in this case.

Same issue happens if you try to store a Maybe into a generic collection.


As a result we have MaybeInt, MaybeLong, MaybeULong, MaybeBoolean, etc.

Also we have MStackOfLong, MStackOfInt, .... etc. because we use lots of 
mutable stacks, and don't want to box/unbox for primitives going onto/off of 
them.


And... even MStackOfMaybe[T], which avoids the auto-boxing associated with 
storing a Maybe[T] in the generic MStack collection.


I really like having Maybe types though. I'd like to be able to prohibit the 
auto-boxing to force non-generic usage optionally, and make people explicitly 
create the boxed variants.


But I know of no such option. I haven't really searched though.


________________________________
From: Sloane, Brandon <[email protected]>
Sent: Thursday, June 6, 2019 5:43:01 PM
To: [email protected]
Subject: Scala is broken?

While investigating issue DAFFODIL-2146, I discovered that dstate.currentValue 
was taking on the value of "One(0)".


My first instinct is to attribute the to a bug in my enumeration support code 
(which it probably is), however my second instinct is to note that 
dstate.currentValue is declared to be of type AnyRef, while One(0) is of type 
Maybe, so the above situation should not be possible.


I was able to reproduce the issue in a small test program (using Daffodil as a 
dependency):


    val x1: Maybe[JLong] = One(JLong.valueOf(0))
    val x : Any = x1
    val y: AnyRef = Numbers.asAnyRef(x)
    println(x1.isInstanceOf[AnyRef])
    println(x.isInstanceOf[AnyRef])
    println(y)


I would expect the above program to output (not sure what I expect on the third 
line):


false

false

????


However, it actually outputs:


true
true
One(0)


Helpfully, Eclipse provides a warning on the first println that the expression 
can never be true.


For reference, Numbers.asAnyRef is defined as:


  def asAnyRef(n: Any): AnyRef = {
    n match {
      // case bi: BigInt => bi.bigInteger
      // case bd: BigDecimal => bd.bigDecimal
      case ar: AnyRef =>  ar
      case b: Boolean => JBoolean.valueOf(b)
      case _ => asNumber(n)
    }
  }


and I observe the same behaviour if I replace that call with a local version of 
the function:


  def asAnyRef(n: Any): AnyRef = {
    n match {
      case ar: AnyRef => ar
    }
  }



What appears to be happening is that Maybe is being recognized as an AnyRef by 
the pattern match, thereby allowing for a Maybe -> AnyRef cast which should not 
be possible.


Based on this, I tested the behavour of Numbers.asAnyRef on scala.Int value and 
found (by println debugging) that such a case is also going the the ar:AnyRef 
code path.


I suspect that the asAnyRef function is always a no-op at runtime, and the only 
reason our works normally is that scala will automagically cast between boxed 
and unboxed versions of the built-in anyVal types.


Assuming my analysis is correct, asAnyRef is really unsafeCoerceToAnyRef. As 
such, I would suggest that we remove the function and explicitly box values 
where Scala fails to do so automatically.


Thoughts?


Brandon T. Sloane

Associate, Services

[email protected] | tresys.com

Reply via email to