Kevin Meredith
Kevin Meredith

Reputation: 41919

Understanding Type in Compiled Scala File

Given:

$cat build.sbt
scalaVersion := "2.12.1"

$cat src/main/scala/net/X.scala
package net 

trait Foo 
object X {

    val a: Int with Foo = 42.asInstanceOf[Int with Foo]

    println(a + a)

}

After compiling it via sbt compile, I javap'd the output class files:

$javap target/scala-2.12/classes/net/X\$.class 
Compiled from "X.scala"
public final class net.X$ {
  public static net.X$ MODULE$;
  public static {};
  public int a();
}

$javap target/scala-2.12/classes/net/X.class 
Compiled from "X.scala"
public final class net.X {
  public static int a();
}

Why does a have the type, int?

I had specified a type of Int with Foo in object X.

Upvotes: 0

Views: 82

Answers (1)

Jasper-M
Jasper-M

Reputation: 15086

This is simply the way all intersection types in Scala are compiled to JVM bytecode. The JVM has no way to represent things like Int with Foo, so the compiler erases the type to the first "simple" type: Int in this case. This means that if you use the value a like a Foo the compiler has to insert a cast into the bytecode.

Take a look at the following REPL session:

Welcome to Scala 2.12.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions for evaluation. Or try :help.

scala> trait Foo { def foo = "foo" }
defined trait Foo

scala> trait Bar { def bar = "bar" }
defined trait Bar

scala> class FooBar extends Foo with Bar
defined class FooBar

scala> class Test { val foobar: Foo with Bar = new FooBar }
defined class Test

scala> object Main {
     |   def main(): Unit = {
     |     val test = new Test().foobar
     |     println(test.foo)
     |     println(test.bar)
     |   }
     | }
defined object Main

scala> :javap -p -filter Test
Compiled from "<console>"
public class Test {
  private final Foo foobar;
  public Foo foobar();
  public Test();
}

scala> :javap -c -p -filter Main
Compiled from "<console>"
  ...
  public void main();
    Code:
      ...
      15: invokeinterface #62,  1           // InterfaceMethod Foo.foo:()Ljava/lang/String;
      ...
      27: checkcast     #23                 // class Bar
      30: invokeinterface #69,  1           // InterfaceMethod Bar.bar:()Ljava/lang/String;
      ...

Int with Foo is actually a special case. Int is a final type and Foo is a trait. Apparently the compiler prefers final types and classes over traits. So in Foo with Bar where Foo is a trait and Bar is a class, the type still gets erased to Bar instead of Foo.

Upvotes: 2

Related Questions