Skip to content

Seemingly correct Java generic signature leads to NoSuchMethodError when Java and Scala erase types differently #12300

Open
@smarter

Description

@smarter

reproduction steps

using Scala 2.13.4,

trait MyTrait
class Scala {
  def foo[T <: Object with MyTrait](x: T): Unit = {}
}
public class Java {
  public static void main(String... args) {
    Scala s = new Scala();
    s.foo(new MyTrait() {});
  }
}

problem

% java Java
Exception in thread "main" java.lang.NoSuchMethodError: Scala.foo(Ljava/lang/Object;)V
        at Java.main(Java.java:4)

The issue is that javac relies on the generic signature to compute the bytecode signature (even though the actual bytecode signature is present in the bytecode), the generic signature matches what we have in source code:

% javap -p -v Scala
  ...
  public <T extends java.lang.Object & MyTrait> void foo(T);
    descriptor: (LMyTrait;)V
  ...

... but whereas Scala has some complicated rules to decide how to erase an intersection type, Java always erases them to the erasure of the left most type in the intersection (which is supposed to be a class), so it emits a call to Scala.foo with the signature (Ljava/lang/Object;)V, whereas the actual signature emitted by Scala was (LMyTrait;)V.

Short of changing the rules for Scala erasure to match Java erasure (which can't be done without breaking binary compatibility, and might not be ideal either, cf scala/scala3#5139), the only fix is to not generate Java generic signatures (or generate less precise signatures) in situations where Java and Scala disagree on how to erase a type.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions