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)

Reply via email to