-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Add @inheritSignature for forwards binary compatible overrides
#9141
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
Conversation
|
@sjrd my intuition tells me that this might break something Scala.js 😇 |
|
Thanks for the ping. In this case I don't suspect that this will cause any issue in Scala.js. We've got the same model as the JVM for binary signatures, overriding rules and all that. :) |
|
Things get hairy when involving Java sources. |
|
@retronym @lrytz Did anything come of the alternate idea that we had last week, of the compiler ignoring some annotated methods (in this case the more specific signature), and therefore binding to the parent method if this annotation is present? |
The example compiles when I retain the On a higher level, the classfiles produced when using What flags should
The VM doesn't look at generic signatures, and not at the I tested with an intermediate Scala class (that overrides the method without So the result seems to be that a method annotated |
|
On the upside (perhaps it was obvious already) but failing at javac time is better than failing at jit time... |
What happens if you use |
|
Marking the overriding method class C[A] {
def foo(a: A): A = a
}
class Sub extends C[String] {
@scala.annotation.inheritSignature
override def foo(a: String): String = a
}public class Test extends Sub {
public static void main(String[] args) {
System.out.println(new Test().foo("hello"));
}
}gives So at least for this example, the best tradeoff seems to be to remove class Buk extends Sub {
final def superFoo(a: String) = super[Sub].foo(a)
} |
64ebccf to
435dcaa
Compare
|
what if it's |
|
it (the overriding method, the bridge) was always |
|
The actual implementation method, my assumption was that if it's public but synthetic javac won't link against it, but I haven't checked that. |
|
Ah, I see. That doesn't work either... I tested javac 8 and 11. |
|
Hmm, could you post a full example? Here's what I tried and what I got: diff --git src/compiler/scala/tools/nsc/transform/PostErasure.scala src/compiler/scala/tools/nsc/transform/PostErasure.scala
index 6a96fa6574..284c8ec108 100644
--- src/compiler/scala/tools/nsc/transform/PostErasure.scala
+++ src/compiler/scala/tools/nsc/transform/PostErasure.scala
@@ -47,7 +47,7 @@ trait PostErasure extends InfoTransform with TypingTransformers with scala.refle
case ValueClass.BoxAndUnbox(v) => finish(v) // (new B(v)).unbox ==> v
case ValueClass.BoxAndCompare(v1, op, v2) => binop(v1, op, v2) // new B(v1) == new B(v2) ==> v1 == v2
case dd: DefDef if dd.symbol.hasAttachment[InheritedSignature] =>
- dd.symbol.setFlag(Flags.PRIVATE).resetFlag(Flags.OVERRIDE)
+ dd.symbol.setFlag(Flags.PRIVATE).setFlag(Flags.ARTIFACT).resetFlag(Flags.OVERRIDE)
dd.symbol.getAndRemoveAttachment[InheritedSignature].get.bridge.resetFlag(Flags.ARTIFACT).setFlag(Flags.OVERRIDE)
tree
case tree => treeclass C[A] {
def foo(a: A): A = a
}
class Sub extends C[String] {
@scala.annotation.inheritSignature
override def foo(a: String): String = a
// private java.lang.String foo(java.lang.String);
// descriptor: (Ljava/lang/String;)Ljava/lang/String;
// flags: ACC_PRIVATE, ACC_SYNTHETIC
// public java.lang.Object foo(java.lang.Object);
// descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
// flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
}public class Test extends Sub {
void sup() {
super.foo(""); // invokespecial #3 // Method Sub.foo:(Ljava/lang/Object;)Ljava/lang/Object;
}
void cur() {
foo(""); // invokevirtual #4 // Method foo:(Ljava/lang/Object;)Ljava/lang/Object;
}
} |
|
Oh, you were all along suggesting to make the actual implementation synthetic, not the bridge... I'm sorry I misunderstood 🤦♂️ Thank you for digging in! I pushed the change. |
|
By the way, it seems that the |
|
Added some compile-time errors in RefChecks, allows making assumptions later on. Added a WIP hack for the REPL issue - the problem is that modifying flags (like setting PRIVATE) isn't reverted in adaptToNewRun. @retronym better ideas are welcome, the WIP is mostly for showing what the issue is. |
we did it before at least for |
|
Also, we'll probably make this annotation |
|
Does this do the right/enough things for value classes? For example can I define |
|
We can't make this work for value classes to avoid boxing. A call Shifting away from value classes, we can't add new overrides to avoid boxing either: A call |
a84c1cb to
822294e
Compare
| * | ||
| * Invocations of `C.f: String` are rewritten to `A.f`. | ||
| */ | ||
| final class inheritSignature extends StaticAnnotation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scala/bug#12336 might apply here as well, but in this case we do want to pickle the annotation for separate compilation.
There seems to be support in the compiler for missing annotation classes, just using a method that carries an annotation with a missing symbol doesn't trigger an error. Mixin can fail though, as shown in the bug report.
Methods annotated `@inheritSignature` are emitted `private` in bytecode,
which allows adding binary compatible overrides when the bytecode
signature is refined.
```
class A { def f: AnyRef = "A.f" }
class C extends A { @inheritSignature override def f: String = "C.f" }
```
The method `C.f: String` is made `private`, the bridge remains `public`
(and the `bridge` flag is removed). Calls to `C.f` are rewritten to
`A.f`.
|
So the issue observed with scala/bug#12336 also applies here, and it's not enough to mark the annotation If we use some other library creates a sub-trait (compiled also with 2.13.6) using the sub-trait with an earlier compiler/stdlib (2.13.5) then fails: In fact, marking trait methods gives at runtime If the So maybe what we have to enforce is that |
|
This is not going to work if we assume that an existing compiler will ever encouter a newer standard library in which Compiling this with support for package scala.something
import scala.annotation.inheritSignature
class A[T] {
def f(x: T): Int = 0
}
class ASub extends A[Int] {
@inheritSignature override final def f(x: Int): Int = 1
}and then this with 2.13.6: package user
object Test {
def main(args: Array[String]): Unit = {
// java.lang.IllegalAccessError: class user.Test$ tried to access private method 'int scala.something.ASub.f(int)'
println((new scala.something.ASub2).f(1))
}
}booms. Seems risky, even if standard library and compiler versions are usually linked in Scala 2 world. Considering Scala 3, updating the standard library to a 2.13.7 that uses So we cannot ship this PR. |
Methods annotated
@inheritSignatureare emittedprivatein bytecode, which allows adding binary compatible overrides when the bytecode signature is refined.The method
C.f: Stringis madeprivate, the bridge remainspublic(and the). Calls tobridgeflag is removedC.fare rewritten toA.f.Closes scala/bug#11804. Hat-tip to @szeiger for the idea and for doing the initial research.