Option(null.asInstanceOf[Boolean]) == None? That's a bug, right?

665 views
Skip to first unread message

Simon Ochsenreither

unread,
Apr 20, 2013, 5:32:45 PM4/20/13
to scala-l...@googlegroups.com
Consider this:

scala> Option(null.asInstanceOf[Boolean])
res0: Option[Boolean] = None

scala> val boolean = null.asInstanceOf[Boolean]
boolean: Boolean = false

scala> Option(boolean)
res1: Option[Boolean] = Some(false)

scala> Option({null.asInstanceOf[Boolean]})
res2: Option[Boolean] = None

scala> Option({val boolean = null.asInstanceOf[Boolean]; boolean})
res3: Option[Boolean] = Some(false)


Ideas? Opinions? Facts?

Thanks and bye,

Simon

Som Snytt

unread,
Apr 20, 2013, 6:21:58 PM4/20/13
to scala-l...@googlegroups.com
But I didn't yet finish figuring out why

scala> Int unbox null
res5: Int = 0





--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Simon Ochsenreither

unread,
Apr 20, 2013, 6:25:11 PM4/20/13
to scala-l...@googlegroups.com
I think it is a bug. Even with all the potential implicit magic taken into account, there is only one valid result of null.asInstanceOf[Boolean] and that is a primitive false. Even with the boxing that happens afterwards, it shouldn't result in null.

Paul Phillips

unread,
Apr 20, 2013, 6:35:43 PM4/20/13
to scala-l...@googlegroups.com
On Sat, Apr 20, 2013 at 3:25 PM, Simon Ochsenreither <simon.och...@gmail.com> wrote:
I think it is a bug. Even with all the potential implicit magic taken into account, there is only one valid result of null.asInstanceOf[Boolean] and that is a primitive false. Even with the boxing that happens afterwards, it shouldn't result in null.

It is indeed a bug with the recognition of literal null.



Andrew Phillips

unread,
Apr 20, 2013, 6:43:25 PM4/20/13
to scala-l...@googlegroups.com
scala> Option(null.asInstanceOf[Boolean]) 
res0: Option[Boolean] = None

I'm wondering whether the asInstanceOf bit ever gets applied here, or whether this actually just becomes Option(null). This looks similar to https://groups.google.com/d/msg/scala-language/H2tvh0O1iqA/HsphGXLc9foJ - I wonder (since paulp has now confirmed this case is a bug) whether the underlying issue is the same.

Interesting also (2.10.0 REPL):

scala> Option(null.asInstanceOf[Boolean]: Boolean)
res2: Option[Boolean] = Some(false)

ap

Andrew Phillips

unread,
Apr 20, 2013, 6:45:07 PM4/20/13
to scala-l...@googlegroups.com
> But I didn't yet finish figuring out why
scala> Int unbox null
> res5: Int = 0

By "figuring out why", do you mean why this actually happens or what the reasoning is behind saying it should be the correct result?

ap

Paul Phillips

unread,
Apr 20, 2013, 6:56:55 PM4/20/13
to scala-l...@googlegroups.com

Simon Ochsenreither

unread,
Apr 20, 2013, 7:18:48 PM4/20/13
to scala-l...@googlegroups.com


Thanks a lot!
Btw, I could reproduce this bug on 2.8, 2.9, 2.10 and 2.11.

I did the paperwork and filed an issue for it, linking to this thread and your pull request: https://issues.scala-lang.org/browse/SI-7397

Bye,

Simon

Paul Phillips

unread,
Apr 21, 2013, 1:06:16 AM4/21/13
to scala-l...@googlegroups.com

On Sat, Apr 20, 2013 at 4:18 PM, Simon Ochsenreither <simon.och...@gmail.com> wrote:
I did the paperwork and filed an issue for it, linking to this thread and your pull request: https://issues.scala-lang.org/browse/SI-7397

Thanks, that helps. Looks like the ol' 20 minute flier crashed and burned though.

Som Snytt

