Skip to content
Permalink
Browse files
GROOVY-10553: prevent duplicate type annotations
  • Loading branch information
eric-milles committed Mar 28, 2022
1 parent fd1b632 commit b8bfccaf6a13edc6094e3e91d29d54eebebf2b03
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 21 deletions.
@@ -213,9 +213,11 @@ private void extractTypeUseAnnotations(List<AnnotationNode> mixed, ClassNode tar
}
}
if (!typeUseAnnos.isEmpty()) {
targetType.addTypeAnnotations(typeUseAnnos);
targetType.setAnnotated(true);
for (AnnotationNode anno : typeUseAnnos) {
if (!targetType.getTypeAnnotations().contains(anno)) {
targetType.addTypeAnnotation(anno);
targetType.setAnnotated(true);
}
if (keepTarget != null && !anno.isTargetAllowed(keepTarget)) {
mixed.remove(anno);
}
@@ -56,7 +56,6 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

@@ -225,7 +224,7 @@ private ClassNode createHelperClass(final ClassNode cNode) {

// add methods
List<MethodNode> methods = new ArrayList<>(cNode.getMethods());
List<MethodNode> nonPublicAPIMethods = new LinkedList<>();
List<MethodNode> nonPublicAPIMethods = new ArrayList<>();
List<Statement> staticInitStatements = null;
for (final MethodNode methodNode : methods) {
boolean declared = methodNode.getDeclaringClass() == cNode;
@@ -362,16 +361,17 @@ private void registerASTTransformations(final ClassNode helper) {
}

/**
* Copies annotation from the trait to the helper, excluding the trait annotation itself.
* Copies annotations from the trait to the helper, excluding non-applicable
* items such as {@code @Trait} and {@code @Sealed}.
*
* @param cNode the trait class node
* @param helper the helper class node
*/
private static void copyClassAnnotations(final ClassNode cNode, final ClassNode helper) {
List<AnnotationNode> annotations = cNode.getAnnotations();
for (AnnotationNode annotation : annotations) {
if (!annotation.getClassNode().equals(Traits.TRAIT_CLASSNODE)
&& !annotation.getClassNode().equals(SEALED_TYPE)) {
for (AnnotationNode annotation : cNode.getAnnotations()) {
ClassNode annotationType = annotation.getClassNode();
if (!annotationType.equals(Traits.TRAIT_CLASSNODE)
&& !annotationType.equals(SEALED_TYPE)) {
helper.addAnnotation(annotation);
}
}
@@ -541,11 +541,12 @@ private void processField(final FieldNode field, final MethodNode initializer, f
fieldHelper,
null
);
dummyField.setSynthetic(true);
// copy annotations from field to dummy field
List<AnnotationNode> copied = new LinkedList<>();
List<AnnotationNode> notCopied = new LinkedList<>();
GeneralUtils.copyAnnotatedNodeAnnotations(field, copied, notCopied);
dummyField.addAnnotations(copied);
List<AnnotationNode> copy = new ArrayList<>();
List<AnnotationNode> skip = new ArrayList<>();
GeneralUtils.copyAnnotatedNodeAnnotations(field, copy, skip);
dummyField.addAnnotations(copy);
fieldHelper.addField(dummyField);

// retain legacy field (will be given lower precedence than above)
@@ -559,11 +560,8 @@ private void processField(final FieldNode field, final MethodNode initializer, f
fieldHelper,
null
);
// copy annotations from field to legacy dummy field
copied = new LinkedList<>();
notCopied = new LinkedList<>();
GeneralUtils.copyAnnotatedNodeAnnotations(field, copied, notCopied);
dummyField.addAnnotations(copied);
dummyField.setSynthetic(true);
dummyField.addAnnotations(copy);
fieldHelper.addField(dummyField);
}

@@ -1888,15 +1888,48 @@ final class TraitASTTransformationTest {
trait Foo {
@Deprecated void foo() { 'ok' }
}
@ASTTest(phase=CANONICALIZATION,value={
assert node.getDeclaredMethod('foo').annotations.any { it.classNode.nameWithoutPackage == 'Deprecated'}
@ASTTest(phase=CANONICALIZATION, value={
assert node.getDeclaredMethod('foo').annotations.any {
it.classNode.nameWithoutPackage == 'Deprecated'
}
})
class Bar implements Foo {}
class Bar implements Foo {
}
def b = new Bar()
b.foo()
'''
}

@Test // GROOVY-10553
void testAnnotationShouldBeCarriedOver2() {
assertScript '''
import groovy.transform.*
import java.lang.annotation.*
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.FIELD,ElementType.TYPE_USE])
@interface Foo {
}
trait Bar {
@Foo String string
}
class Baz implements Bar {
}
@ASTTest(phase=CLASS_GENERATION, value={
def type = node.rightExpression.type
assert type.name == 'Baz'
def field = type.getField('Bar__string')
assert field.type.typeAnnotations.size() == 1
field = type.interfaces[1].getField('$ins$1Bar__string')
assert field.type.typeAnnotations.size() == 1 // no duplicate
})
def baz = new Baz(string:'foobar')
'''
}

@Test
void testShouldCompileTraitMethodStatically() {
def err = shouldFail '''

0 comments on commit b8bfcca

Please sign in to comment.