-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Rename @alpha to @targetName #10149
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
Rename @alpha to @targetName #10149
Conversation
ebe5828
to
40f7b3d
Compare
Does this change have any effect over the possibility solving #7942 ? |
| Name clash between defined and inherited member: | ||
| def f(): Int in class A at line 3 and | ||
| def g(): Int in class B at line 5 | ||
| have the same name and type after erasure. |
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.
Regarding the error message when no @targetName
is applied, maybe it's worth proposing the user the option of using @targetName
to resolve the error within the error message itself.
BTW, is there a purpose for |
I think |
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.
LGTM, with some minor comments.
The following code now compiles:
import scala.annotation.targetName
class B {
final def foo(): Int = ???
def bar(): Int = ???
}
class C extends B {
@targetName("fooB") def foo(): Int = ???
@targetName("barB") def bar(): Int = ???
}
Instead, I think the compiler should report two errors above. The reason is that @targetName
is intended to overcome the limitations of target platforms, rather than change the (static) semantics of the surface language.
The following code currently compiles, it should be rejected:
class C {
@targetName("foo B") def foo(): Int = ???
}
The following code currently compiles, should be rejected:
class C(x: String) {
@targetName(x) def foo(): Int = ???
}
/cc @bishabosha Could you please comment about Scala 2 forward interoperability?
The following comment is intended for open discussions in the future, not for this PR.
explicit annotation VS synthesized annotation
The advantage of explicit annotation is obvious: better debuggability and Java interoperability.
On the other hand, when programmers don't care about Java interoperability & debuggability (e.g. during prototyping), can the compiler automatically synthesize target names to overcome the limitations of the target platform?
There are already many places where the compiler performs synthesis to overcome platform limitations, is it possible to hide the implementation detail for targetName
by default, and allows users to optionally use @targetName
when they care about debuggability and Java Interop?
I don't think so. I believe the only characters that should be rejected are We should forbid unpaired surrogate characters as well. |
But Scala 2 allows |
Good point, as Scala already allows such names with backquotes, for consistency they need to be supported (However, for better platform portability, it seems better for language design decisions to be independent of a particular platform). |
In practice, the other platforms already have to handle what the JVM can handle. |
The backend encodes names so that illegal method names are avoided. For instance, def `..` = 1 will end up as
|
That's quite a fundamental question we need to answer here. Should @TargetNAME be allowed to refine the overriding behavior? Right now the answer is yes. If we change that it could get tricky and also expensive. Here's a problematic example: trait A:
def f: Any
trait B:
@targetName("ff") def f: Int
abstract class C extends A, B That's currently legal. If it should be illegal, this means that that no class could ever extend both
it still would not work since each of the two definitions of So this would be a pretty harsh restriction, and it's particular annoying since it arises from global behavior and cannot be controlled by local knowledge alone. The other objection is that even checking a situation like this is expensive. Enumerating overriding pairs is quadratic in the size of the program. It's a significant part of total compile-time when compiling things like stdlib. Here we would make it twice as expensive. On the other hand, what would we really gain from a restriction? |
class C(x: String) {
@targetName(x) def foo(): Int = ???
} Is there a way to do this by annotating the definition of the |
To clarify I think the following example will work: trait A {
def f: Any
}
trait B {
@targetName("ff")
def f: Int
}
abstract class C extends A with B {
def f: Any = ""
@targetName("ff")
def f: Int = 3
} The reason is that overriding check takes the whole type into consideration, while conflicit check only takes signature and target name into consideration.
What I propose here is the following:
With the scheme above, will performance be less a concern?
I think it is safer to guard against abuse of language features: class A {
final def f(): Int = ???
}
class B extends A {
@targetName("ff")
def f(): Int = ???
} |
No that does not work. |
Yes, I missed that
The use cases intended by the first is clear, while it is unclear for the 2nd. Another worry about the 2nd is that it may create inconsistencies. For example, can we override the target name directly? trait A {
@targetName("ff")
def f(): Int
}
class B extends A {
def ff(): Int = ???
} In the current PR, the code above is rejected, which makes reasoning about overriding more complex. |
Here's what's stated in targetName.md:
So, both regular names and target names need to match, otherwise it's not an override. After erasure, it's only the targetname that matters, but it is verified that this change does not introduce any new overrides. |
The changes to Tasty should be fine in this case for Scala 2 forward compat, it makes it easier to work with than just the |
This will streamline some of the later matching code.
Alternatives that have the same signature but different targetNames are considered to be separate.
So far, annotations of symbols printed as just the class name; also include their arguments. But avoid this for Body annotations, since they just repeat the possibly large body of an inline function.
In case of a double definition error that's due to an erasure clash, add a hint that one can resolve it with a @TargetNAME annotation
Don't issue the hint of one of the conflicting definitions already carries a @TargetNAME.
Co-authored-by: Fengyun Liu <liu@fengy.me>
test performance please |
performance test scheduled: 4 job(s) in queue, 1 running. |
Performance test finished successfully: Visit http://dotty-bench.epfl.ch/10149/ to see the changes. Benchmarks is based on merging with master (aaec84d) |
test performance please |
performance test scheduled: 8 job(s) in queue, 1 running. |
We now restrict the power of @TargetNAME where it deviates from normal semantics. The new rules are as follows:
So this means that targetNames can only avoid breakage due to erasure clashes. They cannot introduce new distinctions on their own. I also found a way to make this efficient. The @liufengyun Can you review the latest commits implementing this? |
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.
LGTM
Performance test finished successfully: Visit http://dotty-bench.epfl.ch/10149/ to see the changes. Benchmarks is based on merging with master (0700573) |
... and make @TargetNAME work to avoid double definitions. Two definitions now
match
only if they have the same names, signatures and erased names. The erased name of a definition is the name given in a@targetName
annotation, or elsethe declared name, if no
@targetName
annotation is given.Fixes #7942