question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Inlining or quoting generic types causes boxing

See original GitHub issue

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

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:19 (19 by maintainers)

github_iconTop GitHub Comments

1reaction
nicolasstuckicommented, Apr 20, 2021

IMO this is behaving the opposite of “normal” code. For lack of a better term, “true inlining” doesn’t care about types or semantics

What you refer to as “true inlining” is syntactic inlining, this kind of inlining is found in preprocessor such as C/C++ macros. On the other hand, we have semantic inlining which provides strong guarantees about the program semantics and less surprises on the users of the method/macro.

Scala 3 uses semantic inlining.

0reactions
nicolasstuckicommented, May 12, 2021

generate code that doesn’t type-check once it’s spliced

Interested to know of any examples of this.

There are some cases that might be miscategorized as not type checking after inlining such as the use of summonInline or error which might fail during type checking but not due to type checking.

Read more comments on GitHub >

github_iconTop Results From Across the Web

c# - Does a generic function implicitly cast value types to objects ...
So, while in both C# and IL, comparing a value type to null involves boxing, the C# compiler will remove such pointless cruft...
Read more >
Proposal for primitive generics - Kotlin Discussions
It eliminates the need for auto-boxing / unboxing, removes the compiler type casts, enables reified types for the entire class (instead of just ......
Read more >
Generic programming - Wikipedia
Generic programming is a style of computer programming in which algorithms are written in terms of types to-be-specified-later ... (For value types like...
Read more >
In C# how to avoid boxing/unboxing of the value types in ...
Hello, I'm trying to avoid boxing/unboxing in a generic method, ... and then from that force convert the type, which is causing the...
Read more >
Biomechanics of the head for Olympic boxer punches to the face
Methods: Seven Olympic boxers from five weight classes delivered 18 straight punches to ... Translational and rotational head acceleration, neck responses, ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found