-
Notifications
You must be signed in to change notification settings - Fork 1.1k
AbstractMethodError when inheriting trait from java abstract class and referencing val from the trait #13702
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
Comments
This issue was picked for the Issue Spree 14 of 12th April which takes place in a week from now. @gagandeepkalra and @ghostbuster91 will be working on it. If you have any insight into the issue or guidance on how to fix it, please leave it here. |
Here is what we discovered: Currently it is impossible to have a regular java class inherit a scala trait that has a trait ScalaTrait {
val foo: String = "abc"
}
public class JavaClass implements ScalaTrait {
} object Test {
def main(args: Array[String]): Unit = {
val javaC = new JavaClass
assert(javaC.foo == "abc" )
}
}
Above code doesn't compile because:
That is because of how the trait ScalaTrait {
val foo :String = "abc"
} gets transformed into: package <empty> {
@SourceFile("tests/run/i13702_2/ScalaTrait.scala") trait ScalaTrait extends
Object
{
def $init(): Unit =
{
this.ScalaTrait$_setter_$foo_$eq("abc")
()
}
def foo(): String
def ScalaTrait$_setter_$foo_$eq(x$0: String): Unit
}
} Java doesn't implement the getter and setter methods. In case of abstract class the code compiles, because an abstract java class is allowed to have unimplemented members and for the scala compiler it seems that everything is ok. It crashes in runtime because the getter is still unimplemented. Should fix the compiler to fail the compilation in such cases? In case when there is a scala class between the java class and a scala trait with default value (like in #10509), that scala class implements the abstract trait members and is responsible for calling the initialization method class ScalaClass extends ScalaTrait {
}
trait ScalaTrait {
val foo :String = "abc"
} gets transformed into: package <empty> {
@SourceFile("tests/run/i13702_2/ScalaTrait.scala") trait ScalaTrait extends
Object
{
def $init(): Unit =
{
this.ScalaTrait$_setter_$foo_$eq("abc")
()
}
def foo(): String
def ScalaTrait$_setter_$foo_$eq(x$0: String): Unit
}
@SourceFile("tests/run/i13702_2/ScalaTrait.scala") class ScalaClass extends
Object
, ScalaTrait {
def <init>(): Unit =
{
super()
super[ScalaTrait].$init()
scala.runtime.Statics#releaseFence()
()
}
private var foo: String
def foo(): String = this.foo
def ScalaTrait$_setter_$foo_$eq(x$0: String): Unit = this.foo = x$0
}
}
We don't see any way how that could work for a java class. Even if we changed the encoding and didn't have to implement the abstract method, the We assumme that changing |
That would certainly be better than crashing at runtime, so if you think that's easier to implement, go for it. |
We can probably reduce scope, considering we have 2 scenarios For the
and keep the trait initialization as is for |
This doesn't sound like a binary compatible change, so not something we can do in scala 3. |
This issue was picked for the Issue Spree 15 of May 3rd which takes place a week from now. @gagandeepkalra and @ghostbuster91 will be working on it. If you have any insight into the issue or guidance on how to fix it, please leave it here. |
Reproduced today: ➜ ~/scala-snippets-6 cat 13702/ScalaTrait.scala
trait ScalaTrait {
def foo1: String
def foo2: String = "default foo value 2"
val val1: String
val val2: String = "default val value 2"
}
➜ ~/scala-snippets-6 cat 13702/JavaAbstractClass.java
public abstract class JavaAbstractClass implements ScalaTrait {
public String javaFoo1() { return foo1(); }
public String javaFoo2() { return foo2(); }
public String javaVal1() { return val1(); }
public String javaVal2() { return val2(); }
}
➜ ~/scala-snippets-6 cat 13702/ScalaTrait.scala
trait ScalaTrait {
def foo1: String
def foo2: String = "default foo value 2"
val val1: String
val val2: String = "default val value 2"
}
➜ ~/scala-snippets-6 scala 13702
Success(implemented foo in scala 1)
Success(default foo value 2)
Success(implemented val in scala 1)
Exception in thread "main" java.lang.AbstractMethodError: Method ScalaClass$.val2()Ljava/lang/String; is abstract
at ScalaClass$.val2(ScalaClass.scala)
at JavaAbstractClass.javaVal2(JavaAbstractClass.java:6)
at ScalaClass$.main$$anonfun$4(ScalaClass.scala:12)
at scala.util.Try$.apply(Try.scala:217)
at ScalaClass$.main(ScalaClass.scala:12)
at ScalaClass.main(ScalaClass.scala)
➜ ~/scala-snippets-6 scala --version
Scala code runner version: 1.4.0
Scala version (default): 3.5.0
➜ ~/scala-snippets-6 javap 13702/.scala-build/13702_b85f7ed1e7-6012b1875a/classes/main/ScalaTrait.class
Compiled from "ScalaTrait.scala"
public interface ScalaTrait {
public static void $init$(ScalaTrait);
public abstract java.lang.String foo1();
public static java.lang.String foo2$(ScalaTrait);
public default java.lang.String foo2();
public abstract java.lang.String val1();
public abstract java.lang.String val2();
public abstract void ScalaTrait$_setter_$val2_$eq(java.lang.String);
} |
It's not clear to me what are the next steps to fix this issue. If I understand correctly, with the current scheme, a class implementing a trait with a How can that ever work when implementing a Scala trait from Java, as we can't influence the compilation of the Java code? If that doesn't work, why should we fix the special case where the Java class is again subclassed in Scala? And if we fix this specific scenario, how should we do it? Should the compiler somehow figure out that |
We discussed this today during the compiler meeting and headed towards "won't fix" for this issue. The rational is the following:
|
This is the exact same bug as in Scala 2: scala/bug#12456
problem
The program compiles fine, but an exception is thrown at runtime:
The text was updated successfully, but these errors were encountered: