Cannot be cast to class scala.runtime.BoxedUnit

I guess that in the given code following things happen:

// R is erased totally, so asInstanceOf does nothing in runtime, but still affects scalac typechecker
def execute[R](): R = (new Object).asInstanceOf[R]

// scalac sees that execute[Int]() returns Int, which is not a subclass of Unit, so it must drop it
val r1: Unit = execute[Int]()

// scalac sees that execute[Nothing]() returns Nothing, which is a subclass of Unit, so it tries to use it as Unit
val r2: Unit = execute[Nothing]()

// scalac sees that execute[Unit]() returns Unit, so it tries to use it as Unit
val r3: Unit = execute[Unit]()

Therefore there’s no bug in the compiler. Instead, there’s a bad combination of type erasure and runtime type casts.

I’ll guess again. My hunch is that discarding the value is tried only after assignment doesn’t typecheck. Is that useful at all? I don’t know.

3 Likes

I think you’re right.

def execute[R](): R = (new Object).asInstanceOf[R]
// Behavior is similar here
val r1: Int = execute[Int]() // class java.lang.Object cannot be cast to class java.lang.Integer
val r2: Unit = execute[Unit]() // class java.lang.Object cannot be cast to class scala.runtime.BoxedUnit

r1 can be used as Int, cast is necessary.
r2 can be used as Unit. But there is only one value of type Unit, therefore no need to cast.

val r3: Unit = execute[Int]()

r3 does not type check. But we see no warning from the compiler. And scala runtime is smart enough to discard the result. Such behavior is probably necessary for operating without explicit return. I can imagine the following scenario:

def getInt(): Int = 1
def foo(): Unit = getInt
foo()

If there was no discarding mechanism, the compiler would have to raise an error, since Int is not a subtype of Unit.

val r4: Int = execute[Unit]()// ERROR: Type mismatch!

r4 does not type check. But in this case compiler knows that Unit cannot be used as Int and raises an error.

I still think cast Unit -> Unit should not happen, because:

  1. There is no real value to it. Cast to Unit is equivalent to discarding the value from the client perspective.
  2. It would be consistent with the discarding mechanism that works fine without generics:
      // During the cast the value will be discarded in all cases. Sometimes, we'll see a warning.
      val u1: Unit = ()
      val u2: Unit = new Object
      val u3: Unit = true // warning: a pure expression does nothing in statement position
      val u4: Unit = 1 // warning: a pure expression does nothing in statement position

The real questions IMO are why

(new Object()).asInstanceOf[Unit]

doesn’t throw a ClassCastException, and why

val u2: Unit = new Object

doesn’t emit a discard warning

It’s worth noting that there are no casts in your last examples at all.

  val u1: Unit = ()
  val u2: Unit = new Object //I have no idea what's happening here
  val u3: Unit = true // warning: a pure expression does nothing in statement position
  // this is equivalent to val u3: Unit = { true; () }. There is no casting going on.
  val u4: Unit = 1 // warning: a pure expression does nothing in statement position
 //  this is equivalent to val u4: Unit = { 1; () }. there is no casting going ong

Note that there is nothing in the Scala spec that demands it throw a ClassCastException at any point. When an asInstanceOf is supposed to be successful, then Scala must not throw. But if it is not supposed to be successful, Scala can do whatever it wants.

In practice it often chooses to do the least amount of compile-time and/or run-time work sufficient to convince the JVM, or JavaScript, to pursue.

2 Likes

You have to ask:

$ scala -Wvalue-discard
Welcome to Scala 2.13.0 (OpenJDK 64-Bit Server VM, Java 12.0.1).
Type in expressions for evaluation. Or try :help.

scala> val u: Unit = new Object
                     ^
       warning: discarded non-Unit value
u: Unit = ()
2 Likes

According to the Value Discarding section of the Scala specs:

If e has some value type and the expected type is Unit , e is converted to the expected type by embedding it in the term { e; () } .

Therefore, I would expect this expression:

val u: Unit = execute[Unit]()

to be rewritten by the compiler to:

val u: Unit = { execute[Unit](); () }

In such case, no conversion error occurs and the value discarding behaviour is consistent.

But it seems that because:

Implicit conversions can be applied to expressions whose type does not match their expected type

and since we have Unit => Unit, implicit conversion does not happen.

And yet, the behaviour in r3 does not confirm to my logic:

def execute[R](): R = (new Object).asInstanceOf[R]

val r1: Unit = execute[Unit]() // class java.lang.Object cannot be cast to class scala.runtime.BoxedUnit
val r2: Int = execute[Int]() // class java.lang.Object cannot be cast to class java.lang.Integer

val r3: Unit = (new Object).asInstanceOf[Unit] // SUCCESS!
val r4: Int = (new Object).asInstanceOf[Int] // class java.lang.Object cannot be cast to class java.lang.Integer

Maybe r3 it’s treated as a special case. But then, why not do the same in r1? In my opinion, when the expected type is Unit it makes sense to discard the value regardless of its type.

I just saw the blue box saying let’s welcome y’all to the community, so welcome!

The icon is two manly hands man-handling, one hand clearly the superior hand.

I’d propose an icon based on the famous Sistine chapel God/man hands. Offhand, so to speak, I don’t remember what it represents. Is it when Adam says, I’m lonely! or when God says, You know what tickles? and then demonstrates.

