Skip to content
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

Represent Java annotations as interfaces so they can be extended, and disallow various misuses of them #16260

Merged
merged 4 commits into from
Nov 14, 2022

Conversation

smarter
Copy link
Member

@smarter smarter commented Oct 28, 2022

Inspired by the work of hrhino in Scala 2 (scala/scala#6869).

@smarter smarter requested a review from sjrd October 28, 2022 15:26
@smarter smarter force-pushed the proper-java-annots branch 2 times, most recently from d28e23f to 30e4aff Compare October 31, 2022 12:56
Copy link
Member

@sjrd sjrd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! I only have minor comments

Comment on lines 14 to 15
object Test {
def main(args: Array[String]) = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the changes in this file intended? Were they temporarily done to be compilable by Scala 2?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the changes in this file intended?

The addition of new is intended, the other ones weren't and I removed them. I've updated the commit message of the corresponding commit to explain why this was needed:

This change is not fully backwards source compatible: this is illustrated
by the diffs in tests/run/repeatable/Test_1.scala:

    -@FirstLevel_0(Array(Plain_0(4), Plain_0(5)))
    +@FirstLevel_0(Array(new Plain_0(4), new Plain_0(5)))

Here, FirstLevel_0 takes an array of `Plain_0` annotations as arguments, and in
previous releases of Scala 3 we could put `Plain_0(4)` in this array without
`new`. This is because the compiler generates a "constructor proxy" apply method
for classes, but this no longer works since `Plain_0` is now a trait. While we
could potentially tweak the constructor proxy logic to handle this case, it
seems simpler to require a `new` here, both because Scala 2 does it too and
because it ensures that user code that inspects the annotation tree does not
have to deal with constructor proxies.

The Trait and Abstract flags are not supposed to be set together, and we don't
set Abstract when deserializing Java interfaces from classfiles so we shouldn't
do it when reading them from source code either.

While we're at it, this commit also drops Flags.Trait which is implied by Flags.JavaInterface.
…ion,Annotation}

This compiler fiction isn't necessary to handle them as annotations and could
lead to runtime crashes when upcasting an annotation value.
Previously we treated Java annotations as if they were classes, like Scala
annotations. For example, given

    @interface Ann { int foo(); }

we pretended it was defined as:

    abstract class Ann(foo: Int) extends java.lang.annotation.Annotation { def foo(): Int }

We take advantage of this to type annotation trees as if they were
new calls, for example `@Ann(1)` is typed as `new Ann(1)`.

Pretending that annotations are classes is fine most of the time and matches
what Scala 2.12 did, but it's problematic because the JVM treats annotations as
interfaces. In practice this was only an issue with code trying to extend
Java annotations, which would either be rejected at compile-time or miscompiled
before this commit.

This commit switches our representation of annotations to be trait-based
instead:

    trait Ann(foo: Int) extends java.lang.annotation.Annotation { def foo(): Int }

Classes are then free to extend annotations using the same pattern as in Scala 2.13:

    class Foo extends Ann {val annotationType = classOf[Retention]; def foo(): Int = 1}

Notice that we still pretend these traits have constructors, this lets us type
annotation trees in much the same way as before, and crucially it means that
macros that depended on the exact tree shape of annotation trees can continue to
work, as demonstrated by the annot-java-tree test extracted from wartremover.
To prevent miscompilation issues, we disallow passing arguments to the
annotation constructor in `extends` clause.

This change is not fully backwards source compatible: this is illustrated
by the diffs in tests/run/repeatable/Test_1.scala:

    -@FirstLevel_0(Array(Plain_0(4), Plain_0(5)))
    +@FirstLevel_0(Array(new Plain_0(4), new Plain_0(5)))

Here, FirstLevel_0 takes an array of `Plain_0` annotations as arguments, and in
previous releases of Scala 3 we could put `Plain_0(4)` in this array without
`new`. This is because the compiler generates a "constructor proxy" apply method
for classes, but this no longer works since `Plain_0` is now a trait. While we
could potentially tweak the constructor proxy logic to handle this case, it
seems simpler to require a `new` here, both because Scala 2 does it too and
because it ensures that user code that inspects the annotation tree does not
have to deal with constructor proxies.

The treatment of default arguments to annotations stays unchanged from 85cd1cf.

Fixes scala#5690. Fixes scala#12840. Fixes scala#14199.
A Java interface that extends java.lang.annotation.Annotation might not be a
Java annotation, so to prevent false positives we need to keep track of whether
an interface is also an annotation when parsing Java sources and bytecode.
@smarter smarter requested a review from sjrd November 14, 2022 13:17
@smarter smarter enabled auto-merge November 14, 2022 14:27
@smarter smarter merged commit 970d119 into scala:main Nov 14, 2022
@smarter smarter deleted the proper-java-annots branch November 14, 2022 14:31
@Kordyjan Kordyjan added this to the 3.3.0 milestone Aug 1, 2023
nicolasstucki added a commit to dotty-staging/dotty that referenced this pull request Dec 14, 2023
smarter added a commit that referenced this pull request Dec 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants