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
