You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/11/21 20:28:31 UTC
[groovy] branch GROOVY_4_0_X updated: GROOVY-10790: `@MapConstructor(noArg=true)` plus `@TupleConstructor`
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push:
new 95c7e6ba02 GROOVY-10790: `@MapConstructor(noArg=true)` plus `@TupleConstructor`
95c7e6ba02 is described below
commit 95c7e6ba025a1846c7638dd4f25c5c141206fccb
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Nov 21 11:04:21 2022 -0600
GROOVY-10790: `@MapConstructor(noArg=true)` plus `@TupleConstructor`
---
.../java/org/codehaus/groovy/ast/Parameter.java | 4 +-
.../org/codehaus/groovy/classgen/Verifier.java | 30 +-
.../org/codehaus/groovy/control/SourceUnit.java | 26 +-
.../codehaus/groovy/control/messages/Message.java | 41 +-
.../groovy/control/messages/SimpleMessage.java | 30 +-
.../groovy/control/messages/WarningMessage.java | 38 +-
.../groovy/tools/javac/JavaStubGenerator.java | 4 +-
.../groovy/transform/ASTTransformationVisitor.java | 7 +-
.../transform/MapConstructorASTTransformation.java | 23 +-
.../TupleConstructorASTTransformation.java | 146 +++----
.../groovy/transform/ImmutableTransformTest.groovy | 467 +++++++++------------
.../transform/TupleConstructorTransformTest.groovy | 308 ++++++++------
12 files changed, 555 insertions(+), 569 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/ast/Parameter.java b/src/main/java/org/codehaus/groovy/ast/Parameter.java
index 5c25900f2a..93f0fde79f 100644
--- a/src/main/java/org/codehaus/groovy/ast/Parameter.java
+++ b/src/main/java/org/codehaus/groovy/ast/Parameter.java
@@ -85,9 +85,9 @@ public class Parameter extends AnnotatedNode implements Variable {
return defaultValue;
}
- public void setInitialExpression(Expression init) {
+ public void setInitialExpression(final Expression init) {
defaultValue = init;
- hasDefaultValue = defaultValue != null;
+ hasDefaultValue = (init != null);
}
@Override
diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
index f431ea8403..ecee1e4a6d 100644
--- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
@@ -93,6 +93,7 @@ import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
import static java.util.stream.Collectors.joining;
import static org.apache.groovy.ast.tools.AnnotatedNodeUtils.hasAnnotation;
+import static org.apache.groovy.ast.tools.AnnotatedNodeUtils.isGenerated;
import static org.apache.groovy.ast.tools.AnnotatedNodeUtils.markAsGenerated;
import static org.apache.groovy.ast.tools.ConstructorNodeUtils.getFirstIfSpecialConstructorCall;
import static org.apache.groovy.ast.tools.ExpressionUtils.transformInlineConstants;
@@ -109,6 +110,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.bytecodeX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.castX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorThisX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX;
@@ -987,7 +989,6 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
List<ConstructorNode> constructors = new ArrayList<>(type.getDeclaredConstructors());
addDefaultParameters(constructors, (arguments, params, method) -> {
// GROOVY-9151: check for references to parameters that have been removed
- List<Parameter> paramList = Arrays.asList(params);
for (ListIterator<Expression> it = arguments.getExpressions().listIterator(); it.hasNext(); ) {
Expression argument = it.next();
if (argument instanceof CastExpression) {
@@ -997,9 +998,8 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
VariableExpression v = (VariableExpression) argument;
if (v.getAccessedVariable() instanceof Parameter) {
Parameter p = (Parameter) v.getAccessedVariable();
- if (p.hasInitialExpression() && !paramList.contains(p)
- && p.getInitialExpression() instanceof ConstantExpression) {
- // replace argument "(Type) param" with "(Type) <param's default>" for simple default value
+ if (p.getInitialExpression() instanceof ConstantExpression && !Arrays.asList(params).contains(p)){
+ // for simple default value, replace argument "(Type) param" with "(Type) <<param's default>>"
it.set(castX(method.getParameters()[it.nextIndex() - 1].getType(), p.getInitialExpression()));
}
}
@@ -1010,7 +1010,7 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
public void visitVariableExpression(final VariableExpression e) {
if (e.getAccessedVariable() instanceof Parameter) {
Parameter p = (Parameter) e.getAccessedVariable();
- if (p.hasInitialExpression() && !paramList.contains(p)) {
+ if (p.hasInitialExpression() && !Arrays.asList(params).contains(p)) {
String error = String.format(
"The generated constructor \"%s(%s)\" references parameter '%s' which has been replaced by a default value expression.",
type.getNameWithoutPackage(),
@@ -1023,9 +1023,15 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
};
visitor.visitArgumentlistExpression(arguments);
- // delegate to original constructor using arguments derived from defaults
- Statement code = new ExpressionStatement(new ConstructorCallExpression(ClassNode.THIS, arguments));
- addConstructor(params, (ConstructorNode) method, code, type);
+ ConstructorNode old = type.getDeclaredConstructor(params);
+ if (old == null || isGenerated(old)) { type.removeConstructor(old);
+ // delegate to original constructor using arguments derived from defaults
+ addConstructor(params, (ConstructorNode) method, stmt(ctorThisX(arguments)), type);
+ } else {
+ String warning = "Default argument(s) specify duplicate constructor: " +
+ old.getTypeDescriptor().replace("void <init>", type.getNameWithoutPackage());
+ type.getModule().getContext().addWarning(warning, method.getLineNumber() > 0 ? method : type);
+ }
});
}
@@ -1088,10 +1094,10 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
}
for (Parameter parameter : parameters) {
- if (parameter.hasInitialExpression()) {
- // remove default expression and store it as node metadata
- parameter.putNodeMetaData(Verifier.INITIAL_EXPRESSION,
- parameter.getInitialExpression());
+ Expression value = parameter.getInitialExpression();
+ if (value != null) {
+ // move the default expression from parameter to node metadata
+ parameter.putNodeMetaData(Verifier.INITIAL_EXPRESSION, value);
parameter.setInitialExpression(null);
}
}
diff --git a/src/main/java/org/codehaus/groovy/control/SourceUnit.java b/src/main/java/org/codehaus/groovy/control/SourceUnit.java
index b5ac3d492b..fb6c35e167 100644
--- a/src/main/java/org/codehaus/groovy/control/SourceUnit.java
+++ b/src/main/java/org/codehaus/groovy/control/SourceUnit.java
@@ -29,8 +29,10 @@ import org.codehaus.groovy.control.io.URLReaderSource;
import org.codehaus.groovy.control.messages.Message;
import org.codehaus.groovy.control.messages.SimpleMessage;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.control.messages.WarningMessage;
import org.codehaus.groovy.syntax.Reduction;
import org.codehaus.groovy.syntax.SyntaxException;
+import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.tools.Utilities;
import java.io.File;
@@ -265,7 +267,7 @@ public class SourceUnit extends ProcessingUnit {
return this.ast;
}
- //---------------------------------------------------------------------------
+ //--------------------------------------------------------------------------
// SOURCE SAMPLING
/**
@@ -306,7 +308,7 @@ public class SourceUnit extends ProcessingUnit {
* @param e the exception that occurred
* @throws CompilationFailedException on error
*/
- public void addException(Exception e) throws CompilationFailedException {
+ public void addException(final Exception e) throws CompilationFailedException {
getErrorCollector().addException(e, this);
}
@@ -319,24 +321,32 @@ public class SourceUnit extends ProcessingUnit {
* @param se the exception, which should have line and column information
* @throws CompilationFailedException on error
*/
- public void addError(SyntaxException se) throws CompilationFailedException {
+ public void addError(final SyntaxException se) throws CompilationFailedException {
getErrorCollector().addError(se, this);
}
/**
* Convenience wrapper for {@link ErrorCollector#addFatalError(org.codehaus.groovy.control.messages.Message)}.
*
- * @param msg the error message
- * @param node the AST node
+ * @param text the error message
+ * @param node for locating the offending code
* @throws CompilationFailedException on error
*
* @since 3.0.0
*/
- public void addFatalError(String msg, ASTNode node) throws CompilationFailedException {
- getErrorCollector().addFatalError(Message.create(new SyntaxException(msg, node), this));
+ public void addFatalError(final String text, final ASTNode node) throws CompilationFailedException {
+ getErrorCollector().addFatalError(Message.create(new SyntaxException(text, node), this));
+ }
+
+ /**
+ * @since 4.0.7
+ */
+ public void addWarning(final String text, final ASTNode node) {
+ Token token = new Token(0, "", node.getLineNumber(), node.getColumnNumber()); // ASTNode to CSTNode
+ getErrorCollector().addWarning(new WarningMessage(WarningMessage.POSSIBLE_ERRORS, text, token, this));
}
- public void addErrorAndContinue(SyntaxException se) {
+ public void addErrorAndContinue(final SyntaxException se) {
getErrorCollector().addErrorAndContinue(se, this);
}
diff --git a/src/main/java/org/codehaus/groovy/control/messages/Message.java b/src/main/java/org/codehaus/groovy/control/messages/Message.java
index 2392607a9e..f72fd2c048 100644
--- a/src/main/java/org/codehaus/groovy/control/messages/Message.java
+++ b/src/main/java/org/codehaus/groovy/control/messages/Message.java
@@ -31,44 +31,37 @@ import java.io.PrintWriter;
public abstract class Message {
/**
- * Writes the message to the specified PrintWriter. The supplied
- * ProcessingUnit is the unit that holds this Message.
+ * Creates a new {@code Message} from the specified text.
*/
- public abstract void write(PrintWriter writer, Janitor janitor);
+ public static Message create(final String text, final ProcessingUnit owner) {
+ return new SimpleMessage(text, owner);
+ }
/**
- * A synonym for write( writer, owner, null ).
+ * Creates a new {@code Message} from the specified text and data.
*/
- public final void write(PrintWriter writer) {
- write(writer, null);
+ public static Message create(final String text, final Object data, final ProcessingUnit owner) {
+ return new SimpleMessage(text, data, owner);
}
- //---------------------------------------------------------------------------
- // FACTORY METHODS
-
/**
- * Creates a new Message from the specified text.
+ * Creates a new {@code Message} from the specified {@code SyntaxException}.
*/
- public static Message create(String text, ProcessingUnit owner) {
- return new SimpleMessage(text, owner);
+ public static Message create(final SyntaxException error, final SourceUnit owner) {
+ return new SyntaxErrorMessage(error, owner);
}
+ //--------------------------------------------------------------------------
+
/**
- * Creates a new Message from the specified text.
+ * Writes this message to the specified {@link PrintWriter}.
*/
- public static Message create(String text, Object data, ProcessingUnit owner) {
- return new SimpleMessage(text, data, owner);
- }
+ public abstract void write(PrintWriter writer, Janitor janitor);
/**
- * Creates a new Message from the specified SyntaxException.
+ * Writes this message to the specified {@link PrintWriter}.
*/
- public static Message create(SyntaxException error, SourceUnit owner) {
- return new SyntaxErrorMessage(error, owner);
+ public final void write(final PrintWriter writer) {
+ write(writer, null);
}
-
}
-
-
-
-
diff --git a/src/main/java/org/codehaus/groovy/control/messages/SimpleMessage.java b/src/main/java/org/codehaus/groovy/control/messages/SimpleMessage.java
index 5afc7687e7..2c537bc5c8 100644
--- a/src/main/java/org/codehaus/groovy/control/messages/SimpleMessage.java
+++ b/src/main/java/org/codehaus/groovy/control/messages/SimpleMessage.java
@@ -28,32 +28,32 @@ import java.io.PrintWriter;
* A base class for compilation messages.
*/
public class SimpleMessage extends Message {
- protected String message; // Message text
- protected Object data; // Data, when the message text is an I18N identifier
+
+ /** used when {@link message} is an I18N identifier */
+ protected Object data;
+ protected String message;
protected ProcessingUnit owner;
- public SimpleMessage(String message, ProcessingUnit source) {
- this(message, null, source);
+ public SimpleMessage(final String message, final ProcessingUnit owner) {
+ this(message, null, owner);
}
- public SimpleMessage(String message, Object data, ProcessingUnit source) {
+ public SimpleMessage(final String message, final Object data, final ProcessingUnit owner) {
this.message = message;
- this.data = null;
- this.owner = source;
+ this.owner = owner;
+ this.data = data;
+ }
+
+ public String getMessage() {
+ return message;
}
@Override
- public void write(PrintWriter writer, Janitor janitor) {
+ public void write(final PrintWriter writer, final Janitor janitor) {
if (owner instanceof SourceUnit) {
- String name = ((SourceUnit) owner).getName();
- writer.println("" + name + ": " + message);
+ writer.println(((SourceUnit) owner).getName() + ": " + message);
} else {
writer.println(message);
}
}
-
- public String getMessage() {
- return message;
- }
-
}
diff --git a/src/main/java/org/codehaus/groovy/control/messages/WarningMessage.java b/src/main/java/org/codehaus/groovy/control/messages/WarningMessage.java
index d28d800488..d17c2aeaec 100644
--- a/src/main/java/org/codehaus/groovy/control/messages/WarningMessage.java
+++ b/src/main/java/org/codehaus/groovy/control/messages/WarningMessage.java
@@ -28,18 +28,23 @@ import java.io.PrintWriter;
* A class for warning messages.
*/
public class WarningMessage extends LocatedMessage {
- //---------------------------------------------------------------------------
+
+ //--------------------------------------------------------------------------
// WARNING LEVELS
- public static final int NONE = 0; // For querying, ignore all errors
- public static final int LIKELY_ERRORS = 1; // Warning indicates likely error
- public static final int POSSIBLE_ERRORS = 2; // Warning indicates possible error
- public static final int PARANOIA = 3; // Warning indicates paranoia on the part of the compiler
+ /** Ignore all (for querying) */
+ public static final int NONE = 0;
+ /** Warning indicates likely error */
+ public static final int LIKELY_ERRORS = 1;
+ /** Warning indicates possible error */
+ public static final int POSSIBLE_ERRORS = 2;
+ /** Warning indicates paranoia on the part of the compiler */
+ public static final int PARANOIA = 3;
/**
* Returns true if a warning would be relevant to the specified level.
*/
- public static boolean isRelevant(int actual, int limit) {
+ public static boolean isRelevant(final int actual, final int limit) {
return actual <= limit;
}
@@ -47,23 +52,23 @@ public class WarningMessage extends LocatedMessage {
* Returns true if this message is as or more important than the
* specified importance level.
*/
- public boolean isRelevant(int importance) {
+ public boolean isRelevant(final int importance) {
return isRelevant(this.importance, importance);
}
- //---------------------------------------------------------------------------
- // CONSTRUCTION AND DATA ACCESS
+ //--------------------------------------------------------------------------
- private final int importance; // The warning level, for filtering
+ /** The warning level (for filtering). */
+ private final int importance;
/**
* Creates a new warning message.
*
* @param importance the warning level
* @param message the message text
- * @param context context information for locating the offending source text
+ * @param context for locating the offending source text
*/
- public WarningMessage(int importance, String message, CSTNode context, SourceUnit owner) {
+ public WarningMessage(final int importance, final String message, final CSTNode context, final SourceUnit owner) {
super(message, context, owner);
this.importance = importance;
}
@@ -73,18 +78,17 @@ public class WarningMessage extends LocatedMessage {
*
* @param importance the warning level
* @param message the message text
- * @param data additional data needed when generating the message
- * @param context context information for locating the offending source text
+ * @param data data needed for generating the message
+ * @param context for locating the offending source text
*/
- public WarningMessage(int importance, String message, Object data, CSTNode context, SourceUnit owner) {
+ public WarningMessage(final int importance, final String message, final Object data, final CSTNode context, final SourceUnit owner) {
super(message, data, context, owner);
this.importance = importance;
}
@Override
- public void write(PrintWriter writer, Janitor janitor) {
+ public void write(final PrintWriter writer, final Janitor janitor) {
writer.print("warning: ");
super.write(writer, janitor);
}
-
}
diff --git a/src/main/java/org/codehaus/groovy/tools/javac/JavaStubGenerator.java b/src/main/java/org/codehaus/groovy/tools/javac/JavaStubGenerator.java
index b5fa76ae70..83f6f7025f 100644
--- a/src/main/java/org/codehaus/groovy/tools/javac/JavaStubGenerator.java
+++ b/src/main/java/org/codehaus/groovy/tools/javac/JavaStubGenerator.java
@@ -79,6 +79,7 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
+import static org.apache.groovy.ast.tools.AnnotatedNodeUtils.markAsGenerated;
import static org.apache.groovy.ast.tools.ConstructorNodeUtils.getFirstIfSpecialConstructorCall;
import static org.codehaus.groovy.ast.ClassHelper.CLASS_Type;
import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper;
@@ -280,13 +281,14 @@ public class JavaStubGenerator {
@Override
protected void addConstructor(Parameter[] params, ConstructorNode ctor, Statement code, ClassNode node) {
- if (code instanceof ExpressionStatement) { //GROOVY-4508
+ if (!(code instanceof BlockStatement)) { // GROOVY-4508
Statement stmt = code;
code = new BlockStatement();
((BlockStatement) code).addStatement(stmt);
}
ConstructorNode newCtor = new ConstructorNode(ctor.getModifiers(), params, ctor.getExceptions(), code);
newCtor.setDeclaringClass(node);
+ markAsGenerated(node, newCtor);
constructors.add(newCtor);
}
diff --git a/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java b/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java
index 499a7562d1..137ae5f815 100644
--- a/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java
@@ -352,21 +352,20 @@ public final class ASTTransformationVisitor extends ClassCodeVisitorSupport {
}
}
- private static void addPhaseOperationsForGlobalTransforms(CompilationUnit compilationUnit,
- Map<String, URL> transformNames, boolean isFirstScan) {
+ private static void addPhaseOperationsForGlobalTransforms(CompilationUnit compilationUnit, Map<String, URL> transformNames, boolean isFirstScan) {
GroovyClassLoader transformLoader = compilationUnit.getTransformLoader();
for (Map.Entry<String, URL> entry : transformNames.entrySet()) {
try {
Class<?> gTransClass = transformLoader.loadClass(entry.getKey(), false, true, false);
GroovyASTTransformation transformAnnotation = gTransClass.getAnnotation(GroovyASTTransformation.class);
if (transformAnnotation == null) {
- compilationUnit.getErrorCollector().addWarning(new WarningMessage(
+ compilationUnit.getErrorCollector().addWarning(
WarningMessage.POSSIBLE_ERRORS,
"Transform Class " + entry.getKey() + " is specified as a global transform in " + entry.getValue().toExternalForm()
+ " but it is not annotated by " + GroovyASTTransformation.class.getName()
+ " the global transform associated with it may fail and cause the compilation to fail.",
null,
- null));
+ null);
continue;
}
if (ASTTransformation.class.isAssignableFrom(gTransClass)) {
diff --git a/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
index dbc31c467b..40538689bb 100644
--- a/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/MapConstructorASTTransformation.java
@@ -27,6 +27,7 @@ import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.DynamicVariable;
@@ -53,8 +54,6 @@ import java.util.Set;
import static org.apache.groovy.ast.tools.AnnotatedNodeUtils.markAsGenerated;
import static org.apache.groovy.ast.tools.ClassNodeUtils.hasNoArgConstructor;
import static org.apache.groovy.ast.tools.VisibilityUtils.getVisibility;
-import static org.codehaus.groovy.ast.ClassHelper.make;
-import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
import static org.codehaus.groovy.ast.tools.GeneralUtils.copyStatementsWithSuperAdjustment;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
@@ -73,11 +72,11 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation i
private CompilationUnit compilationUnit;
- static final Class MY_CLASS = MapConstructor.class;
- static final ClassNode MY_TYPE = make(MY_CLASS);
+ static final Class<?> MY_CLASS = MapConstructor.class;
+ static final ClassNode MY_TYPE = ClassHelper.make(MY_CLASS);
static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
- private static final ClassNode MAP_TYPE = makeWithoutCaching(Map.class, false);
- private static final ClassNode LHMAP_TYPE = makeWithoutCaching(LinkedHashMap.class, false);
+ private static final ClassNode MAP_TYPE = ClassHelper.makeWithoutCaching(Map.class, false);
+ private static final ClassNode LHMAP_TYPE = ClassHelper.makeWithoutCaching(LinkedHashMap.class, false);
@Override
public String getAnnotationName() {
@@ -85,7 +84,12 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation i
}
@Override
- public void visit(ASTNode[] nodes, SourceUnit source) {
+ public void setCompilationUnit(final CompilationUnit unit) {
+ this.compilationUnit = unit;
+ }
+
+ @Override
+ public void visit(final ASTNode[] nodes, final SourceUnit source) {
init(nodes, source);
AnnotatedNode parent = (AnnotatedNode) nodes[1];
AnnotationNode anno = (AnnotationNode) nodes[0];
@@ -256,9 +260,4 @@ public class MapConstructorASTTransformation extends AbstractASTTransformation i
}
};
}
-
- @Override
- public void setCompilationUnit(CompilationUnit unit) {
- this.compilationUnit = unit;
- }
}
diff --git a/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
index 8762b5d867..87bd6c5c26 100644
--- a/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
@@ -45,7 +45,6 @@ import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.EmptyStatement;
-import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.classgen.VariableScopeVisitor;
import org.codehaus.groovy.control.CompilationUnit;
@@ -61,7 +60,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-import static groovy.transform.DefaultsMode.AUTO;
import static groovy.transform.DefaultsMode.OFF;
import static groovy.transform.DefaultsMode.ON;
import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedConstructor;
@@ -70,7 +68,6 @@ import static org.apache.groovy.ast.tools.ConstructorNodeUtils.checkPropNamesS;
import static org.apache.groovy.ast.tools.VisibilityUtils.getVisibility;
import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.copyStatementsWithSuperAdjustment;
@@ -86,8 +83,10 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+import static org.codehaus.groovy.runtime.DefaultGroovyMethods.plus;
import static org.codehaus.groovy.transform.ImmutableASTTransformation.makeImmutable;
import static org.codehaus.groovy.transform.NamedVariantASTTransformation.processImplicitNamedParam;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
/**
* Handles generation of code for the @TupleConstructor annotation.
@@ -105,6 +104,11 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
private static final ClassNode LHMAP_TYPE = ClassHelper.makeWithoutCaching(LinkedHashMap.class, false);
private static final ClassNode POJO_TYPE = ClassHelper.make(POJO.class);
+ @Override
+ public int priority() {
+ return 5;
+ }
+
@Override
public String getAnnotationName() {
return MY_TYPE_NAME;
@@ -171,18 +175,19 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
final boolean includeProperties, final boolean includeSuperFields, final boolean includeSuperProperties,
final List<String> excludes, final List<String> includes, final boolean allNames, final boolean allProperties,
final SourceUnit sourceUnit, final PropertyHandler handler, final ClosureExpression pre, final ClosureExpression post) {
- boolean callSuper = xform.memberHasValue(anno, "callSuper", true);
- boolean force = xform.memberHasValue(anno, "force", true);
+ boolean namedVariant = xform.memberHasValue(anno, "namedVariant", Boolean.TRUE);
+ boolean callSuper = xform.memberHasValue(anno, "callSuper", Boolean.TRUE);
DefaultsMode defaultsMode = maybeDefaultsMode(anno, "defaultsMode");
if (defaultsMode == null) {
- if (anno.getMember("defaults") == null) {
- defaultsMode = ON;
- } else {
- boolean defaults = !xform.memberHasValue(anno, "defaults", false);
- defaultsMode = defaults ? ON : OFF;
- }
+ boolean defaults = anno.getMember("defaults") == null
+ || !xform.memberHasValue(anno, "defaults", Boolean.FALSE);
+ defaultsMode = defaults ? ON : OFF;
}
- boolean namedVariant = xform.memberHasValue(anno, "namedVariant", true);
+ boolean force = xform.memberHasValue(anno, "force", Boolean.TRUE);
+ boolean makeImmutable = makeImmutable(cNode);
+
+ // no processing if explicit constructor(s) found, unless forced or ImmutableBase is in play
+ if (!force && !makeImmutable && hasExplicitConstructor(null, cNode)) return;
Set<String> names = new HashSet<>();
List<PropertyNode> superList;
@@ -191,16 +196,8 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
} else {
superList = new ArrayList<>();
}
-
List<PropertyNode> list = getAllProperties(names, cNode, includeProperties, includeFields, false, allProperties, false, true);
- boolean makeImmutable = makeImmutable(cNode);
- boolean specialNamedArgCase = (ImmutableASTTransformation.isSpecialNamedArgCase(list, defaultsMode == OFF) && superList.isEmpty()) ||
- (ImmutableASTTransformation.isSpecialNamedArgCase(superList, defaultsMode == OFF) && list.isEmpty());
-
- // no processing if existing constructors found unless forced or ImmutableBase in play
- if (hasExplicitConstructor(null, cNode) && !force && !makeImmutable) return;
-
List<Parameter> params = new ArrayList<>();
List<Expression> superParams = new ArrayList<>();
BlockStatement preBody = new BlockStatement();
@@ -215,23 +212,25 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
BlockStatement body = new BlockStatement();
- List<PropertyNode> tempList = new ArrayList<>(list);
- tempList.addAll(superList);
- if (!handler.validateProperties(xform, body, cNode, tempList)) {
+ if (!handler.validateProperties(xform, body, cNode, plus(list, superList))) {
return;
}
+ boolean specialNamedArgCase = (superList.isEmpty() && ImmutableASTTransformation.isSpecialNamedArgCase(list, defaultsMode == OFF))
+ || (list.isEmpty() && ImmutableASTTransformation.isSpecialNamedArgCase(superList, defaultsMode == OFF));
+
for (PropertyNode pNode : superList) {
String name = pNode.getName();
FieldNode fNode = pNode.getField();
- if (shouldSkipUndefinedAware(name, excludes, includes, allNames)) continue;
- params.add(createParam(fNode, name, defaultsMode, xform, makeImmutable));
- if (callSuper) {
- superParams.add(varX(name));
- } else if (!superInPre && !specialNamedArgCase) {
- Statement propInit = handler.createPropInit(xform, anno, cNode, pNode, null);
- if (propInit != null) {
- body.addStatement(propInit);
+ if (!shouldSkipUndefinedAware(name, excludes, includes, allNames)) {
+ params.add(createParam(fNode, name, defaultsMode, xform, makeImmutable));
+ if (callSuper) {
+ superParams.add(varX(name));
+ } else if (!superInPre && !specialNamedArgCase) {
+ Statement propInit = handler.createPropInit(xform, anno, cNode, pNode, null);
+ if (propInit != null) {
+ body.addStatement(propInit);
+ }
}
}
}
@@ -256,8 +255,7 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
}
if (includes != null) {
- Comparator<Parameter> includeComparator = Comparator.comparingInt(p -> includes.indexOf(p.getName()));
- params.sort(includeComparator);
+ params.sort(Comparator.comparingInt(p -> includes.indexOf(p.getName())));
}
for (PropertyNode pNode : list) {
@@ -273,12 +271,12 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
body.addStatement(post.getCode());
}
- boolean hasMapCons = AnnotatedNodeUtils.hasAnnotation(cNode, MapConstructorASTTransformation.MY_TYPE);
- int modifiers = getVisibility(anno, cNode, ConstructorNode.class, org.objectweb.asm.Opcodes.ACC_PUBLIC);
- ConstructorNode consNode = addGeneratedConstructor(cNode, modifiers, params.toArray(Parameter.EMPTY_ARRAY), ClassNode.EMPTY_ARRAY, body);
+ int modifiers = getVisibility(anno, cNode, ConstructorNode.class, ACC_PUBLIC);
+ // add main tuple constructor; if any parameters have default values then Verifier will generate the other variants
+ ConstructorNode tupleCtor = addGeneratedConstructor(cNode, modifiers, params.toArray(Parameter.EMPTY_ARRAY), ClassNode.EMPTY_ARRAY, body);
if (cNode.getNodeMetaData("_RECORD_HEADER") != null) {
- consNode.addAnnotations(cNode.getAnnotations());
- consNode.putNodeMetaData("_SKIPPABLE_ANNOTATIONS", Boolean.TRUE);
+ tupleCtor.addAnnotations(cNode.getAnnotations());
+ tupleCtor.putNodeMetaData("_SKIPPABLE_ANNOTATIONS", Boolean.TRUE);
}
if (namedVariant) {
BlockStatement inner = new BlockStatement();
@@ -289,26 +287,25 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
List<String> propNames = new ArrayList<>();
Map<Parameter, Expression> seen = new HashMap<>();
for (Parameter p : params) {
- if (!processImplicitNamedParam(xform, consNode, mapParam, inner, args, propNames, p, false, seen)) return;
+ if (!processImplicitNamedParam(xform, tupleCtor, mapParam, inner, args, propNames, p, false, seen)) return;
}
- NamedVariantASTTransformation.createMapVariant(xform, consNode, anno, mapParam, genParams, cNode, inner, args, propNames);
+ NamedVariantASTTransformation.createMapVariant(xform, tupleCtor, anno, mapParam, genParams, cNode, inner, args, propNames);
}
if (sourceUnit != null && !body.isEmpty()) {
- VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(sourceUnit);
- scopeVisitor.visitClass(cNode);
+ new VariableScopeVisitor(sourceUnit).visitClass(cNode);
}
- // GROOVY-8868 don't want an empty body to cause the constructor to be deleted later
- if (body.isEmpty()) {
- body.addStatement(new ExpressionStatement(ConstantExpression.EMPTY_EXPRESSION));
+ if (body.isEmpty()) { // GROOVY-8868: retain empty constructor
+ body.addStatement(stmt(ConstantExpression.EMPTY_EXPRESSION));
}
// If the first param is def or a Map, named args might not work as expected so we add a hard-coded map constructor in this case
// we don't do it for LinkedHashMap for now (would lead to duplicate signature)
// or if there is only one Map property (for backwards compatibility)
// or if there is already a @MapConstructor annotation
- if (!params.isEmpty() && defaultsMode != OFF && !hasMapCons && specialNamedArgCase) {
+ if (!params.isEmpty() && defaultsMode != OFF && specialNamedArgCase
+ && !AnnotatedNodeUtils.hasAnnotation(cNode, MapConstructorASTTransformation.MY_TYPE)) {
ClassNode firstParamType = params.get(0).getType();
if (params.size() > 1 || ClassHelper.isObjectType(firstParamType)) {
String message = "The class " + cNode.getName() + " was incorrectly initialized via the map constructor with null.";
@@ -317,31 +314,31 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
}
}
- private static Parameter createParam(FieldNode fNode, String name, DefaultsMode defaultsMode, AbstractASTTransformation xform, boolean makeImmutable) {
+ private static Parameter createParam(final FieldNode fNode, final String name, final DefaultsMode defaultsMode, final AbstractASTTransformation xform, final boolean makeImmutable) {
ClassNode fType = fNode.getType();
ClassNode type = fType.getPlainNodeReference();
- type.setGenericsPlaceHolder(fType.isGenericsPlaceHolder());
type.setGenericsTypes(fType.getGenericsTypes());
- Parameter param = new Parameter(type, name);
- if (defaultsMode == ON) {
- param.setInitialExpression(providedOrDefaultInitialValue(fNode));
- } else if (defaultsMode == AUTO && fNode.hasInitialExpression()) {
- param.setInitialExpression(fNode.getInitialExpression());
- fNode.setInitialValueExpression(null);
- } else if (!makeImmutable && fNode.hasInitialExpression()) {
- xform.addError("Error during " + MY_TYPE_NAME + " processing, default value processing disabled but default value found for '" + fNode.getName() + "'", fNode);
- }
- return param;
- }
+ type.setGenericsPlaceHolder(fType.isGenericsPlaceHolder());
- private static Expression providedOrDefaultInitialValue(final FieldNode fNode) {
- ClassNode fType = fNode.getType();
Expression init = fNode.getInitialExpression();
- fNode.setInitialValueExpression(null); // GROOVY-10238
- if (init == null || (ClassHelper.isPrimitiveType(fType) && ExpressionUtils.isNullConstant(init))) {
- init = defaultValueX(fType);
+ Parameter param = new Parameter(type, name);
+ switch (defaultsMode) {
+ case ON:
+ if (init == null || (ClassHelper.isPrimitiveType(fType) && ExpressionUtils.isNullConstant(init)))
+ init = defaultValueX(fType);
+ // falls through
+ case AUTO:
+ if (init != null) {
+ param.setInitialExpression(init);
+ fNode.setInitialValueExpression(null); // GROOVY-10238
+ }
+ break;
+ default:
+ if (init != null && !makeImmutable) {
+ xform.addError("Error during " + MY_TYPE_NAME + " processing, default value processing disabled but default value found for '" + fNode.getName() + "'", fNode);
+ }
}
- return init;
+ return param;
}
public static void addSpecialMapConstructors(final int modifiers, final ClassNode cNode, final String message, final boolean addNoArg) {
@@ -350,8 +347,8 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
VariableExpression namedArgs = varX(NAMED_ARGS);
namedArgs.setAccessedVariable(parameters[0]);
code.addStatement(ifElseS(equalsNullX(namedArgs),
- illegalArgumentBlock(message),
- processArgsBlock(cNode, namedArgs)));
+ throwS(ctorX(ClassHelper.make(IllegalArgumentException.class), args(constX(message)))),
+ processNamedArgs(cNode, namedArgs)));
addGeneratedConstructor(cNode, modifiers, parameters, ClassNode.EMPTY_ARRAY, code);
// potentially add a no-arg constructor too
if (addNoArg) {
@@ -361,17 +358,13 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
}
}
- private static BlockStatement illegalArgumentBlock(final String message) {
- return block(throwS(ctorX(ClassHelper.make(IllegalArgumentException.class), args(constX(message)))));
- }
-
- private static BlockStatement processArgsBlock(final ClassNode cNode, final VariableExpression namedArgs) {
+ private static BlockStatement processNamedArgs(final ClassNode cNode, final VariableExpression namedArgs) {
BlockStatement block = new BlockStatement();
List<PropertyNode> props = new ArrayList<>();
for (PropertyNode pNode : cNode.getProperties()) {
if (pNode.isStatic()) continue;
- // if (namedArgs.containsKey(propertyName)) propertyNode= namedArgs.propertyName;
+ // if (namedArgs.containsKey(propertyName)) propertyNode = namedArgs.propertyName;
MethodCallExpression containsProperty = callX(namedArgs, "containsKey", constX(pNode.getName()));
containsProperty.setImplicitThis(false);
block.addStatement(ifS(containsProperty, assignS(varX(pNode), propX(namedArgs, pNode.getName()))));
@@ -382,7 +375,7 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
return block;
}
- private static DefaultsMode maybeDefaultsMode(AnnotationNode node, String name) {
+ private static DefaultsMode maybeDefaultsMode(final AnnotationNode node, final String name) {
if (node != null) {
final Expression member = node.getMember(name);
if (member instanceof ConstantExpression) {
@@ -403,9 +396,4 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
}
return null;
}
-
- @Override
- public int priority() {
- return 5;
- }
}
diff --git a/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy b/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
index e74dac7e94..cab5f1d309 100644
--- a/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/ImmutableTransformTest.groovy
@@ -18,62 +18,40 @@
*/
package org.codehaus.groovy.transform
-import groovy.test.GroovyShellTestCase
import org.codehaus.groovy.control.MultipleCompilationErrorsException
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TestName
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import static groovy.test.GroovyAssert.isAtLeastJdk
-import static org.junit.Assume.assumeTrue
+import static groovy.test.GroovyAssert.assertScript
+import static groovy.test.GroovyAssert.shouldFail
/**
- * Tests for the @Immutable transform.
+ * Tests for the {@code @Immutable} transform.
*/
-@RunWith(JUnit4)
-class ImmutableTransformTest extends GroovyShellTestCase {
+final class ImmutableTransformTest {
- @Rule public TestName nameRule = new TestName()
-
- @Before
- void setUp() {
- super.setUp()
- // check java version requirements
- assumeTrue(nameRule.methodName.endsWith('_vm8').implies(isAtLeastJdk('1.8')))
- }
-
- @After
- void tearDown() {
- super.tearDown()
+ private final GroovyShell shell = GroovyShell.withConfig {
+ imports { star 'groovy.transform' }
}
@Test
void testImmutable() {
- def objects = evaluate('''
- import groovy.transform.Immutable
+ def objects = shell.evaluate '''
enum Coin { HEAD, TAIL }
@Immutable class Bar {
String x, y
Coin c
Collection nums
}
- [new Bar(x:'x', y:'y', c:Coin.HEAD, nums:[1,2]),
- new Bar('x', 'y', Coin.HEAD, [1,2])]
- ''')
-
+ [new Bar(x:'x', y:'y', c:Coin.HEAD, nums:[1,2]), new Bar('x', 'y', Coin.HEAD, [1,2])]
+ '''
assert objects[0].hashCode() == objects[1].hashCode()
assert objects[0] == objects[1]
- assert objects[0].nums.class.name.contains("Unmodifiable")
+ assert objects[0].nums.class.name.contains('Unmodifiable')
}
@Test
void testImmutableClonesListAndCollectionFields() {
- def objects = evaluate("""
- import groovy.transform.Immutable
+ def objects = shell.evaluate '''
def myNums = [1, 2]
@Immutable class Bar {
List nums
@@ -82,27 +60,25 @@ class ImmutableTransformTest extends GroovyShellTestCase {
def myBar = new Bar(nums:myNums, otherNums:myNums)
myNums << 3
[myNums, myBar]
- """)
-
- assertNotSame(objects[0], objects[1].nums)
- assertNotSame(objects[0], objects[1].otherNums)
- assertNotSame(objects[1].nums, objects[1].otherNums)
- assertEquals 3, objects[0].size()
- assertEquals 2, objects[1].nums.size()
- assertEquals 2, objects[1].otherNums.size()
- assertTrue objects[1].nums.class.name.contains("Unmodifiable")
- assertTrue objects[1].otherNums.class.name.contains("Unmodifiable")
+ '''
+ assert objects[0] !== objects[1].nums
+ assert objects[0] !== objects[1].otherNums
+ assert objects[1].nums !== objects[1].otherNums
+ assert objects[0].size() == 3
+ assert objects[1].nums.size() == 2
+ assert objects[1].otherNums.size() == 2
+ assert objects[1].nums.class.name.contains('Unmodifiable')
+ assert objects[1].otherNums.class.name.contains('Unmodifiable')
}
@Test
void testImmutableField() {
- def person = evaluate("""
- import groovy.transform.Immutable
+ def person = shell.evaluate '''
@Immutable class Person {
boolean married
}
new Person(married:false)
- """)
+ '''
shouldFail(ReadOnlyPropertyException) {
person.married = true
}
@@ -110,9 +86,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testCloneableField() {
- def (originalDolly, lab) = evaluate('''
- import groovy.transform.*
-
+ def (originalDolly, lab) = shell.evaluate '''
@AutoClone
class Dolly implements Cloneable {
String name
@@ -125,11 +99,9 @@ class ImmutableTransformTest extends GroovyShellTestCase {
def dolly = new Dolly(name: "The Sheep")
[dolly, new Lab(name: "Area 51", clone: dolly)]
- ''')
-
+ '''
def clonedDolly = lab.clone
def clonedDolly2 = lab.clone
-
assert lab.name == 'Area 51'
assert !originalDolly.is(clonedDolly)
assert originalDolly.name == clonedDolly.name
@@ -139,47 +111,41 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testCloneableFieldNotCloneableObject() {
- shouldFail(CloneNotSupportedException, '''
- import groovy.transform.Immutable
-
- class Dolly {
- String name
- }
+ shouldFail shell, CloneNotSupportedException, '''
+ class Dolly {
+ String name
+ }
- @Immutable class Lab {
- String name
- Cloneable clone
- }
+ @Immutable class Lab {
+ String name
+ Cloneable clone
+ }
- def dolly = new Dolly(name: "The Sheep")
- [dolly, new Lab(name: "Area 51", clone: dolly)]
- ''')
+ def dolly = new Dolly(name: "The Sheep")
+ [dolly, new Lab(name: "Area 51", clone: dolly)]
+ '''
}
@Test
void testImmutableListProp() {
- def objects = evaluate("""
- import groovy.transform.Immutable
+ def objects = shell.evaluate '''
@Immutable class HasList {
String[] letters
List nums
}
def letters = 'A,B,C'.split(',')
def nums = [1, 2]
- [new HasList(letters:letters, nums:nums),
- new HasList(letters, nums)]
- """)
-
- assertEquals objects[0].hashCode(), objects[1].hashCode()
- assertEquals objects[0], objects[1]
+ [new HasList(letters:letters, nums:nums), new HasList(letters, nums)]
+ '''
+ assert objects[0].hashCode() == objects[1].hashCode()
+ assert objects[0] == objects[1]
assert objects[0].letters.size() == 3
assert objects[0].nums.size() == 2
}
@Test
void testImmutableAsMapKey() {
- assertScript """
- import groovy.transform.Immutable
+ assertScript shell, '''
@Immutable final class HasString {
String s
}
@@ -187,49 +153,43 @@ class ImmutableTransformTest extends GroovyShellTestCase {
def k2 = new HasString('xyz')
def map = [(k1):42]
assert map[k2] == 42
- """
+ '''
}
@Test
void testImmutableWithOnlyMap() {
- assertScript """
- import groovy.transform.Immutable
+ assertScript shell, '''
@Immutable final class HasMap {
Map map
}
new HasMap([:])
- """
+ '''
}
@Test
void testImmutableWithPrivateStaticFinalField() {
- assertScript """
- @groovy.transform.Immutable class Foo {
- private static final String BAR = 'baz'
- }
- assert new Foo().BAR == 'baz'
- """
+ assertScript shell, '''
+ @Immutable class Foo {
+ private static final String BAR = 'baz'
+ }
+ assert new Foo().BAR == 'baz'
+ '''
}
@Test
void testImmutableWithInvalidPropertyName() {
- def msg = shouldFail(MissingPropertyException) {
- assertScript """
- import groovy.transform.Immutable
- @Immutable class Simple { }
- new Simple(missing:'Name')
- """
- }
- assert msg.contains('No such property: missing for class: Simple')
+ def err = shouldFail shell, MissingPropertyException, '''
+ @Immutable class Simple {}
+ new Simple(missing:'Name')
+ '''
+ assert err =~ 'No such property: missing for class: Simple'
}
@Test
void testImmutableWithHashMap() {
- assertScript """
- import groovy.transform.Immutable
- import groovy.transform.options.LegacyHashMapPropertyHandler
- @Immutable(propertyHandler = LegacyHashMapPropertyHandler, noArg = false)
- final class HasHashMap {
+ assertScript shell, '''
+ @Immutable(propertyHandler=groovy.transform.options.LegacyHashMapPropertyHandler, noArg=false)
+ class HasHashMap {
HashMap map = [d:4]
}
assert new HasHashMap([a:1]).map == [a:1]
@@ -241,37 +201,12 @@ class ImmutableTransformTest extends GroovyShellTestCase {
assert new HasHashMap(map:5, c:3).map == [map:5, c:3]
assert new HasHashMap(map:null).map == null
assert new HasHashMap(map:[:]).map == [:]
- """
- }
-
- @Test
- void testDefaultValuesAreImmutable_groovy6293() {
- assertScript """
- import groovy.transform.Immutable
- @Immutable class Y { Collection c = []; int foo = 1 }
- def y = new Y(foo: 3)
- assert y.c.class.name.contains('Unmodifiable')
- assert y.c == []
- assert y.foo == 3
- """
- }
-
- @Test
- void testNoArgConstructor_groovy6473() {
- assertScript """
- import groovy.transform.Immutable
- @Immutable class Y { Collection c = []; int foo = 1 }
- def y = new Y()
- assert y.c.class.name.contains('Unmodifiable')
- assert y.c == []
- assert y.foo == 1
- """
+ '''
}
@Test
void testImmutableEquals() {
- assertScript """
- import groovy.transform.Immutable
+ assertScript shell, """
@Immutable class This { String value }
@Immutable class That { String value }
class Other { }
@@ -288,8 +223,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testExistingToString() {
- assertScript """
- import groovy.transform.Immutable
+ assertScript shell, '''
@Immutable class Foo {
String value
}
@@ -307,13 +241,12 @@ class ImmutableTransformTest extends GroovyShellTestCase {
def baz = new Baz('abc')
assert bar.toString() == 'zzz' + foo.toString().replaceAll('Foo', 'Bar')
assert baz.toString() == 'zzzxxx'
- """
+ '''
}
@Test
void testExistingEquals() {
- assertScript """
- import groovy.transform.Immutable
+ assertScript shell, '''
@Immutable class Foo {
String value
}
@@ -350,13 +283,12 @@ class ImmutableTransformTest extends GroovyShellTestCase {
assert baz1 == baz3
assert baz3 != baz1
assert baz3 != baz4
- """
+ '''
}
@Test
void testExistingHashCode() {
- assertScript """
- import groovy.transform.Immutable
+ assertScript shell, '''
@Immutable class Foo {
String value
}
@@ -385,14 +317,13 @@ class ImmutableTransformTest extends GroovyShellTestCase {
def baz2 = new Baz('def')
assert baz1.hashCode() == -1
assert baz2.hashCode() == -100
- """
+ '''
}
@Test
void testBuiltinImmutables() {
- assertScript '''
+ assertScript shell, '''
import java.awt.Color
- import groovy.transform.Immutable
@Immutable class Person {
UUID id
@@ -410,8 +341,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testPrivateFieldAssignedViaConstructor() {
- assertScript '''
- import groovy.transform.Immutable
+ assertScript shell, '''
@Immutable(includeStatic = true)
class Numbers {
private int a1 = 1
@@ -428,28 +358,23 @@ class ImmutableTransformTest extends GroovyShellTestCase {
private static final int c4 = 4
}
def n1 = new Numbers(b1:1, b3:3, c1:1, c2:2, c3:3)
- assert [1..4, 'a'..'c'].combinations().collect{ num, let -> n1."$let$num" } ==
- [1, 2, 3, 4, 1, -2, 3, -4, 1, 2, 3, 4]
+ assert [1..4, 'a'..'c'].combinations().collect{ num, let -> n1."$let$num" } == [1, 2, 3, 4, 1, -2, 3, -4, 1, 2, 3, 4]
'''
}
@Test
void testPrivateFinalFieldAssignedViaConstructorShouldCauseError() {
- shouldFail(ReadOnlyPropertyException) {
- evaluate '''
- import groovy.transform.Immutable
- @Immutable class Numbers {
- private final int b2 = -2
- }
- def n1 = new Numbers(b2:2)
- '''
- }
+ shouldFail shell, ReadOnlyPropertyException, '''
+ @Immutable class Numbers {
+ private final int b2 = -2
+ }
+ def n1 = new Numbers(b2:2)
+ '''
}
@Test
void testImmutableWithImmutableFields() {
- assertScript '''
- import groovy.transform.Immutable
+ assertScript shell, '''
@Immutable class Bar { Integer i }
@Immutable class Foo { Bar b }
def fb = new Foo(new Bar(3))
@@ -459,8 +384,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testImmutableWithConstant() {
- assertScript '''
- import groovy.transform.Immutable
+ assertScript shell, '''
@Immutable class MinIntegerHolder {
Integer i
public static final MIN = 3
@@ -476,8 +400,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testStaticsAllowed_ThoughUsuallyBadDesign() {
// design here is questionable as getDescription() method is not idempotent
- assertScript '''
- import groovy.transform.Immutable
+ assertScript shell, '''
@Immutable class Person {
String first, last
static species = 'Human'
@@ -506,9 +429,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testImmutableToStringVariants() {
- assertScript '''
- import groovy.transform.*
-
+ assertScript shell, '''
@Immutable
class Person1 { String first, last }
@@ -526,10 +447,9 @@ class ImmutableTransformTest extends GroovyShellTestCase {
'''
}
- @Test
+ @Test // GROOVY-4997
void testImmutableUsageOnInnerClasses() {
- assertScript '''
- import groovy.transform.Immutable
+ assertScript shell, '''
class A4997 {
@Immutable
static class B4997 { String name }
@@ -546,8 +466,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testKnownImmutableClassesWithNamedParameters() {
- assertScript '''
- import groovy.transform.*
+ assertScript shell, '''
@Immutable(knownImmutableClasses = [Address])
class Person {
String first, last
@@ -562,8 +481,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testKnownImmutableClassesWithExplicitConstructor() {
- assertScript '''
- @groovy.transform.Immutable(knownImmutableClasses = [Address])
+ assertScript shell, '''
+ @Immutable(knownImmutableClasses = [Address])
class Person {
String first, last
Address address
@@ -578,8 +497,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testKnownImmutableClassesWithCoercedConstruction() {
- assertScript '''
- @groovy.transform.Immutable(knownImmutableClasses = [Address])
+ assertScript shell, '''
+ @Immutable(knownImmutableClasses = [Address])
class Person {
String first, last
Address address
@@ -594,34 +513,32 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testKnownImmutableClassesMissing() {
- def msg = shouldFail(RuntimeException) {
- evaluate '''
- @groovy.transform.ToString class Address { String street }
-
- @groovy.transform.Immutable
- class Person {
- String first, last
- Address address
- }
+ def err = shouldFail shell, RuntimeException, '''
+ @ToString class Address { String street }
- new Person(first: 'John', last: 'Doe', address: new Address(street: 'Street'))
- '''
- }
- assert msg.contains("Unsupported type (Address) found for field 'address' while constructing immutable class Person")
- assert msg.contains("Immutable classes only support properties with effectively immutable types")
+ @Immutable
+ class Person {
+ String first, last
+ Address address
+ }
+
+ new Person(first: 'John', last: 'Doe', address: new Address(street: 'Street'))
+ '''
+ assert err.message.contains("Unsupported type (Address) found for field 'address' while constructing immutable class Person")
+ assert err.message.contains("Immutable classes only support properties with effectively immutable types")
}
// GROOVY-5828
@Test
void testKnownImmutableCollectionClass() {
- assertScript '''
- @groovy.transform.Immutable
+ assertScript shell, '''
+ @Immutable
class ItemsControl { List list }
def itemsControl = new ItemsControl(['Fee', 'Fi', 'Fo', 'Fum'])
assert itemsControl.list.class.name.contains('Unmodifiable')
// ok, Items not really immutable but pretend so for the purpose of this test
- @groovy.transform.Immutable(knownImmutableClasses = [List])
+ @Immutable(knownImmutableClasses = [List])
class Items { List list }
def items = new Items(['Fee', 'Fi', 'Fo', 'Fum'])
assert !items.list.class.name.contains('Unmodifiable')
@@ -631,9 +548,9 @@ class ImmutableTransformTest extends GroovyShellTestCase {
// GROOVY-5828
@Test
void testKnownImmutables() {
- assertScript '''
+ assertScript shell, '''
// ok, Items not really immutable but pretend so for the purpose of this test
- @groovy.transform.Immutable(knownImmutables = ['list1'])
+ @Immutable(knownImmutables = ['list1'])
class Items {
List list1
List list2
@@ -647,23 +564,19 @@ class ImmutableTransformTest extends GroovyShellTestCase {
// GROOVY-5449
@Test
void testShouldNotThrowNPE() {
- def msg = shouldFail(RuntimeException) {
- evaluate '''
- @groovy.transform.Immutable
+ def err = shouldFail shell, RuntimeException, '''
+ @Immutable
class Person {
def name
}
- '''
- }
- assert msg.contains("Unsupported type (java.lang.Object or def) found for field 'name' while ")
+ '''
+ assert err.message.contains("Unsupported type (java.lang.Object or def) found for field 'name' while ")
}
// GROOVY-6192
@Test
void testWithEqualsAndHashCodeASTOverride() {
- assertScript '''
- import groovy.transform.*
-
+ assertScript shell, '''
@Immutable
@EqualsAndHashCode(includes = ['id'])
class B {
@@ -678,8 +591,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
// GROOVY-6354
@Test
void testCopyWith() {
- def tester = new GroovyClassLoader().parseClass(
- '''@groovy.transform.Immutable(copyWith = true)
+ def tester = new GroovyClassLoader().parseClass('''\
+ |@groovy.transform.Immutable(copyWith = true)
|class Person {
| String first, last
|}
@@ -714,8 +627,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testGenericsCopyWith() {
- def tester = new GroovyClassLoader().parseClass(
- '''@groovy.transform.Immutable(copyWith = true)
+ def tester = new GroovyClassLoader().parseClass('''\
+ |@groovy.transform.Immutable(copyWith = true)
|class Person {
| List<String> names
|}
@@ -738,8 +651,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testWithPrivatesCopyWith() {
- def tester = new GroovyClassLoader().parseClass(
- '''@groovy.transform.Immutable(copyWith=true)
+ def tester = new GroovyClassLoader().parseClass('''\
+ |@groovy.transform.Immutable(copyWith=true)
|class Foo {
| String first
| String last
@@ -769,8 +682,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testStaticWithPrivatesCopyWith() {
- def tester = new GroovyClassLoader().parseClass(
- '''@groovy.transform.Immutable(copyWith=true)
+ def tester = new GroovyClassLoader().parseClass('''\
+ |@groovy.transform.Immutable(copyWith=true)
|@groovy.transform.CompileStatic
|class Foo {
| String first
@@ -801,8 +714,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testTypedWithPrivatesCopyWith() {
- def tester = new GroovyClassLoader().parseClass(
- '''@groovy.transform.Immutable(copyWith=true)
+ def tester = new GroovyClassLoader().parseClass('''\
+ |@groovy.transform.Immutable(copyWith=true)
|@groovy.transform.TypeChecked
|class Foo {
| String first
@@ -833,8 +746,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testStaticCopyWith() {
- def tester = new GroovyClassLoader().parseClass(
- '''@groovy.transform.Immutable(copyWith = true)
+ def tester = new GroovyClassLoader().parseClass('''\
+ |@groovy.transform.Immutable(copyWith = true)
|@groovy.transform.CompileStatic
|class Person {
| String first, last
@@ -870,8 +783,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testTypedCopyWith() {
- def tester = new GroovyClassLoader().parseClass(
- '''@groovy.transform.Immutable(copyWith = true)
+ def tester = new GroovyClassLoader().parseClass('''\
+ |@groovy.transform.Immutable(copyWith = true)
|@groovy.transform.TypeChecked
|class Person {
| String first, last
@@ -907,8 +820,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testCopyWithSkipping() {
- def tester = new GroovyClassLoader().parseClass(
- '''@groovy.transform.Immutable(copyWith = true)
+ def tester = new GroovyClassLoader().parseClass('''\
+ |@groovy.transform.Immutable(copyWith = true)
|class Person {
| String first, last
| List<Person> copyWith( i ) {
@@ -929,8 +842,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testStaticCopyWithSkipping() {
- def tester = new GroovyClassLoader().parseClass(
- '''@groovy.transform.Immutable(copyWith = true)
+ def tester = new GroovyClassLoader().parseClass('''\
+ |@groovy.transform.Immutable(copyWith = true)
|@groovy.transform.CompileStatic
|class Person {
| String first, last
@@ -952,8 +865,8 @@ class ImmutableTransformTest extends GroovyShellTestCase {
@Test
void testTypedCopyWithSkipping() {
- def tester = new GroovyClassLoader().parseClass(
- '''@groovy.transform.Immutable(copyWith = true)
+ def tester = new GroovyClassLoader().parseClass('''\
+ |@groovy.transform.Immutable(copyWith = true)
|@groovy.transform.TypeChecked
|class Person {
| String first, last
@@ -973,28 +886,47 @@ class ImmutableTransformTest extends GroovyShellTestCase {
assert result.first == [ 'tim', 'tim' ]
}
+ // GROOVY-6293
+ @Test
+ void testDefaultValuesAreImmutable() {
+ assertScript shell, '''
+ @Immutable class Y { Collection c = []; int foo = 1 }
+ def y = new Y(foo: 3)
+ assert y.c.class.name.contains('Unmodifiable')
+ assert y.c == []
+ assert y.foo == 3
+ '''
+ }
+
+ // GROOVY-6473
+ @Test
+ void testNoArgConstructor() {
+ assertScript shell, '''
+ @Immutable class Y { Collection c = []; int foo = 1 }
+ def y = new Y()
+ assert y.c.class.name.contains('Unmodifiable')
+ assert y.c == []
+ assert y.foo == 1
+ '''
+ }
+
// GROOVY-7227
@Test
void testKnownImmutablesWithInvalidPropertyNameResultsInError() {
- def message = shouldFail {
- evaluate """
- import groovy.transform.Immutable
- @Immutable(knownImmutables=['sirName'])
- class Person {
- String surName
- }
- new Person(surName: "Doe")
- """
- }
- assert message.contains("Error during immutable class processing: 'knownImmutables' property 'sirName' does not exist.")
+ def err = shouldFail shell, '''
+ @Immutable(knownImmutables=['sirName'])
+ class Person {
+ String surName
+ }
+ new Person(surName: "Doe")
+ '''
+ assert err.message.contains("Error during immutable class processing: 'knownImmutables' property 'sirName' does not exist.")
}
// GROOVY-7162
@Test
void testImmutableWithSuperClass() {
- assertScript '''
- import groovy.transform.*
-
+ assertScript shell, '''
@EqualsAndHashCode
class Person {
String name
@@ -1016,33 +948,10 @@ class ImmutableTransformTest extends GroovyShellTestCase {
'''
}
- // GROOVY-7600
- @Test
- void testImmutableWithOptional_vm8() {
- assertScript '''
- @groovy.transform.Immutable class Person {
- String name
- Optional<String> address
- }
- def p = new Person('Joe', Optional.of('Home'))
- assert p.toString() == 'Person(Joe, Optional[Home])'
- assert p.address.get() == 'Home'
- '''
- shouldFail(MultipleCompilationErrorsException) {
- evaluate '''
- @groovy.transform.Immutable class Person {
- String name
- Optional<Date> address
- }
- '''
- }
- }
-
// GROOVY-7599
@Test
- void testImmutableWithJSR310_vm8() {
- assertScript '''
- import groovy.transform.Immutable
+ void testImmutableWithJSR310() {
+ assertScript shell, '''
import java.time.*
@Immutable
@@ -1056,11 +965,30 @@ class ImmutableTransformTest extends GroovyShellTestCase {
'''
}
+ // GROOVY-7600
+ @Test
+ void testImmutableWithOptional() {
+ assertScript shell, '''
+ @Immutable class Person {
+ String name
+ Optional<String> address
+ }
+ def p = new Person('Joe', Optional.of('Home'))
+ assert p.toString() == 'Person(Joe, Optional[Home])'
+ assert p.address.get() == 'Home'
+ '''
+ shouldFail shell, MultipleCompilationErrorsException, '''
+ @Immutable class Person {
+ String name
+ Optional<Date> address
+ }
+ '''
+ }
+
// GROOVY-8416
@Test
void testMapFriendlyNamedArgs() {
- assertScript '''
- import groovy.transform.Immutable
+ assertScript shell, '''
@Immutable
class Point {
int x, y
@@ -1079,9 +1007,7 @@ class ImmutableTransformTest extends GroovyShellTestCase {
// GROOVY-8967
@Test
void testPropertiesWithDefaultValues() {
- assertScript '''
- import groovy.transform.*
-
+ assertScript shell, '''
@Immutable
class Thing {
int i = 42
@@ -1096,4 +1022,27 @@ class ImmutableTransformTest extends GroovyShellTestCase {
assert thing.with{ [i, c, d, value] } == [-1, null, null, null]
'''
}
+
+ // GROOVY-10790
+ @Test
+ void testDefaultsTrueExtraConstructors() {
+ assertScript shell, '''
+ @Immutable(defaults=true, noArg=false)
+ class Foo {
+ String bar, baz = 'z'
+ }
+ assert new Foo('x','y').toString() == 'Foo(x, y)'
+ assert new Foo('x').toString() == 'Foo(x, z)'
+ assert new Foo().toString() == 'Foo(null, z)'
+ '''
+ assertScript shell, '''
+ @Immutable(defaults=true) // MapConstructor also creates no-arg ctor
+ class Foo {
+ String bar, baz = 'z'
+ }
+ assert new Foo('x','y').toString() == 'Foo(x, y)'
+ assert new Foo('x').toString() == 'Foo(x, z)'
+ assert new Foo().toString() == 'Foo(null, z)'
+ '''
+ }
}
diff --git a/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy b/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy
index 9976e5be22..228690b108 100644
--- a/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy
@@ -18,27 +18,38 @@
*/
package org.codehaus.groovy.transform
-import groovy.test.GroovyShellTestCase
+import org.junit.Test
-class TupleConstructorTransformTest extends GroovyShellTestCase {
+import static groovy.test.GroovyAssert.assertScript
+import static groovy.test.GroovyAssert.shouldFail
+/**
+ * Tests for the {@code TupleConstructor} transform.
+ */
+final class TupleConstructorTransformTest {
+
+ private final GroovyShell shell = GroovyShell.withConfig {
+ imports { star 'groovy.transform' }
+ }
+
+ @Test
void testBasics() {
- assertScript '''
- @groovy.transform.TupleConstructor
+ assertScript shell, '''
+ @TupleConstructor(defaults=false)
class Person {
- String firstName
- String lastName
+ String firstName, lastName
}
def p = new Person('John', 'Doe')
assert p.firstName == 'John'
- assert p.lastName == 'Doe'
+ assert p.lastName == 'Doe'
'''
}
+ @Test
void testCopyConstructor() {
- assertScript '''
- @groovy.transform.TupleConstructor(force=true)
+ assertScript shell, '''
+ @TupleConstructor(force=true)
class Person {
String firstName, lastName
Person(Person that) {
@@ -48,17 +59,18 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
def p = new Person('John', 'Doe')
assert p.firstName == 'John'
- assert p.lastName == 'Doe'
+ assert p.lastName == 'Doe'
p = new Person(p)
assert p.firstName == 'John'
- assert p.lastName == null
+ assert p.lastName == null
'''
}
+ @Test
void testFieldsAndInitializers() {
- assertScript '''
- @groovy.transform.TupleConstructor(includeFields=true)
+ assertScript shell, '''
+ @TupleConstructor(includeFields=true)
class Person {
String firstName = 'John'
private String lastName = 'Doe'
@@ -67,18 +79,21 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
def p = new Person()
assert p.firstName == 'John'
- assert p.lastName == 'Doe'
+ assert p.lastName == 'Doe'
p = new Person('Jane')
assert p.firstName == 'Jane'
- assert p.lastName == 'Doe'
+ assert p.lastName == 'Doe'
+
+ p = new Person('Jane', 'Eyre')
+ assert p.firstName == 'Jane'
+ assert p.lastName == 'Eyre'
'''
}
+ @Test
void testFieldsAndNamesAndPost() {
- assertScript '''
- import groovy.transform.*
-
+ assertScript shell, '''
@ToString(includeFields=true, includeNames=true)
@TupleConstructor(post={ full = "$first $last" })
class Person {
@@ -86,15 +101,13 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
private final String full
}
- assert new Person('Dierk', 'Koenig').toString() ==
- 'Person(first:Dierk, last:Koenig, full:Dierk Koenig)'
+ assert new Person('Dierk', 'Koenig').toString() == 'Person(first:Dierk, last:Koenig, full:Dierk Koenig)'
'''
}
+ @Test
void testSuperPropsAndPreAndPost() {
- assertScript '''
- import groovy.transform.*
-
+ assertScript shell, '''
@TupleConstructor
class Person {
String first, last
@@ -102,7 +115,7 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
@CompileStatic // optional
@ToString(includeSuperProperties=true)
- @TupleConstructor(includeSuperProperties=true, pre={ super(first, last?.toLowerCase()) }, post = { this.first = this.first?.toUpperCase() })
+ @TupleConstructor(includeSuperProperties=true, pre={ super(first, last?.toLowerCase()) }, post={ this.first = this.first?.toUpperCase() })
class Author extends Person {
String bookName
}
@@ -113,9 +126,10 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
}
// GROOVY-7522
- void testExistingEmptyConstructorTakesPrecedence() {
- assertScript '''
- @groovy.transform.TupleConstructor
+ @Test
+ void testExistingConstructorTakesPrecedence() {
+ assertScript shell, '''
+ @TupleConstructor
class Cat {
String name
int age
@@ -125,75 +139,74 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
assert new Cat("Mr. Bigglesworth").name == null
assert Cat.declaredConstructors.size() == 1
'''
+ assertScript shell, '''
+ @TupleConstructor(force=true)
+ class Cat {
+ String name
+ int age
+ Cat(String name) {}
+ }
+
+ assert new Cat().name == null
+ assert new Cat("Mr. Bigglesworth").name == null
+ assert new Cat("Mr. Bigglesworth", 42).name == "Mr. Bigglesworth"
+ assert Cat.declaredConstructors.size() == 3 // (), (String) and (String,int)
+ '''
}
+ @Test
void testIncludesAndExcludesTogetherResultsInError() {
- def message = shouldFail {
- evaluate '''
- import groovy.transform.TupleConstructor
-
- @TupleConstructor(includes='surName', excludes='surName')
- class Person {
- String surName
- }
-
- new Person("Doe")
- '''
- }
- assert message.contains("Error during @TupleConstructor processing: Only one of 'includes' and 'excludes' should be supplied not both.")
+ def err = shouldFail shell, '''
+ @TupleConstructor(includes='surName', excludes='surName')
+ class Person {
+ String surName
+ }
+ '''
+ assert err.message.contains("Error during @TupleConstructor processing: Only one of 'includes' and 'excludes' should be supplied not both.")
}
+ @Test
void testIncludesWithInvalidPropertyNameResultsInError() {
- def message = shouldFail {
- evaluate '''
- import groovy.transform.TupleConstructor
-
- @TupleConstructor(includes='sirName')
- class Person {
- String firstName
- String surName
- }
-
- def p = new Person("John", "Doe")
- '''
- }
- assert message.contains("Error during @TupleConstructor processing: 'includes' property 'sirName' does not exist.")
+ def err = shouldFail shell, '''
+ @TupleConstructor(includes='sirName')
+ class Person {
+ String firstName
+ String surName
+ }
+ '''
+ assert err.message.contains("Error during @TupleConstructor processing: 'includes' property 'sirName' does not exist.")
}
+ @Test
void testExcludesWithInvalidPropertyNameResultsInError() {
- def message = shouldFail {
- evaluate '''
- import groovy.transform.TupleConstructor
-
- @TupleConstructor(excludes='sirName')
- class Person {
- String firstName
- String surName
- }
-
- def p = new Person("John", "Doe")
- '''
- }
- assert message.contains("Error during @TupleConstructor processing: 'excludes' property 'sirName' does not exist.")
+ def err = shouldFail shell, '''
+ @TupleConstructor(excludes='sirName')
+ class Person {
+ String firstName
+ String surName
+ }
+ '''
+ assert err.message.contains("Error during @TupleConstructor processing: 'excludes' property 'sirName' does not exist.")
}
// GROOVY-7523
+ @Test
void testIncludesWithEmptyList() {
- assertScript '''
- @groovy.transform.TupleConstructor(includes=[])
+ assertScript shell, '''
+ @TupleConstructor(includes=[])
class Cat {
String name
int age
}
+
assert Cat.declaredConstructors.size() == 1
'''
}
// GROOVY-7524
+ @Test
void testCombiningWithInheritConstructors() {
- assertScript '''
- import groovy.transform.*
-
+ assertScript shell, '''
@TupleConstructor
class NameId {
String name
@@ -217,9 +230,9 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
}
// GROOVY-7672
+ @Test
void testMultipleUsages() {
- assertScript '''
- import groovy.transform.*
+ assertScript shell, '''
import java.awt.Color
class Named {
@@ -245,11 +258,10 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
}
// GROOVY-6454
+ @Test
void testInternalFieldsAreIncludedIfRequested() {
- assertScript '''
- import groovy.transform.*
-
- @TupleConstructor(allNames = true)
+ assertScript shell, '''
+ @TupleConstructor(allNames=true)
class HasInternalName {
String $internal
}
@@ -259,18 +271,17 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
}
// GROOVY-7981
+ @Test
void testVisibilityOptions() {
- assertScript '''
- import groovy.transform.*
+ assertScript shell, '''
import static groovy.transform.options.Visibility.*
import static java.lang.reflect.Modifier.isPrivate
@VisibilityOptions(PRIVATE)
@Immutable
- @ASTTest(phase = CANONICALIZATION,
- value = {
- node.constructors.every { isPrivate(it.modifiers) }
- })
+ @ASTTest(phase=CANONICALIZATION, value={
+ node.constructors.every { isPrivate(it.modifiers) }
+ })
class Person {
String first, last
int age
@@ -280,33 +291,32 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
}
@CompileStatic
- def method() {
+ void test() {
def p = Person.makePerson(first: 'John', last: 'Smith', age: 20)
assert p.toString() == 'Person(John, Smith, 20)'
}
- method()
+ test()
'''
}
// GROOVY-7981
+ @Test
void testMultipleVisibilityOptions() {
- assertScript '''
- import groovy.transform.*
- import java.lang.reflect.Modifier
+ assertScript shell, '''
import static groovy.transform.options.Visibility.*
-
- @VisibilityOptions(value = PROTECTED, id = 'first_only')
- @VisibilityOptions(constructor = PRIVATE, id = 'age_only')
- @TupleConstructor(visibilityId = 'first_only', includes = 'first', defaults = false, force = true)
- @TupleConstructor(visibilityId = 'age_only', includes = 'age', defaults = false, force = true)
- @ASTTest(phase = CANONICALIZATION,
- value = {
- assert node.constructors.size() == 2
- node.constructors.each {
- assert (it.typeDescriptor == 'void <init>(java.lang.String)' && it.modifiers == Modifier.PROTECTED) ||
- (it.typeDescriptor == 'void <init>(int)' && it.modifiers == Modifier.PRIVATE)
- }
- })
+ import static java.lang.reflect.Modifier.*
+
+ @VisibilityOptions(value=PROTECTED, id='first_only')
+ @VisibilityOptions(constructor=PRIVATE, id='age_only')
+ @TupleConstructor(visibilityId='first_only', includes='first', defaults=false, force=true)
+ @TupleConstructor(visibilityId='age_only', includes='age', defaults=false, force=true)
+ @ASTTest(phase=CANONICALIZATION, value={
+ assert node.constructors.size() == 2
+ node.constructors.each {
+ assert (it.typeDescriptor == 'void <init>(java.lang.String)' && isProtected(it.modifiers)) ||
+ (it.typeDescriptor == 'void <init>(int)' && isPrivate(it.modifiers))
+ }
+ })
class Person {
String first, last
int age
@@ -315,15 +325,15 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
assert new Person(42).age == 42
}
}
+
Person.test()
'''
}
// GROOVY-8455, GROOVY-8453
+ @Test
void testPropPsuedoPropAndFieldOrderIncludingInheritedMembers() {
- assertScript '''
- import groovy.transform.TupleConstructor
-
+ assertScript shell, '''
class Basepubf{}
class Basep{}
class Basepp{}
@@ -371,27 +381,26 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
}
// GROOVY-10361
- void testTupleConstructorDefaultsModes() {
- assertScript '''
- import groovy.transform.*
- import static groovy.test.GroovyAssert.shouldFail
-
- @TupleConstructor(defaultsMode = DefaultsMode.OFF, includeFields = true)
+ @Test
+ void testDefaultsMode() {
+ assertScript shell, '''
+ @TupleConstructor(defaultsMode=DefaultsMode.OFF, includeFields=true)
class A {
String won
private int too
}
- assert A.declaredConstructors.toString() == '[public A(java.lang.String,int)]'
- shouldFail """
- @TupleConstructor(defaultsMode = DefaultsMode.OFF, includeFields = true)
+ assert A.declaredConstructors.toString() == '[public A(java.lang.String,int)]'
+ '''
+ shouldFail shell, '''
+ @TupleConstructor(defaultsMode=DefaultsMode.OFF, includeFields=true)
class B {
String won = 'one'
private int too = 2
}
- """
-
- @TupleConstructor(defaultsMode = DefaultsMode.AUTO, includeFields = true)
+ '''
+ assertScript shell, '''
+ @TupleConstructor(defaultsMode=DefaultsMode.AUTO, includeFields=true)
class C {
String one = 'won'
int too = 2
@@ -405,8 +414,9 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
'public C(java.lang.String,int)',
'public C(int)'
].toSet()
-
- @TupleConstructor(defaultsMode = DefaultsMode.ON, includeFields = true)
+ '''
+ assertScript shell, '''
+ @TupleConstructor(defaultsMode=DefaultsMode.ON, includeFields=true)
class D {
String one = 'won'
int too = 2
@@ -422,22 +432,48 @@ class TupleConstructorTransformTest extends GroovyShellTestCase {
'public D()'
].toSet()
'''
- assertScript '''
- import groovy.transform.*
- @Canonical(defaultsMode=DefaultsMode.AUTO)
- class Bar {
- String a = 'a'
- long b
- Integer c = 24
- short d
- String e = 'e'
- }
-
- short one = 1
- assert new Bar(3L, one).toString() == 'Bar(a, 3, 24, 1, e)'
- assert new Bar('A', 3L, one).toString() == 'Bar(A, 3, 24, 1, e)'
- assert new Bar('A', 3L, 42, one).toString() == 'Bar(A, 3, 42, 1, e)'
- assert new Bar('A', 3L, 42, one, 'E').toString() == 'Bar(A, 3, 42, 1, E)'
+ assertScript shell, '''
+ @Canonical(defaultsMode=DefaultsMode.AUTO)
+ class Bar {
+ String a = 'a'
+ long b
+ Integer c = 24
+ short d
+ String e = 'e'
+ }
+
+ short one = 1
+ assert new Bar(3L, one).toString() == 'Bar(a, 3, 24, 1, e)'
+ assert new Bar('A', 3L, one).toString() == 'Bar(A, 3, 24, 1, e)'
+ assert new Bar('A', 3L, 42, one).toString() == 'Bar(A, 3, 42, 1, e)'
+ assert new Bar('A', 3L, 42, one, 'E').toString() == 'Bar(A, 3, 42, 1, E)'
+ '''
+ }
+
+ // GROOVY-10790
+ @Test
+ void testWithMapConstructor() {
+ assertScript shell, '''
+ @MapConstructor @TupleConstructor
+ @ToString
+ class Foo {
+ String bar, baz = 'z'
+ }
+
+ assert new Foo('x','y').toString() == 'Foo(x, y)'
+ assert new Foo('x').toString() == 'Foo(x, z)'
+ assert new Foo().toString() == 'Foo(null, z)'
+ '''
+ assertScript shell, ''' // multiple sources of no-arg constructor
+ @MapConstructor(noArg=true) @TupleConstructor
+ @ToString
+ class Foo {
+ String bar, baz = 'z'
+ }
+
+ assert new Foo('x','y').toString() == 'Foo(x, y)'
+ assert new Foo('x').toString() == 'Foo(x, z)'
+ assert new Foo().toString() == 'Foo(null, z)'
'''
}
}