Skip to content

Commit

Permalink
improve support for record types
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Nov 9, 2021
1 parent 2a92d49 commit e1e9ab5
Show file tree
Hide file tree
Showing 12 changed files with 915 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ArrayExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.CastExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
Expand Down Expand Up @@ -2523,13 +2524,24 @@ public static boolean missesGenericsTypes(ClassNode cn) {
* @return the result of the expression
*/
public static Object evaluateExpression(Expression expr, CompilerConfiguration config) {
// GRECLIPSE add
Expression ce = expr instanceof CastExpression ? ((CastExpression) expr).getExpression() : expr;
if (ce instanceof ConstantExpression) {
if (expr.getType().equals(ce.getType()))
return ((ConstantExpression) ce).getValue();
} else if (ce instanceof ListExpression) {
if (expr.getType().isArray() && expr.getType().getComponentType().equals(STRING_TYPE))
return ((ListExpression) ce).getExpressions().stream().map(e -> evaluateExpression(e, config)).toArray(String[]::new);
}
// GRECLIPSE end
String className = "Expression$" + UUID.randomUUID().toString().replace('-', '$');
ClassNode node = new ClassNode(className, Opcodes.ACC_PUBLIC, OBJECT_TYPE);
ReturnStatement code = new ReturnStatement(expr);
addGeneratedMethod(node, "eval", Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, code);
CompilerConfiguration copyConf = new CompilerConfiguration(config);
// GRECLIPSE add
copyConf.setPreviewFeatures(false);
copyConf.setTargetBytecode(CompilerConfiguration.DEFAULT.getTargetBytecode());
// GRECLIPSE end
CompilationUnit cu = new CompilationUnit(copyConf);
// GRECLIPSE add
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,9 @@ private Expression annotationValueToExpression (Object value) {
if (value instanceof Class)
return new ClassExpression(ClassHelper.makeWithoutCaching((Class)value));

if (value instanceof Enum)
return new PropertyExpression(new ClassExpression(ClassHelper.makeWithoutCaching(value.getClass())), value.toString());

if (value.getClass().isArray()) {
ListExpression elementExprs = new ListExpression();
int len = Array.getLength(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ArrayExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.CastExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
Expand Down Expand Up @@ -2358,13 +2359,26 @@ public static boolean missesGenericsTypes(ClassNode cn) {
* @return the result of the expression
*/
public static Object evaluateExpression(final Expression expr, final CompilerConfiguration config) {
// GRECLIPSE add
Expression ce = expr instanceof CastExpression ? ((CastExpression) expr).getExpression() : expr;
if (ce instanceof ConstantExpression) {
if (expr.getType().equals(ce.getType()))
return ((ConstantExpression) ce).getValue();
} else if (ce instanceof ListExpression) {
if (expr.getType().isArray() && expr.getType().getComponentType().equals(STRING_TYPE))
return ((ListExpression) ce).getExpressions().stream().map(e -> evaluateExpression(e, config)).toArray(String[]::new);
}
// GRECLIPSE end
String className = "Expression$" + UUID.randomUUID().toString().replace('-', '$');
ClassNode node = new ClassNode(className, Opcodes.ACC_PUBLIC, OBJECT_TYPE);
ReturnStatement code = new ReturnStatement(expr);
addGeneratedMethod(node, "eval", Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, code);
CompilerConfiguration copyConf = new CompilerConfiguration(config);
// disable preview features so class can be inspected by this JVM
copyConf.setPreviewFeatures(false);
// GRECLIPSE add
copyConf.setTargetBytecode(CompilerConfiguration.DEFAULT.getTargetBytecode());
// GRECLIPSE end
CompilationUnit cu = new CompilationUnit(copyConf);
// GRECLIPSE add
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,9 @@ private Expression annotationValueToExpression (Object value) {
if (value instanceof Class)
return new ClassExpression(ClassHelper.makeWithoutCaching((Class<?>)value));

if (value instanceof Enum)
return new PropertyExpression(new ClassExpression(ClassHelper.makeWithoutCaching(value.getClass())), value.toString());

if (value.getClass().isArray()) {
ListExpression elementExprs = new ListExpression();
int len = Array.getLength(value);
Expand Down
1 change: 1 addition & 0 deletions base/org.codehaus.groovy40/.checkstyle
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<file-match-pattern match-pattern="groovy/transform/trait/TraitComposer.java" include-pattern="false" />
<file-match-pattern match-pattern="groovy/transform/trait/TraitReceiverTransformer.java" include-pattern="false" />
<file-match-pattern match-pattern="groovy/transform/trait/Traits.java" include-pattern="false" />
<file-match-pattern match-pattern="groovy/vmplugin/v8/Java8.java" include-pattern="false" />
</fileset>
<filter name="DerivedFiles" enabled="true" />
</fileset-config>
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@

import groovy.lang.Tuple2;
import groovy.lang.Tuple3;
import groovy.transform.CompileStatic;
import groovy.transform.NonSealed;
import groovy.transform.Sealed;
import groovy.transform.Trait;
import groovy.transform.TupleConstructor;
import groovy.transform.*;
import groovyjarjarantlr4.v4.runtime.ANTLRErrorListener;
import groovyjarjarantlr4.v4.runtime.CharStream;
import groovyjarjarantlr4.v4.runtime.CharStreams;
Expand Down Expand Up @@ -161,7 +157,6 @@
import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid;
import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.cloneParams;
import static org.codehaus.groovy.ast.tools.GeneralUtils.closureX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.listX;
Expand Down Expand Up @@ -1591,7 +1586,7 @@ public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) {
boolean isInterfaceWithDefaultMethods = (isInterface && this.containsDefaultMethods(ctx));

if (isSealed) {
AnnotationNode sealedAnnotationNode = new AnnotationNode(ClassHelper.makeCached(Sealed.class));
AnnotationNode sealedAnnotationNode = makeAnnotationNode(Sealed.class);
if (asBoolean(ctx.ps)) {
ListExpression permittedSubclassesListExpression =
listX(Arrays.stream(this.visitTypeList(ctx.ps))
Expand All @@ -1601,17 +1596,13 @@ public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) {
}
classNode.addAnnotation(sealedAnnotationNode);
} else if (isNonSealed) {
classNode.addAnnotation(new AnnotationNode(ClassHelper.makeCached(NonSealed.class)));
classNode.addAnnotation(makeAnnotationNode(NonSealed.class));
}
if (isInterfaceWithDefaultMethods || asBoolean(ctx.TRAIT())) {
/* GRECLIPSE edit
classNode.addAnnotation(new AnnotationNode(ClassHelper.makeCached(Trait.class)));
*/
classNode.addAnnotation(makeAnnotationNode(Trait.class));
// GRECLIPSE end
}
if (isRecord) {
classNode.addAnnotation(makeAnnotationNode(groovy.transform.RecordType.class));
classNode.addAnnotation(makeAnnotationNode(RecordType.class));
}
classNode.addAnnotations(modifierManager.getAnnotations());

Expand Down Expand Up @@ -1646,7 +1637,7 @@ public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) {
classNode.setInterfaces(this.visitTypeList(ctx.is));
this.initUsingGenerics(classNode);
if (isRecord) {
transformRecordHeaderToProperties(ctx, classNode);
this.transformRecordHeaderToProperties(ctx, classNode);
}

} else if (isAnnotation) {
Expand Down Expand Up @@ -2002,55 +1993,47 @@ private void validateParametersOfMethodDeclaration(final Parameter[] parameters,
}

@Override
public MethodNode visitCompactConstructorDeclaration(CompactConstructorDeclarationContext ctx) {
public MethodNode visitCompactConstructorDeclaration(final CompactConstructorDeclarationContext ctx) {
ClassNode classNode = ctx.getNodeMetaData(CLASS_DECLARATION_CLASS_NODE);
Objects.requireNonNull(classNode, "classNode should not be null");

if (classNode.getAnnotations().stream().noneMatch(a -> "groovy.transform.RecordType".equals(a.getClassNode().getName()))) {
createParsingFailedException("Only `record` can have compact constructor", ctx);
if (classNode.getAnnotations().stream().noneMatch(a -> a.getClassNode().getName().equals(RecordType.class.getName()))) {
createParsingFailedException("Only record can have compact constructor", ctx);
}

ModifierManager modifierManager = new ModifierManager(this, this.visitModifiers(ctx.modifiers()));

if (modifierManager.containsAny(VAR)) {
if (new ModifierManager(this, this.visitModifiers(ctx.modifiers())).containsAny(VAR)) {
throw createParsingFailedException("var cannot be used for compact constructor declaration", ctx);
}

String methodName = this.visitMethodName(ctx.methodName());
String className = classNode.getNodeMetaData(CLASS_NAME);
if (!methodName.equals(className)) {
createParsingFailedException("Compact constructor should have the same name with record: " + className, ctx.methodName());
createParsingFailedException("Compact constructor should have the same name as record: " + className, ctx.methodName());
}

Parameter[] header = classNode.getNodeMetaData(RECORD_HEADER);
Objects.requireNonNull(header, "record header should be set");
classNode.removeNodeMetaData(RECORD_HEADER); // GRECLIPSE edit
Objects.requireNonNull(header, "record header should not be null");

final Parameter[] parameters = cloneParams(header);
Statement code = this.visitMethodBody(ctx.methodBody());
code.visit(new CodeVisitorSupport() {
@Override
public void visitPropertyExpression(PropertyExpression expression) {
final String propertyName = expression.getPropertyAsString();
if (THIS_STR.equals(expression.getObjectExpression().getText()) && Arrays.stream(parameters).anyMatch(p -> p.getName().equals(propertyName))) {
createParsingFailedException("Cannot assign a value to final variable `" + propertyName + "`", expression.getProperty());
public void visitPropertyExpression(final PropertyExpression expression) {
String propertyName = expression.getPropertyAsString();
if (THIS_STR.equals(expression.getObjectExpression().getText()) && Arrays.stream(header).anyMatch(p -> p.getName().equals(propertyName))) {
createParsingFailedException("Cannot assign a value to final variable '" + propertyName + "'", expression.getProperty());
}
super.visitPropertyExpression(expression);
}
});

attachTupleConstructorAnnotationToRecord(classNode, parameters, code);
return null;
}

private void attachTupleConstructorAnnotationToRecord(ClassNode classNode, Parameter[] parameters, Statement block) {
ClassNode tupleConstructorType = ClassHelper.makeCached(TupleConstructor.class);
List<AnnotationNode> annos = classNode.getAnnotations(tupleConstructorType);
AnnotationNode tupleConstructor = annos.isEmpty() ? new AnnotationNode(tupleConstructorType) : annos.get(0);
tupleConstructor.setMember("pre", closureX(block));
List<AnnotationNode> annos = classNode.getAnnotations(ClassHelper.make(TupleConstructor.class));
AnnotationNode tupleConstructor = annos.isEmpty() ? makeAnnotationNode(TupleConstructor.class) : annos.get(0);
tupleConstructor.setMember("pre", closureX(code));
if (annos.isEmpty()) {
classNode.addAnnotation(tupleConstructor);
}

return null;
}

@Override
Expand Down Expand Up @@ -2520,7 +2503,7 @@ private void declareField(final VariableDeclarationContext ctx, final ModifierMa
classNode.getFields().remove(propertyNode.getField());
fieldNode = new FieldNode(fieldName, modifiers, variableType, classNode.redirect(), propertyNode.hasInitialExpression() ? propertyNode.getInitialExpression() : initialValue);
propertyNode.setField(fieldNode);
propertyNode.addAnnotation(new AnnotationNode(ClassHelper.make(CompileStatic.class)));
propertyNode.addAnnotation(makeAnnotationNode(CompileStatic.class));
classNode.addField(fieldNode);
// expand properties early so AST transforms will be handled correctly
PropertyExpander expander = new PropertyExpander(classNode);
Expand Down Expand Up @@ -5417,7 +5400,7 @@ private void addErrorListeners() {
//--------------------------------------------------------------------------

// GRECLIPSE edit
private /*static*/ class DeclarationListStatement extends Statement {
private class DeclarationListStatement extends Statement {
private final List<ExpressionStatement> declarationStatements;

public DeclarationListStatement(DeclarationExpression... declarations) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ArrayExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.CastExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
Expand Down Expand Up @@ -2153,13 +2155,26 @@ public static boolean missesGenericsTypes(ClassNode cn) {
* @return the result of the expression
*/
public static Object evaluateExpression(final Expression expr, final CompilerConfiguration config) {
// GRECLIPSE add
Expression ce = expr instanceof CastExpression ? ((CastExpression) expr).getExpression() : expr;
if (ce instanceof ConstantExpression) {
if (expr.getType().equals(ce.getType()))
return ((ConstantExpression) ce).getValue();
} else if (ce instanceof ListExpression) {
if (expr.getType().isArray() && expr.getType().getComponentType().equals(STRING_TYPE))
return ((ListExpression) ce).getExpressions().stream().map(e -> evaluateExpression(e, config)).toArray(String[]::new);
}
// GRECLIPSE end
String className = "Expression$" + UUID.randomUUID().toString().replace('-', '$');
ClassNode node = new ClassNode(className, Opcodes.ACC_PUBLIC, OBJECT_TYPE);
ReturnStatement code = new ReturnStatement(expr);
addGeneratedMethod(node, "eval", Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, code);
CompilerConfiguration copyConf = new CompilerConfiguration(config);
// disable preview features so class can be inspected by this JVM
copyConf.setPreviewFeatures(false);
// GRECLIPSE add
copyConf.setTargetBytecode(CompilerConfiguration.DEFAULT.getTargetBytecode());
// GRECLIPSE end
CompilationUnit cu = new CompilationUnit(copyConf);
try {
cu.addClassNode(node);
Expand Down
Loading

0 comments on commit e1e9ab5

Please sign in to comment.