Open
Description
Compiler version
3.0.1-RC1-bin-20210402-775d881-NIGHTLY
Minimized code
import scala.compiletime.*
extension [A](a: A)
transparent inline def ==*(b: A): Boolean =
inline erasedValue[A] match
case _: Int => println("COMPARING INTS"); a == b
case _ => ???
class Test:
var i = 1
var res = false
def x1 = res = 1 ==* 1
def x2 = res = 1 ==* 2
def x3 = res = 1 ==* i
def z1 = res = 1 == i
Output
> javap -c target/scala-3.0.1-RC1/classes/Test.class
Compiled from "BUG.scala"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #13 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #15 // Field i:I
9: aload_0
10: iconst_0
11: putfield #17 // Field res:Z
14: return
public int i();
Code:
0: aload_0
1: getfield #15 // Field i:I
4: ireturn
public void i_$eq(int);
Code:
0: aload_0
1: iload_1
2: putfield #15 // Field i:I
5: return
public boolean res();
Code:
0: aload_0
1: getfield #17 // Field res:Z
4: ireturn
public void res_$eq(boolean);
Code:
0: aload_0
1: iload_1
2: putfield #17 // Field res:Z
5: return
public void x1();
Code:
0: aload_0
1: getstatic #33 // Field scala/Predef$.MODULE$:Lscala/Predef$;
4: ldc #35 // String COMPARING INTS
6: invokevirtual #39 // Method scala/Predef$.println:(Ljava/lang/Object;)V
9: iconst_1
10: invokevirtual #41 // Method res_$eq:(Z)V
13: return
public void x2();
Code:
0: aload_0
1: getstatic #33 // Field scala/Predef$.MODULE$:Lscala/Predef$;
4: ldc #35 // String COMPARING INTS
6: invokevirtual #39 // Method scala/Predef$.println:(Ljava/lang/Object;)V
9: iconst_0
10: invokevirtual #41 // Method res_$eq:(Z)V
13: return
public void x3();
Code:
0: aload_0
1: aload_0
2: invokevirtual #45 // Method i:()I
5: istore_1
6: getstatic #33 // Field scala/Predef$.MODULE$:Lscala/Predef$;
9: ldc #35 // String COMPARING INTS
11: invokevirtual #39 // Method scala/Predef$.println:(Ljava/lang/Object;)V
14: iconst_1
15: invokestatic #51 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
18: iload_1
19: invokestatic #51 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
22: astore_2
23: dup
24: ifnonnull 35
27: pop
28: aload_2
29: ifnull 42
32: goto 46
35: aload_2
36: invokevirtual #55 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
39: ifeq 46
42: iconst_1
43: goto 47
46: iconst_0
47: invokevirtual #41 // Method res_$eq:(Z)V
50: return
public void z1();
Code:
0: aload_0
1: iconst_1
2: aload_0
3: invokevirtual #45 // Method i:()I
6: if_icmpne 13
9: iconst_1
10: goto 14
13: iconst_0
14: invokevirtual #41 // Method res_$eq:(Z)V
17: return
}
Expectation
We can see in the disassembly of x3
that it boxes the Ints. I was expecting that inline
would choose the appropriate implementation of ==
based on the types of its arguments. I started down the path of matching on erasedValue[Int]
to provide more type info to the inliner but that didn't make a difference.
Workaround
The following generates code that doesn't box but considering that I'd have to do it for every primitive, for every inline method I write, it would very quickly become an unreasonable amount of work and boilerplate.
private object Hidden:
inline def int_eq(a: Int, b: Int): Boolean = a == b
extension [A](a: A)
transparent inline def ==*(b: A): Boolean =
inline erasedValue[A] match
case _: Int => println("COMPARING INTS"); Hidden.int_eq(a.asInstanceOf[Int], b.asInstanceOf[Int])
case _ => ???