unread,
Apr 21, 2013, 1:43:02 AM4/21/13
to scala-l...@googlegroups.com
I was wondering if it's literally true that "paulp has a branch for that", and if not, I was wondering at the typing speed in symbols per minute that was required.

@sharedocs1: Both, I think, the actual and putative causes.  The puzzler answer says it throws, which is why I tried it.  I'll look at it again in a bit so I can sleep tonight.  It sure looks like it would throw, i.e., null.asInstanceOf[java.lang.Integer].intValue().


> By "figuring out why", do you mean why this actually happens or what the reasoning is behind saying it should be the correct result?

The way you express this question makes me think I misunderstood something.

Wait, wait, don't tell me.


--

Som Snytt

unread,
Apr 21, 2013, 2:50:07 AM4/21/13
to scala-l...@googlegroups.com
I took a look, but I'm still not sure if this is the proximate cause or the reason behind why it was supposed to be what the result was intended.

scala> def m = Int unbox null
m: Int

scala> :javap #m
  public int m();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aconst_null  
         1: invokestatic  #20                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
         4: ireturn      
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   L;
      LineNumberTable:
        line 7: 0


But this is an improvement:

scala> def f: Any = Int unbox null
f: Any

scala> :javap #f
  public java.lang.Object f();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aconst_null  
         1: areturn      
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       2     0  this   L;
      LineNumberTable:
        line 7: 0



Paul Phillips

unread,
Apr 21, 2013, 3:37:45 AM4/21/13
to scala-l...@googlegroups.com
On Sat, Apr 20, 2013 at 10:43 PM, Som Snytt <som....@gmail.com> wrote:
It sure looks like it would throw, i.e., null.asInstanceOf[java.lang.Integer].intValue().

Just for fun, that code is never called. This is the subject of https://issues.scala-lang.org/browse/SI-6898 . Clearly you aren't putting enough time into keeping up with pull requests.

Paul Phillips

unread,
Apr 21, 2013, 3:38:10 AM4/21/13
to scala-l...@googlegroups.com

On Sun, Apr 21, 2013 at 12:37 AM, Paul Phillips <pa...@improving.org> wrote:
Clearly you aren't putting enough time into keeping up with pull requests

Som Snytt

unread,
Apr 21, 2013, 3:58:38 AM4/21/13
to scala-l...@googlegroups.com
Oh right, thanks.

I see that github lets me sort PRs by "Oldest" so I can see the ones that are stale by more than a month.

An alternative workflow would be that every PR first must be formulated as a puzzler.  Once published on scalapuzzlers, we can document that the PR is not a trivial whitespace change.  That also verifies that someone is actually puzzled by the current behavior.

(The truth is that I skipped that PR to look at the TailCalls micro-optimization.  Serves me right.)

I'm still curious, not to say puzzled, about how it will shake out with null.asInstanceOf[Unit] and Of[Nothing].


Paul Phillips

unread,
Apr 21, 2013, 9:25:38 AM4/21/13
to scala-l...@googlegroups.com
On Sat, Apr 20, 2013 at 10:06 PM, Paul Phillips <pa...@improving.org> wrote:
Thanks, that helps. Looks like the ol' 20 minute flier crashed and burned though.

Nightmarishly, it turns out specialization depends on the current behavior. Say you have a specialized class like this:

class Klass[@specialized(Long) A]( val a: A )

The Long-specialized subclass has to call the generic Klass constructor with some kind of argument. It currently passes null, which is passed through untouched where it lies unseen because the field is inaccessible, as all means of accessing it have been overridden to point to the specialized field in Klass$mcJ$sp.

After I teach erasure that null.asInstanceOf[Long] means it's a Long, the lesson is taken too well: no longer is the null passed through untouched, because it's a Long. And since the eventual destination is a generic field, it is boxed into a java.lang.Long before being placed there.

Spelled in diff form:

 diff --git a/Klass$mcJ$sp.class b/Klass$mcJ$sp.class
 index 6af6c5282c..2d7f69c300 100644
 --- a/Klass$mcJ$sp.class
 +++ b/Klass$mcJ$sp.class
 @@ -34,7 +34,7 @@ public class Klass$mcJ$sp extends Klass<java.lang.Object> {
      Code:
          : lload_1
          : putfield      #                  // Field a$mcJ$sp:J
 -        : aconst_null
 -        : invokespecial #                  // Method Klass."<init>":(Ljava/lang/Object;)V
 +        : invokestatic  #                  // Method scala/runtime/BoxesRunTime.boxToLong:(J)Ljava/lang/Long;
 +       : invokespecial #                  // Method Klass."<init>":(Ljava/lang/Object;)V
         : return
  }

Andrew Phillips

unread,
Apr 21, 2013, 11:41:32 AM4/21/13
to scala-l...@googlegroups.com
> The way you express this question makes me think I misunderstood something.
>
> Wait, wait, don't tell me.

Sorry about the cryptic formulation. Also curious to see how this one shakes out - it's a pity this now seems to be even more complicated (paulp's comment about specialization depending on the current behavior) than first thought.

I know it's more of a "historical" question now, but I would also be curious to know more about how the "null becomes default primitive" logic in BoxesRunTime came about in the first place. Even better, of course, would be to be able to relegate this to the archives ;-)

ap

Paul Phillips

unread,
Apr 21, 2013, 12:55:05 PM4/21/13
to scala-l...@googlegroups.com
On Sun, Apr 21, 2013 at 8:41 AM, Andrew Phillips <share...@gmail.com> wrote:
I know it's more of a "historical" question now, but I would also be curious to know more about how the "null becomes default primitive" logic in BoxesRunTime came about in the first place. Even better, of course, would be to be able to relegate this to the archives ;-)

It's java interop, as usual. You have to do something with variations on this:

  val m = new java.util.HashMap[Int, Int]
  m get 0

That returns null on the java side. We can either:

 - disallow parameterizing on primitive types, like java does
 - throw an exception when we see null - keeping in mind that's how java returns "element not found"
 - map null into something in Intspace
 - can't think of a fourth option right now

If I had a better answer than what we're doing, I'm sure you'd have heard me outline it 100 times by now. I don't.

Here's another common situation.

public class J<T> {
  public T cell;
}

// scala> new J[Int]
// res0: J[Int] = J@3570e0e9

// scala> res0.cell
// res1: Int = 0

// scala> res0.cell: Any
// res2: Any = null



Andrew Phillips

unread,
Apr 21, 2013, 1:11:49 PM4/21/13
to scala-l...@googlegroups.com
Thanks for the explanation, Paul!

ap

Erik Osheim

unread,
Apr 22, 2013, 1:32:47 AM4/22/13
to scala-l...@googlegroups.com
On Sun, Apr 21, 2013 at 06:25:38AM -0700, Paul Phillips wrote:
> After I teach erasure that null.asInstanceOf[Long] means it's a Long, the
> lesson is taken too well: no longer is the null passed through untouched,
> because it's a Long. And since the eventual destination is a generic field,
> it is boxed into a java.lang.Long before being placed there.

Ouch! For anything using specialized fields that will truly be an
unfortunate situation!

This reinforces my (preexisting) conviction that given the current
specialization machinery, one is better off using specialized traits
(sans fields) extended by concrete classes, with a generic factory
constructor using ClassTags (or something similar).

-- Erik

Vlad Ureche

unread,
Apr 22, 2013, 7:11:18 AM4/22/13
to scala-l...@googlegroups.com

2013/4/22 Erik Osheim <er...@plastic-idolatry.com>


I think the solution lies on the programmer side, as Erik pointed out.
Because fixing the duplicate fields in specialization will get us even more bytecode than 10^n:
class Klass[@specialized(Everything) A]( val a: A )
will turn into
trait Klass[A] { def a: A; def a$mcI$sp: Int; /* and 9 other specialized variants */ }
trait Klass$mcI$sp[A](val a$mcI$sp: Int) { def a: A = ...; def a$mcI$sp: Int = ...; /* and 9 other redirects */ }
...
Even if we implement the redirects in the trait, we'll still have 10^n classes defining 10^n methods each.
So I'd say it's not outside PaulP's scope, it's outside specialization's scope altogether.

