Mattias Reichel created GROOVY-12434:
----------------------------------------
Summary: Implementing a precompiled trait fails with "Duplicate
annotation" when a trait method has a TYPE_USE annotation on its return type
Key: GROOVY-12434
URL: https://issues.apache.org/jira/browse/GROOVY-12434
Project: Groovy
Issue Type: Bug
Components: Compiler
Affects Versions: 6.0.0
Reporter: Mattias Reichel
When a trait method's return type carries a {{TYPE_USE}} annotation, such as
JSpecify's {{@Nullable}}, the trait is written with that annotation twice. A
class in a later compilation that implements the trait then fails to compile on
Groovy 6.0.0 with {{java.lang.annotation.AnnotationFormatError: Duplicate
annotation}}.
This compiles on Groovy 5.1.3 and fails on 6.0.0. It does not happen when the
trait and the implementing class are in the same compilation, or when the
annotation is on a parameter instead of the return type. It fails whether the
trait was compiled by 5.1.3 or by 6.0.0, so traits in existing jars built with
Groovy 5 are affected too.
h3. Steps to reproduce
{code:title=trait/MaybeNull.java}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface MaybeNull { }
{code}
{code:title=trait/Named.groovy}
trait Named {
@MaybeNull String getName() { null }
}
{code}
{code:title=src/Person.groovy}
class Person implements Named { }
{code}
{noformat}
mkdir lib out
javac -d lib trait/MaybeNull.java
java -cp groovy-6.0.0.jar:lib org.codehaus.groovy.tools.FileSystemCompiler -cp
lib -d lib trait/Named.groovy
java -cp groovy-6.0.0.jar org.codehaus.groovy.tools.FileSystemCompiler -cp lib
-d out src/Person.groovy
{noformat}
h3. Expected
Compilation succeeds, as on Groovy 5.1.3.
h3. Actual
{noformat}
>>> a serious error occurred: Duplicate annotation for class: interface
>>> MaybeNull: @MaybeNull()
>>> stacktrace:
java.lang.annotation.AnnotationFormatError: Duplicate annotation for class:
interface MaybeNull: @MaybeNull()
at
java.base/sun.reflect.annotation.TypeAnnotationParser.mapTypeAnnotations(TypeAnnotationParser.java:382)
at
java.base/sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl.<init>(AnnotatedTypeFactory.java:138)
...
at
org.codehaus.groovy.vmplugin.v8.Java8.configureClassNode(Java8.java:392)
at org.codehaus.groovy.ast.ClassNode.lazyClassInit(ClassNode.java:192)
at org.codehaus.groovy.ast.ClassNode.getMethods(ClassNode.java:1090)
at org.codehaus.groovy.ast.ClassNode.getMethods(ClassNode.java:1089)
at
org.codehaus.groovy.transform.trait.TraitComposer.applyTrait(TraitComposer.java:134)
at
org.codehaus.groovy.transform.trait.TraitComposer.doExtendTraits(TraitComposer.java:110)
...
{noformat}
h3. Notes
{{javap -v}} shows the return type annotation twice on the trait's interface
method and on its helper method. The class files are the same whether the trait
is compiled by 5.1.3 or by 6.0.0:
{noformat}
public abstract java.lang.String getName();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
RuntimeVisibleAnnotations:
0: #9()
org.codehaus.groovy.transform.trait.Traits$Implemented
RuntimeVisibleTypeAnnotations:
0: #10(): METHOD_RETURN
MaybeNull
1: #10(): METHOD_RETURN
MaybeNull
{noformat}
{{Named$Trait$Helper.getName(Named)}} has the same two {{METHOD_RETURN}}
entries. The same method on a plain class or interface is written with a single
entry.
Groovy 5.1.3 accepts the duplicate because it does not read the annotated
return type when it initializes the trait's {{ClassNode}}. Groovy 6.0.0 reads
it through reflection in {{Java8.configureClassNode}}, where the JDK rejects
the duplicate. Groovy 5.1.3 can still implement a trait compiled by 6.0.0.
GROOVY-12413 fixed similar duplicated {{TYPE_USE}} annotations on record
components and tuple-constructor parameters. The trait transform seems to copy
the return type annotations as well as the method annotations.
Found while moving Apache Grails to Groovy 6: a JSpecify {{@Nullable}} on a
return type in the Grails unit testing traits ({{ControllerUnitTest}},
{{UrlMappingsUnitTest}}, {{InterceptorUnitTest}}) makes every test class that
implements them fail to compile. The workaround is to leave {{TYPE_USE}}
annotations off trait method return types.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)