Description
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.