Skip to content

is not a trait error when extends Java annotation #12840

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

Closed
xuwei-k opened this issue Jun 16, 2021 · 5 comments
Closed

is not a trait error when extends Java annotation #12840

xuwei-k opened this issue Jun 16, 2021 · 5 comments

Comments

@xuwei-k
Copy link
Contributor

xuwei-k commented Jun 16, 2021

Compiler version

3.0.1-RC1, 3.0.2-RC1-bin-20210615-a8bbc0e-NIGHTLY

Minimized code

A.scala

package example

class A

Named.java

package example;

public @interface Named {}

abstract class NamedImpl implements Named {}

Output

[error] 5 |abstract class NamedImpl implements Named {}
[error]   |                                    ^^^^^
[error]   |                                    class Named is not a trait
[error] one error found

Expectation

compile success because javac Named.java success.

Note

Could not build playframework due to this issue.

https://github.com/playframework/playframework/blob/d1f59e95892c69b1bd7e03d63997bb8c54a8de8a/core/play/src/main/java/play/inject/NamedImpl.java#L7-L17

@xuwei-k xuwei-k changed the title "is not a trait" error when extends Java anntation is not a trait error when extends Java annotation Jun 16, 2021
@romanowski
Copy link
Contributor

3.0 is also affected.

@odersky
Copy link
Contributor

odersky commented Jun 16, 2021

It's most likely a problem in JavaParser.

@smarter
Copy link
Member

smarter commented Jun 16, 2021

We intentionally pretend that Java annotations are classes, this is done to make them closer to Scala annotations and avoid special-casing, but it does mean that things can go wrong sometimes. Scala 2 used to do the same but switched to representing them as traits a while ago (#5690 (comment) has the history) which carries its own set of challenges, and even if we changed our representation now, we'd still need to support the old representation coming from tasty files compiled with scala 3.0. In brief, it's a mess.

@xuwei-k
Copy link
Contributor Author

xuwei-k commented Jul 14, 2021

another example

build.sbt

libraryDependencies += "com.typesafe.play" %% "play" % "2.8.8" cross CrossVersion.for3Use2_13

scalaVersion := "3.0.1"

Main.scala

object Main {
  new play.inject.NamedImpl("")
}

log

[info] compiling 1 Scala source to /Users/kenji/scala-3-issue-12840/target/scala-3.0.1/classes ...
Error while emitting Main.scala
[info] exception occurred while compiling /Users/kenji/scala-3-issue-12840/Main.scala
java.lang.AssertionError: assertion failed: Invalid interfaces in Lplay/inject/NamedImpl;: List(Ljavax/inject/Named;, Ljava/io/Serializable;) while compiling /Users/kenji/scala-3-issue-12840/Main.scala
[error] ## Exception when compiling 1 sources to /Users/kenji/scala-3-issue-12840/target/scala-3.0.1/classes
[error] java.lang.AssertionError: assertion failed: Invalid interfaces in Lplay/inject/NamedImpl;: List(Ljavax/inject/Named;, Ljava/io/Serializable;)
[error] scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
[error] dotty.tools.backend.jvm.BTypes$ClassBType.checkInfoConsistency(BTypes.scala:623)
[error] dotty.tools.backend.jvm.BTypes$ClassBType.info_$eq(BTypes.scala:600)
[error] dotty.tools.backend.jvm.BTypesFromSymbols.setClassInfo(BTypesFromSymbols.scala:200)
[error] dotty.tools.backend.jvm.BTypesFromSymbols.classBTypeFromSymbol$$anonfun$1(BTypesFromSymbols.scala:93)
[error] scala.collection.mutable.HashMap.getOrElse(HashMap.scala:436)
[error] dotty.tools.backend.jvm.BTypesFromSymbols.classBTypeFromSymbol(BTypesFromSymbols.scala:94)
[error] dotty.tools.backend.jvm.BCodeHelpers$BCInnerClassGen.getClassBTypeAndRegisterInnerClass(BCodeHelpers.scala:270)
[error] dotty.tools.backend.jvm.BCodeHelpers$BCInnerClassGen.getClassBTypeAndRegisterInnerClass$(BCodeHelpers.scala:210)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.getClassBTypeAndRegisterInnerClass(BCodeSkelBuilder.scala:63)
[error] dotty.tools.backend.jvm.BCodeHelpers.primitiveOrClassToBType$2$$anonfun$1(BCodeHelpers.scala:808)
[error] scala.collection.immutable.HashMap.getOrElse(HashMap.scala:683)
[error] dotty.tools.backend.jvm.BCodeHelpers.primitiveOrClassToBType$1(BCodeHelpers.scala:808)
[error] dotty.tools.backend.jvm.BCodeHelpers.dotty$tools$backend$jvm$BCodeHelpers$$typeToTypeKind(BCodeHelpers.scala:827)
[error] dotty.tools.backend.jvm.BCodeHelpers$BCInnerClassGen.toTypeKind(BCodeHelpers.scala:299)
[error] dotty.tools.backend.jvm.BCodeHelpers$BCInnerClassGen.toTypeKind$(BCodeHelpers.scala:210)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.toTypeKind(BCodeSkelBuilder.scala:63)
[error] dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genApply(BCodeBodyBuilder.scala:712)
[error] dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:346)
[error] dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genStat(BCodeBodyBuilder.scala:96)
[error] dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genBlock$$anonfun$1(BCodeBodyBuilder.scala:895)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.immutable.List.foreach(List.scala:333)
[error] dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genBlock(BCodeBodyBuilder.scala:895)
[error] dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:417)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.emitNormalMethodBody$1(BCodeSkelBuilder.scala:765)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.genDefDef(BCodeSkelBuilder.scala:800)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.gen(BCodeSkelBuilder.scala:608)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.gen$$anonfun$1(BCodeSkelBuilder.scala:614)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.immutable.List.foreach(List.scala:333)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.gen(BCodeSkelBuilder.scala:614)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.genPlainClass(BCodeSkelBuilder.scala:229)
[error] dotty.tools.backend.jvm.GenBCodePipeline$Worker1.visit(GenBCode.scala:229)
[error] dotty.tools.backend.jvm.GenBCodePipeline$Worker1.run(GenBCode.scala:194)
[error] dotty.tools.backend.jvm.GenBCodePipeline.buildAndSendToDisk(GenBCode.scala:529)
[error] dotty.tools.backend.jvm.GenBCodePipeline.run(GenBCode.scala:495)
[error] dotty.tools.backend.jvm.GenBCode.run(GenBCode.scala:60)
[error] dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:303)
[error] scala.collection.immutable.List.map(List.scala:246)
[error] dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:304)
[error] dotty.tools.backend.jvm.GenBCode.runOn(GenBCode.scala:64)
[error] dotty.tools.dotc.Run.runPhases$4$$anonfun$4(Run.scala:205)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1323)
[error] dotty.tools.dotc.Run.runPhases$5(Run.scala:216)
[error] dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:224)
[error] scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
[error] dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:67)
[error] dotty.tools.dotc.Run.compileUnits(Run.scala:231)
[error] dotty.tools.dotc.Run.compileSources(Run.scala:166)
[error] dotty.tools.dotc.Run.compile(Run.scala:150)
[error] dotty.tools.dotc.Driver.doCompile(Driver.scala:39)
[error] dotty.tools.xsbt.CompilerBridgeDriver.run(CompilerBridgeDriver.java:88)
[error] dotty.tools.xsbt.CompilerBridge.run(CompilerBridge.java:22)
[error] sbt.internal.inc.AnalyzingCompiler.compile(AnalyzingCompiler.scala:91)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$7(MixedAnalyzingCompiler.scala:186)
[error] scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
[error] sbt.internal.inc.MixedAnalyzingCompiler.timed(MixedAnalyzingCompiler.scala:241)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4(MixedAnalyzingCompiler.scala:176)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4$adapted(MixedAnalyzingCompiler.scala:157)
[error] sbt.internal.inc.JarUtils$.withPreviousJar(JarUtils.scala:239)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compileScala$1(MixedAnalyzingCompiler.scala:157)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compile(MixedAnalyzingCompiler.scala:204)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1(IncrementalCompilerImpl.scala:528)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1$adapted(IncrementalCompilerImpl.scala:528)
[error] sbt.internal.inc.Incremental$.$anonfun$apply$5(Incremental.scala:174)
[error] sbt.internal.inc.Incremental$.$anonfun$apply$5$adapted(Incremental.scala:172)
[error] sbt.internal.inc.Incremental$$anon$2.run(Incremental.scala:457)
[error] sbt.internal.inc.IncrementalCommon$CycleState.next(IncrementalCommon.scala:116)
[error] sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:56)
[error] sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:52)
[error] sbt.internal.inc.IncrementalCommon.cycle(IncrementalCommon.scala:261)
[error] sbt.internal.inc.Incremental$.$anonfun$incrementalCompile$8(Incremental.scala:412)
[error] sbt.internal.inc.Incremental$.withClassfileManager(Incremental.scala:499)
[error] sbt.internal.inc.Incremental$.incrementalCompile(Incremental.scala:399)
[error] sbt.internal.inc.Incremental$.apply(Incremental.scala:166)
[error] sbt.internal.inc.IncrementalCompilerImpl.compileInternal(IncrementalCompilerImpl.scala:528)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileIncrementally$1(IncrementalCompilerImpl.scala:482)
[error] sbt.internal.inc.IncrementalCompilerImpl.handleCompilationError(IncrementalCompilerImpl.scala:332)
[error] sbt.internal.inc.IncrementalCompilerImpl.compileIncrementally(IncrementalCompilerImpl.scala:420)
[error] sbt.internal.inc.IncrementalCompilerImpl.compile(IncrementalCompilerImpl.scala:137)
[error] sbt.Defaults$.compileIncrementalTaskImpl(Defaults.scala:2357)
[error] sbt.Defaults$.$anonfun$compileIncrementalTask$2(Defaults.scala:2314)
[error] sbt.internal.io.Retry$.apply(Retry.scala:46)
[error] sbt.internal.io.Retry$.apply(Retry.scala:28)
[error] sbt.internal.io.Retry$.apply(Retry.scala:23)
[error] sbt.internal.server.BspCompileTask$.compute(BspCompileTask.scala:31)
[error] sbt.Defaults$.$anonfun$compileIncrementalTask$1(Defaults.scala:2310)
[error] scala.Function1.$anonfun$compose$1(Function1.scala:49)
[error] sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:62)
[error] sbt.std.Transform$$anon$4.work(Transform.scala:68)
[error] sbt.Execute.$anonfun$submit$2(Execute.scala:282)
[error] sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:23)
[error] sbt.Execute.work(Execute.scala:291)
[error] sbt.Execute.$anonfun$submit$1(Execute.scala:282)
[error] sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:265)
[error] sbt.CompletionService$$anon$2.call(CompletionService.scala:64)
[error] java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error] java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
[error] java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error] java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
[error] java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
[error] java.lang.Thread.run(Thread.java:748)

@dotordogh
Copy link

We are also running into this issue when trying to cross-build twitter/util's util-reflect. An example is the Widget and it's implementation. We get the same error:

[error] -- [E104] Syntax Error: /Users/dordogh/workspace/source/util/util-reflect/src/test/java/com/twitter/util/reflect/WidgetImpl.java:6:28
[error] 6 |class WidgetImpl implements Widget, Serializable {
[error]   |                            ^^^^^^
[error]   |                            class Widget is not a trait

This is running with scala 3.0.2-RC1.

@smarter smarter added this to the 3.3.0-RC1 milestone Jun 20, 2022
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.
@Kordyjan Kordyjan modified the milestones: 3.3.0-RC1, 3.3.0 Aug 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants