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

Carry over Scalac semantics for Java annotations #5690

Closed
panacekcz opened this issue Jan 9, 2019 · 8 comments
Closed

Carry over Scalac semantics for Java annotations #5690

panacekcz opened this issue Jan 9, 2019 · 8 comments

Comments

@panacekcz
Copy link
Contributor

Looks like ClassfileParser.addAnnotationConstructor.addConstr is missing Flags.Method, so the synthetic constructor for a java annotation is created as a val instead of a method.

Even with that, instantiating java annotations should better be a compile error.

Test code:

object AnnotInst{
  def main(a: Array[String]) = {
    new java.lang.annotation.Inherited
  }
}

Dotc output:

java.lang.AssertionError: assertion failed: not a method-symbol: val <init>
	at scala.Predef$.assert(Predef.scala:223)
	at scala.tools.nsc.backend.jvm.BCodeHelpers$BCInnerClassGen.asmMethodType(BCodeHelpers.scala:253)
	at scala.tools.nsc.backend.jvm.BCodeHelpers$BCInnerClassGen.asmMethodType$(BCodeHelpers.scala:252)
	at scala.tools.nsc.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.asmMethodType(BCodeSkelBuilder.scala:50)
	at scala.tools.nsc.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genCallMethod(BCodeBodyBuilder.scala:1177)
	at scala.tools.nsc.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genApply(BCodeBodyBuilder.scala:753)
	at scala.tools.nsc.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:344)
	at scala.tools.nsc.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:414)
	at scala.tools.nsc.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.emitNormalMethodBody$1(BCodeSkelBuilder.scala:602)
	at scala.tools.nsc.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.genDefDef(BCodeSkelBuilder.scala:635)
	at scala.tools.nsc.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.gen(BCodeSkelBuilder.scala:506)
	at scala.tools.nsc.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.$anonfun$gen$1(BCodeSkelBuilder.scala:508)
	at scala.tools.nsc.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.$anonfun$gen$1$adapted(BCodeSkelBuilder.scala:508)
	at scala.collection.immutable.List.foreach(List.scala:392)
	at scala.tools.nsc.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.gen(BCodeSkelBuilder.scala:508)
	at scala.tools.nsc.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.genPlainClass(BCodeSkelBuilder.scala:116)
	at dotty.tools.backend.jvm.GenBCodePipeline$Worker1.visit(GenBCode.scala:212)
	at dotty.tools.backend.jvm.GenBCodePipeline$Worker1.run(GenBCode.scala:179)
	at dotty.tools.backend.jvm.GenBCodePipeline.buildAndSendToDisk(GenBCode.scala:417)
	at dotty.tools.backend.jvm.GenBCodePipeline.run(GenBCode.scala:383)
	at dotty.tools.backend.jvm.GenBCode.run(GenBCode.scala:53)
	at dotty.tools.dotc.core.Phases$Phase.$anonfun$runOn$1(Phases.scala:297)
	at scala.collection.immutable.List.map(List.scala:286)
	at dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:295)
	at dotty.tools.backend.jvm.GenBCode.runOn(GenBCode.scala:58)
	at dotty.tools.dotc.Run.$anonfun$compileUnits$3(Run.scala:172)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
	at dotty.tools.dotc.util.Stats$.trackTime(Stats.scala:49)
	at dotty.tools.dotc.Run.$anonfun$compileUnits$2(Run.scala:169)
	at dotty.tools.dotc.Run.$anonfun$compileUnits$2$adapted(Run.scala:167)
	at scala.collection.IndexedSeqOptimized.foreach(IndexedSeqOptimized.scala:36)
	at scala.collection.IndexedSeqOptimized.foreach$(IndexedSeqOptimized.scala:33)
	at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:198)
	at dotty.tools.dotc.Run.runPhases$1(Run.scala:167)
	at dotty.tools.dotc.Run.$anonfun$compileUnits$1(Run.scala:192)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
	at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:90)
	at dotty.tools.dotc.Run.compileUnits(Run.scala:147)
	at dotty.tools.dotc.Run.compileSources(Run.scala:134)
	at dotty.tools.dotc.Run.compile(Run.scala:118)
	at dotty.tools.dotc.Driver.doCompile(Driver.scala:33)
	at dotty.tools.dotc.Driver.process(Driver.scala:166)
	at dotty.tools.dotc.Driver.process(Driver.scala:135)
	at dotty.tools.dotc.Driver.process(Driver.scala:147)
	at dotty.tools.dotc.Driver.main(Driver.scala:174)
	at dotty.tools.dotc.Main.main(Main.scala)
Error while emitting AnnotInst.scala
assertion failed: not a method-symbol: val <init>
one error found
@Blaisorblade
Copy link
Contributor

Have tried adding the missing flag to fix the crash? Could you send a PR if that works?

@panacekcz
Copy link
Contributor Author

Yes, but if the correct result is compile error, it would only be a partial fix. So should I do that PR without adding a pos test? Should that PR close this issue?

@Blaisorblade
Copy link
Contributor

Such a PR should reference this issue without closing it. It'd still help.

I'd maybe add a test in pos/ with a comment that this should give an error, and maybe also the desired test in tests/pending/.

==
On the rest of the issue, I am not sure why and when the instantiation should fail; in this case, based on the Java error, the annotation class should probably be marked as abstract (with Flags.Abstract IIUC?). Are Java annotation classes always abstract?

$ javac foo.java
foo.java:3: error: Inherited is abstract; cannot be instantiated
		new java.lang.annotation.Inherited();
		^
1 error

panacekcz pushed a commit to panacekcz/dotty that referenced this issue Jan 16, 2019
Let constructors of java annotations be methods
panacekcz added a commit to panacekcz/dotty that referenced this issue Jan 16, 2019
Let constructors of java annotations be methods
@panacekcz
Copy link
Contributor Author

By JLS 9.6, annotation types are a special kind of interface, and by JLS 9.1.1.1 every interface is abstract.

@Blaisorblade
Copy link
Contributor

*JVM Spec quotes:

If the ACC_ANNOTATION flag is set, the ACC_INTERFACE flag must also be set.
If the ACC_INTERFACE flag is set, the ACC_ABSTRACT flag must also be set

Blaisorblade added a commit that referenced this issue Jan 17, 2019
@Blaisorblade
Copy link
Contributor

TLDR: Action plan: carry over Scalac fixes. (See previous commit for what exactly).

@Blaisorblade Blaisorblade changed the title AssertionError on instantiating java annotation Carry over Scalac semantics for Java annotations Jan 23, 2019
@smarter
Copy link
Member

smarter commented Jan 23, 2019

Action plan: kidnap @hrhino and get him to port all his fixes to dotty :).

@hrhino
Copy link
Contributor

hrhino commented Jan 23, 2019

You'll never find me!!

smarter added a commit to dotty-staging/dotty that referenced this issue Oct 28, 2022
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.

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

Fixes scala#5690. Fixes scala#12840. Fixes scala#14199.
little-inferno pushed a commit to little-inferno/dotty that referenced this issue Jan 25, 2023
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.
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

4 participants