Erik, just as a curiosity, why do you need the factories?

Cheers,
Vlad

Oliver Ruebenacker

unread,
Apr 22, 2013, 9:43:40 AM4/22/13
to scala-l...@googlegroups.com

     Hello,

  What is the argument against throwing a class cast exception?

     Take care
     Oliver

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
IT Project Lead at PanGenX (http://www.pangenx.com)
The purpose is always improvement

Oliver Ruebenacker

unread,
Apr 22, 2013, 9:56:56 AM4/22/13
to scala-l...@googlegroups.com

     Hello,

scala> null.asInstanceOf[String with NotNull]
res4: String with NotNull = null

  ;)

     Take care
     Oliver

Paul Phillips

unread,
Apr 22, 2013, 10:03:35 AM4/22/13
to scala-l...@googlegroups.com

On Mon, Apr 22, 2013 at 6:43 AM, Oliver Ruebenacker <cur...@gmail.com> wrote:
  What is the argument against throwing a class cast exception?

That is a very good question. I can tell you what the argument was five years ago, which is the last time that was tried:


     public static boolean unboxToBoolean(Object b) {
-        return b == null ? false : ((Boolean)b).booleanValue();
+        if (b == null)
+          throw new ClassCastException("null is no Boolean value");
+        return ((Boolean)b).booleanValue();

The commits point the way:

% for c in 75b4429e15 c0705fc670; do git log -n1 --format="%h %cd   %cn    %s" $c; done
 75b4429e15 5 years ago   Gilles Dubochet    Fixed issues #584 and #602.
 c0705fc670 5 years ago   Gilles Dubochet    Fixed issue #668, removed fix for issue #602.

As we see in SI-668, the reason it was reverted was the interaction with the attempt to transparently box java arrays - an attempt which has long been abandoned.


So, what IS the argument against throwing the exception?

Simon Ochsenreither

unread,
Apr 22, 2013, 10:20:46 AM4/22/13
to scala-l...@googlegroups.com
It's more or less the “canonical” way to get the default values for some type T.
If it would stop working, one would need a different way to do it.

new Array[T](1) does not work for value types.

C# has a separate keyword and syntax for it: default(T)

Anyway, personally I prefer correctness over performance, especially if the trouble is associated with optimizations which should have been done by the runtime compiler in the first place.

Oliver Ruebenacker

unread,
Apr 22, 2013, 10:20:07 AM4/22/13
to scala-l...@googlegroups.com

     Hello,

  Actually:

scala> null.asInstanceOf[NotNull]
res5: NotNull = null

     Take care
     Oliver

martin odersky

unread,
Apr 22, 2013, 11:11:05 AM4/22/13
to scala-l...@googlegroups.com
I don't really know. As Simon says, we'd need a different interpretation of default init, and that has to be integrated with bridge methods and specialization. It seems doable, though, hopefully without inventing new default values. For instance, right now we would generate:

  class C[T] { var x: T }
  class CI extends C[Int] {
     // bridge method:
     def x: Int = unboxToInt(super.x)
  }

If we add the null check to unbox this would not work anymore. We need to change the bridge method as follows:

  class CI extends C[Int] {
     // bridge method:
     def x: Int = if (super.x == null) 0 else unboxToInt(super.x)
  }

Not sure how many other gotchas there are.

Cheers

 - Martin

Paul Phillips

unread,
Apr 22, 2013, 11:37:11 AM4/22/13
to scala-l...@googlegroups.com

On Mon, Apr 22, 2013 at 7:20 AM, Simon Ochsenreither <simon.och...@gmail.com> wrote:
It's more or less the “canonical” way to get the default values for some type T.

Yes, but an explicit cast need not have the same behavior; they just happen to right now. There is nothing which prevents "null.asInstanceOf[Int]" from being translated to 0 while still throwing an exception when a null is found where an Int should be.

Reply all
Reply to author
Forward
0 new messages