[ 
https://issues.apache.org/jira/browse/GROOVY-12413?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Paul King updated GROOVY-12413:
-------------------------------
    Description: 
When a record component or a {{@TupleConstructor}} property has a primitive 
type annotated with a {{TYPE_USE}} annotation, the compiler writes the type 
annotation to the wrong places or writes it several times.

h3. Records

{code:groovy}
import java.lang.annotation.*

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
@interface NonNeg {}

record Point(@NonNeg int x, @NonNeg String s) {}

def rc = Point.recordComponents[0]
println rc.annotatedType.annotations
{code}

Actual:
{noformat}
java.lang.annotation.AnnotationFormatError: Duplicate annotation for class: 
interface NonNeg: @NonNeg()
{noformat}

The {{int}} component's type annotation is written three times each to the 
Record attribute, the backing field, the accessor return type and the canonical 
constructor parameter. Any reflective read of it fails with the error above, 
including:
* {{RecordComponent.getAnnotatedType()}}
* {{Field.getAnnotatedType()}}
* {{Method.getAnnotatedReturnType()}}
* {{Constructor.getAnnotatedParameterTypes()}}

For the constructor this fails for every parameter, including the {{String}} 
one. Reference-type components ({{@NonNeg String s}}) are otherwise written 
correctly.

Expected: a single {{@NonNeg}} on each of the four places, as javac produces 
for the equivalent Java record.

h3. @TupleConstructor

{code:groovy}
@groovy.transform.TupleConstructor
class C { @NonNeg int a; @NonNeg String s }

println C.getDeclaredConstructor(int, 
String).annotatedParameterTypes*.annotations
{code}

Actual: {{[[@NonNeg()], []]}}. The {{int}} parameter picks up the field's type 
annotation, but the {{String}} parameter does not.

Expected: {{[[], []]}}. Tuple constructor parameters do not copy the property's 
annotations, so primitive and reference properties should behave the same way.

h3. Cause

{{ClassNode.getPlainNodeReference()}} returns the node itself for primitive 
types instead of a new reference. {{RecordTypeASTTransformation}} (record 
component type) and {{TupleConstructorASTTransformation.createParam}} 
(constructor parameter type) both call it. So for a primitive property, the 
component, field, accessor return type and constructor parameter all share one 
{{ClassNode}}. {{ExtendedVerifier}} then adds the {{TYPE_USE}} annotations to 
that shared node once for each place it appears.

h3. Fix

Use {{getPlainNodeReference(false)}} at both call sites so primitive types also 
get their own node, as {{ExtendedVerifier.visitConstructor}} already does 
(GROOVY-10937).

Reproduced on 5.1.2 and on master.

> TYPE_USE annotations on primitive record components and tuple-constructor 
> parameters are duplicated or misapplied
> -----------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12413
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12413
>             Project: Groovy
>          Issue Type: Bug
>            Reporter: Paul King
>            Priority: Major
>             Fix For: 5.1.2, 6.0.0-RC-2
>
>
> When a record component or a {{@TupleConstructor}} property has a primitive 
> type annotated with a {{TYPE_USE}} annotation, the compiler writes the type 
> annotation to the wrong places or writes it several times.
> h3. Records
> {code:groovy}
> import java.lang.annotation.*
> @Retention(RetentionPolicy.RUNTIME)
> @Target(ElementType.TYPE_USE)
> @interface NonNeg {}
> record Point(@NonNeg int x, @NonNeg String s) {}
> def rc = Point.recordComponents[0]
> println rc.annotatedType.annotations
> {code}
> Actual:
> {noformat}
> java.lang.annotation.AnnotationFormatError: Duplicate annotation for class: 
> interface NonNeg: @NonNeg()
> {noformat}
> The {{int}} component's type annotation is written three times each to the 
> Record attribute, the backing field, the accessor return type and the 
> canonical constructor parameter. Any reflective read of it fails with the 
> error above, including:
> * {{RecordComponent.getAnnotatedType()}}
> * {{Field.getAnnotatedType()}}
> * {{Method.getAnnotatedReturnType()}}
> * {{Constructor.getAnnotatedParameterTypes()}}
> For the constructor this fails for every parameter, including the {{String}} 
> one. Reference-type components ({{@NonNeg String s}}) are otherwise written 
> correctly.
> Expected: a single {{@NonNeg}} on each of the four places, as javac produces 
> for the equivalent Java record.
> h3. @TupleConstructor
> {code:groovy}
> @groovy.transform.TupleConstructor
> class C { @NonNeg int a; @NonNeg String s }
> println C.getDeclaredConstructor(int, 
> String).annotatedParameterTypes*.annotations
> {code}
> Actual: {{[[@NonNeg()], []]}}. The {{int}} parameter picks up the field's 
> type annotation, but the {{String}} parameter does not.
> Expected: {{[[], []]}}. Tuple constructor parameters do not copy the 
> property's annotations, so primitive and reference properties should behave 
> the same way.
> h3. Cause
> {{ClassNode.getPlainNodeReference()}} returns the node itself for primitive 
> types instead of a new reference. {{RecordTypeASTTransformation}} (record 
> component type) and {{TupleConstructorASTTransformation.createParam}} 
> (constructor parameter type) both call it. So for a primitive property, the 
> component, field, accessor return type and constructor parameter all share 
> one {{ClassNode}}. {{ExtendedVerifier}} then adds the {{TYPE_USE}} 
> annotations to that shared node once for each place it appears.
> h3. Fix
> Use {{getPlainNodeReference(false)}} at both call sites so primitive types 
> also get their own node, as {{ExtendedVerifier.visitConstructor}} already 
> does (GROOVY-10937).
> Reproduced on 5.1.2 and on master.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to