vahidreza vahidreza - 4 months ago 18
Groovy Question

GroovyAST add generic field at compile time

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.

Answer

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