So, your question is, after we lie to the compiler and claim that an Object is a (boxed) R, why do we get away with the lie in one case but not the other? The one case (r3) is just an expression that is Unit. There is nothing more to say, you said it was a Unit! The other case, though, is polymorphic, which is Greek for liar, which means we must “trust but verify”. Actually, we just verify.

3 Likes

After compiling the following Scala code:

  def execute[R](): R = (new Object).asInstanceOf[R]

  def main(args: Array[String]): Unit = {
    val u1: Unit = (new Object).asInstanceOf[Unit]
    val i1: Int = (new Object).asInstanceOf[Int]
    val s1: String = (new Object).asInstanceOf[String]

    val u2: Unit = execute[Unit]()
    val i2: Int = execute[Int]()
    val s2: String = execute[String]()
  }

and decompiling it to Java:

  public <R> R execute() { return (R)new Object(); }
  
  public void main(String[] args) {
    new Object(); BoxedUnit u1 = BoxedUnit.UNIT;
    int i1 = BoxesRunTime.unboxToInt(new Object());
    String s1 = (String)new Object();
    
    BoxedUnit u2 = (BoxedUnit)execute();
    int i2 = BoxesRunTime.unboxToInt(execute());
    String s2 = (String)execute();
  }

compiler’s behaviour is consistent when we have primitive and reference types (i1 and i2, s1 and s2) but it isn’t when we have Unit.

6.26 begins: “Implicit conversions can be applied to expressions whose type does not match their expected type” (and other contexts).

You agreed with tarsa that this is a precondition for the “value discard” conversion.

(Edit: sorry, glancing back I see you quoted this precondition.)

Maybe you’re saying that BoxedUnit is different from Unit, but that is an artifact of the implementation. In Scala, you only see Unit and Int, not BoxedUnit and Integer.

In particular, if some generic method hands you an Object you think should be of type Unit (or any other boxed type), you must verify before handling the reference.

Although I sympathize with your idea that a unit is a unit, it’s also possible that a programmer error will result in a reference type that is not a boxed unit. (The example code is such an error.) Rather than ignoring that mistake, you want to discover it early.

There are previous email threads about boxed unit. I think Lukas made the improvements, at least a few years ago. The questions around java interop were not trivial. I think that was on the old “internals” email list, but I don’t really remember. I remember it was an interesting discussion.

It’s a reminder that some smart people – I was going to say something appreciative about how much work goes into scalac, but there is a large spider sauntering down the wall.

I’m wondering if this conversation is of purely academic interest, or if there are real life consequences?

Is there some code that might have been written to do something useful, but instead does something else because of unexpected treatment of Unit?

Also, why we need BoxedUnit? Can we get rid of it in the long term? I think this roughly would mean making it illegal to use Unit as a type argument.

Best, Oliver

My project has a code structure similar to this:

object App {
  def execute[R](): R = (new Object).asInstanceOf[R]

  def doMatch(): Unit = {
    // match is an expression in Scala (i.e., it always results in a value). Therefore, the `execute` call is in fact `val matchResult = execute/.../`
    () match {
      case _ => {
        // The inferred type will be `Unit`, because `doMatch` must return a `Unit`.
        execute() // class java.lang.Object cannot be cast to class scala.runtime.BoxedUnit
      }
    }
  }

  def main(args: Array[String]): Unit = {
    doMatch()
  }
}

I’m not sure why you would not expect ClassCastExceptions here.

1 Like

Unit means I don’t want to return anything from the method. I don’t care what execute returns, just throw it away. By the way, the execute method is part of Java code in my project.

To fix the problem I had to change:

execute()

to

execute[Any]()

If you don’t care what execute() returns, why does it need a type parameter?

It’s used in other places where I do need the returned value. In that particular case, I’ve shown above, the method is executed only for its side effects.

Right, but that’s irrelevant – execute() is crashing while it runs. Whether the result gets thrown away or not doesn’t matter. “Thrown away” doesn’t mean “doesn’t run”, and you are doing an illegal cast inside of execute().

Seriously – what surprises me isn’t that it’s failing for Unit, it’s that it succeeds on Int. You’re asking for a clearly-illegal cast (casting Object to AnyVal). The fact that the compiler sometimes magically makes that succeed due to boxing isn’t terribly obvious, and it isn’t obvious why it would necessarily always work…

1 Like

R in execute is completely erased together with casting. Due to erasure, casting is pushed to call site.

2 Likes

If you make it illegal to use Unit as a type argument then you’re forbidding Function1[T, Unit] for which sugared form is T => Unit. Also you would forbid IO[Unit] and many other useful types.

Automatic () is very convenient for prototyping. If I e.g. change return type of a method m from String to Unit then in many places I don’t need to refactor code, e.g I can still use m in List.map, I can still assign the result of invoking m to a variable, etc

2 Likes

By coincidence, today I was looking at Java bugs related to overloading. One report had a bad cast, like yours, and that value was passed to an overloaded method in Object and String. In Java 7, type inference inferred Object and the Object-taking method was picked; in Java 8, it could infer String, picked the other method, which failed. I think the lesson is that these “under-the-hood” casts, which seem to be just getting in your face, are actually doing useful type system work.

1 Like

Ah, right, I forgot about those. In that case, I guess Unit must remain a legal argument for type parameters.

Makes me wonder how often T => Unit will return BoxedUnit when the author intended it to return nothing at all, and how that affects performance?

I’m assuming that the following will create a Seq with 100 references to BoxedUnit, right?

(1 to 100).map(println)