5

I want to add a generic field to some classes at compile time. To this objective, I was implement my own AST annotation and transformation classes by following the official documentation and annotate desired classes with AST annotation.

But I am getting this error at compile time:

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: /home/.../groovy/Sample.groovy: -1: A transform used a generics containing ClassNode java.util.HashSet for the field x directly. You are not supposed to do this. Please create a new ClassNode referring to the old ClassNode and use the new ClassNode instead of the old one. Otherwise the compiler will create wrong descriptors and a potential NullPointerException in TypeResolver in the OpenJDK. If this is not your own doing, please report this bug to the writer of the transform. @ line -1, column -1.

Have I made a mistake?

Sample codes

For example, suppose I want to add a HashSet<Long> field, named x, to every class annotated by MyAST annotation.

My AST annotation class:

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@GroovyASTTransformationClass(classes = [MyASTTransformation.class])
public @interface MyAST {
}

My AST transformation class:

@CompileStatic
@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
public class MyASTTransformation implements ASTTransformation {

@Override
public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
     ClassNode clazz = (ClassNode) nodes[1];
     ClassNode longHashSetClass = new ClassNode(HashSet.class);
     longHashSetClass.setGenericsTypes([new GenericsType(new ClassNode(Long.class))] as GenericsType[]);
     FieldNode field = new FieldNode("x", FieldNode.ACC_PRIVATE, longHashSetClass, clazz, new ConstantExpression(null));
     clazz.addField(field);
 }
}

Sample annotated class:

@MyAST
public class Sample {
}

Note

When I eliminate the line longHashSetClass.setGenericsTypes([new GenericsType(new ClassNode(Long.class))] as GenericsType[]);, everything is OK but type of x is HashSet instead of HashSet<Long> at runtime.

vahidreza
  • 843
  • 1
  • 8
  • 19

1 Answers1

3

You should use ClassHelper or GenericUtils in order to create a ClassNode :

import static org.codehaus.groovy.ast.ClassHelper.make
import static org.codehaus.groovy.ast.tools.GenericsUtils.makeClassSafeWithGenerics

...

ClassNode hashSet = makeClassSafeWithGenerics(HashSet, make(Long))
Jérémie B
  • 10,611
  • 1
  • 26
  • 43
  • Thank you. It works but if I change `make(HashSet)` to `new ClassNode(HashSet)` I'm getting an error. What's different between them? – vahidreza Jul 31 '16 at 16:54
  • Honestly, I don't know, but ClassNode shouldn't be instanciated directly. these methods handle caching, aliasing and others things necessary when generating bytecodes. – Jérémie B Jul 31 '16 at 17:47