[
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)