Reputation: 317
I was inspecting the byte-code generated by implicit classes and wanted to compare to what is generated when they extend AnyVal
.
object Example1 {
class Wrapper(val self: Int) extends AnyVal {
def add(n: Int): Int = self + n
}
def foo(w: Wrapper): Wrapper = new Wrapper(w.add(42))
}
The (relevant part of) bytecode:
scala>:javap Example1
[...]
public int foo(int);
descriptor: (I)I
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=2
0: getstatic #19 // Field Example1$Wrapper$.MODULE$:LExample1$Wrapper$;
3: iload_1
4: bipush 42
6: invokevirtual #23 // Method Example1$Wrapper$.add$extension:(II)I
9: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this LExample1$;
0 10 1 w I
LineNumberTable:
line 11: 3
[...]
implicit
:object Example2 {
implicit class Wrapper(val self: Int) extends AnyVal {
def add(n: Int): Int = self + n
}
def foo(w: Wrapper): Wrapper = w.add(42)
}
The (relevant part of) byte-code:
scala>:javap Example2
[...]
public int Wrapper(int);
descriptor: (I)I
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: iload_1
1: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this LExample2$;
0 2 1 self I
LineNumberTable:
line 9: 0
public int foo(int);
descriptor: (I)I
flags: ACC_PUBLIC
Code:
stack=4, locals=2, args_size=2
0: aload_0
1: getstatic #23 // Field Example2$Wrapper$.MODULE$:LExample2$Wrapper$;
4: iload_1
5: bipush 42
7: invokevirtual #27 // Method Example2$Wrapper$.add$extension:(II)I
10: invokevirtual #29 // Method Wrapper:(I)I
13: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 this LExample2$;
0 14 1 w I
LineNumberTable:
line 12: 0
[...]
As a result of extending AnyVal
the call to add
is invoked on the companion object, and the type Wrapper
does not show in the type signature of foo
(public int foo(int);
) in both versions.
Hovewer, in the second version, there is a call just before return: 10: invokevirtual #29
.
It calls public int Wrapper(int);
which seemingly doesn't do anything. (Although I might be wrong, as I don't have much experience with reading the byte-code)
So the question is, what's the significance of that call? Can't it be omitted?
Upvotes: 3
Views: 148
Reputation: 15086
The problem is that your code snippets are not equivalent. An implicit class Foo
is compiled/desugared into a class Foo
and an implicit conversion method Foo
. That's also the reason that implicit classes (currently) can't be top level.
So your first snippet should be:
object Example1 {
class Wrapper(val self: Int) extends AnyVal {
def add(n: Int): Int = self + n
}
def Wrapper(self: Int): Wrapper = new Wrapper(self)
def foo(w: Wrapper): Wrapper = Wrapper(w.add(42))
}
The compiler erases calls to value class constructors if possible. But it doesn't erase the call to the Wrapper
method, implicit or not.
I guess that the JIT compiler in the JVM will eventually erase that method call anyway.
Upvotes: 3