Skip to content

Inlining or quoting generic types causes boxing #11998

Open
@japgolly

Description

@japgolly

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 _      => ???

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions