Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
smarter opened this issue Jan 7, 2021 · 1 comment

Comments

@smarter
Copy link
Member

smarter commented Jan 7, 2021

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.

@dwijnand dwijnand added this to the Backlog milestone Jan 8, 2021
smarter added a commit to dotty-staging/dotty that referenced this issue Mar 18, 2021
Thanks to scala#11603, we can now freely how we erase our intersection types
without compromising compatibility with Scala 2. We take advantage of
this by designing a new erasedGlb algorithm which respects associativity
and commutativity and can also erase Java intersections without any
special case. This commit also improves the way we handle intersections
in Java generic signature to produce more precise types when possible
and to ensure that we never emit a type that would lead to javac
emitting invalid bytecode (which can happen in Scala 2:
scala/bug#12300).
smarter added a commit to dotty-staging/dotty that referenced this issue Mar 18, 2021
Thanks to scala#11603, we can now freely decide how we erase our intersection
types without compromising compatibility with Scala 2. We take advantage
of this by designing a new erasedGlb algorithm which respects
associativity and commutativity and can also erase Java intersections
without any special case. This commit also improves the way we handle
intersections in Java generic signature to produce more precise types
when possible and to ensure that we never emit a type that would lead to
javac emitting invalid bytecode (which can happen in Scala 2:
scala/bug#12300).
smarter added a commit to dotty-staging/dotty that referenced this issue Mar 18, 2021
Thanks to scala#11603, we can now freely decide how we erase our intersection
types without compromising compatibility with Scala 2. We take advantage
of this by designing a new erasedGlb algorithm which respects
associativity and commutativity and can also erase Java intersections
without any special case.

This commit also improves the way we handle intersections in Java
generic signature to produce more precise types when possible and to
ensure that we never emit a type that would lead to javac emitting
invalid bytecode (which can happen with Scala 2:
scala/bug#12300).
@smarter
Copy link
Member Author

smarter commented Mar 18, 2021

Fixed in Scala 3 both by aligning Scala erasure with Java erasure and by being more careful about emitting valid generic signatures: scala/scala3#11808. Scala 2 can't change its erasure but the generic signature handling could be reused to fix this issue (it would make the compiler emits a signature equivalent to <T extends MyTrait> instead of the current <T extends Object & MyTrait>.

smarter added a commit to dotty-staging/dotty that referenced this issue Mar 18, 2021
Thanks to scala#11603, we can now freely decide how we erase our intersection
types without compromising compatibility with Scala 2. We take advantage
of this by designing a new erasedGlb algorithm which respects
associativity and commutativity and can also erase Java intersections
without any special case.

This commit also improves the way we handle intersections in Java
generic signature to produce more precise types when possible and to
ensure that we never emit a type that would lead to javac emitting
invalid bytecode (which can happen with Scala 2:
scala/bug#12300).

Fixes scala#5139.
smarter added a commit to dotty-staging/dotty that referenced this issue Mar 18, 2021
Thanks to scala#11603, we can now freely decide how we erase our intersection
types without compromising compatibility with Scala 2. We take advantage
of this by designing a new erasedGlb algorithm which respects
associativity and commutativity and can also erase Java intersections
without any special case.

This commit also improves the way we handle intersections in Java
generic signature to produce more precise types when possible and to
ensure that we never emit a type that would lead to javac emitting
invalid bytecode (which can happen with Scala 2:
scala/bug#12300).

Fixes scala#5139.
smarter added a commit to dotty-staging/dotty that referenced this issue Mar 20, 2021
Thanks to scala#11603, we can now freely decide how we erase our intersection
types without compromising compatibility with Scala 2. We take advantage
of this by designing a new erasedGlb algorithm which respects
associativity and commutativity and can also erase Java intersections
without any special case. Incidentally, this lets us reverse a recent
change in scala-stm which was previously required due to two equivalent
types ending up with different signatures.

This commit also improves the way we handle intersections in Java
generic signature to produce more precise types when possible and to
ensure that we never emit a type that would lead to javac emitting
invalid bytecode (which can happen with Scala 2:
scala/bug#12300).

Fixes scala#5139.
smarter added a commit to dotty-staging/dotty that referenced this issue Mar 21, 2021
Thanks to scala#11603, we can now freely decide how we erase our intersection
types without compromising compatibility with Scala 2. We take advantage
of this by designing a new erasedGlb algorithm which respects
associativity and commutativity and can also erase Java intersections
without any special case. Incidentally, this lets us reverse a recent
change in scala-stm which was previously required due to two equivalent
types ending up with different signatures.

This commit also improves the way we handle intersections in Java
generic signature to produce more precise types when possible and to
ensure that we never emit a type that would lead to javac emitting
invalid bytecode (which can happen with Scala 2:
scala/bug#12300).

Fixes scala#5139.
smarter added a commit to dotty-staging/dotty that referenced this issue Mar 21, 2021
Thanks to scala#11603, we can now freely decide how we erase our intersection
types without compromising compatibility with Scala 2. We take advantage
of this by designing a new erasedGlb algorithm which respects
associativity and commutativity and can also erase Java intersections
without any special case. Incidentally, this lets us reverse a recent
change in scala-stm which was previously required due to two equivalent
types ending up with different signatures.

This commit also improves the way we handle intersections in Java
generic signature to produce more precise types when possible and to
ensure that we never emit a type that would lead to javac emitting
invalid bytecode (which can happen with Scala 2:
scala/bug#12300).

Fixes scala#5139.
smarter added a commit to dotty-staging/dotty that referenced this issue Mar 22, 2021
Thanks to scala#11603, we can now freely decide how we erase our intersection
types without compromising compatibility with Scala 2. We take advantage
of this by designing a new erasedGlb algorithm which respects
associativity and commutativity and can also erase Java intersections
without any special case. Incidentally, this lets us reverse a recent
change in scala-stm which was previously required due to two equivalent
types ending up with different signatures.

This commit also improves the way we handle intersections in Java
generic signature to produce more precise types when possible and to
ensure that we never emit a type that would lead to javac emitting
invalid bytecode (which can happen with Scala 2:
scala/bug#12300).

Fixes scala#5139.
michelou pushed a commit to michelou/scala3 that referenced this issue Mar 22, 2021
Thanks to scala#11603, we can now freely decide how we erase our intersection
types without compromising compatibility with Scala 2. We take advantage
of this by designing a new erasedGlb algorithm which respects
associativity and commutativity and can also erase Java intersections
without any special case. Incidentally, this lets us reverse a recent
change in scala-stm which was previously required due to two equivalent
types ending up with different signatures.

This commit also improves the way we handle intersections in Java
generic signature to produce more precise types when possible and to
ensure that we never emit a type that would lead to javac emitting
invalid bytecode (which can happen with Scala 2:
scala/bug#12300).

Fixes scala#5139.
michelou pushed a commit to michelou/scala3 that referenced this issue Mar 22, 2021
Thanks to scala#11603, we can now freely decide how we erase our intersection
types without compromising compatibility with Scala 2. We take advantage
of this by designing a new erasedGlb algorithm which respects
associativity and commutativity and can also erase Java intersections
without any special case. Incidentally, this lets us reverse a recent
change in scala-stm which was previously required due to two equivalent
types ending up with different signatures.

This commit also improves the way we handle intersections in Java
generic signature to produce more precise types when possible and to
ensure that we never emit a type that would lead to javac emitting
invalid bytecode (which can happen with Scala 2:
scala/bug#12300).

Fixes scala#5139.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants