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 2025/07/15 17:20:54 UTC
(groovy) 02/02: GROOVY-11644: check for `namedVariant` constructor duplication
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch GROOVY-11644
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 8660ceb881d07937b7c222db4976f0e27803db78
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Jul 15 12:20:11 2025 -0500
GROOVY-11644: check for `namedVariant` constructor duplication
---
.../transform/RecordTypeASTTransformation.java | 5 ++++
.../TupleConstructorASTTransformation.java | 30 +++++++++++++++++++++-
.../org/codehaus/groovy/classgen/RecordTest.groovy | 22 ++++++++++++++++
3 files changed, 56 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/codehaus/groovy/transform/RecordTypeASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/RecordTypeASTTransformation.java
index 2a344463ce..0b80bc8e6e 100644
--- a/src/main/java/org/codehaus/groovy/transform/RecordTypeASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/RecordTypeASTTransformation.java
@@ -39,6 +39,7 @@ import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.RecordComponentNode;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
@@ -283,6 +284,10 @@ public class RecordTypeASTTransformation extends AbstractASTTransformation imple
if (hasAnnotation(cNode, TupleConstructorASTTransformation.MY_TYPE)) {
AnnotationNode tupleConstructor = cNode.getAnnotations(TupleConstructorASTTransformation.MY_TYPE).get(0);
+ if (pList.size() == 1 && pList.get(0).getType().equals(MAP_TYPE)
+ && memberHasValue(tupleConstructor, "namedVariant", Boolean.TRUE)) {
+ tupleConstructor.setMember("namedVariant", ConstantExpression.PRIM_FALSE); // GROOVY-11644: conflicts
+ }
if (unsupportedTupleAttribute(tupleConstructor, "excludes")) return;
if (unsupportedTupleAttribute(tupleConstructor, "includes")) return;
if (unsupportedTupleAttribute(tupleConstructor, "includeProperties")) return;
diff --git a/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
index e3b226167e..25a81f9fb0 100644
--- a/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java
@@ -60,10 +60,12 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Stream;
import static groovy.transform.DefaultsMode.OFF;
import static groovy.transform.DefaultsMode.ON;
import static java.util.stream.Collectors.joining;
+import static java.util.stream.IntStream.rangeClosed;
import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedConstructor;
import static org.apache.groovy.ast.tools.ClassNodeUtils.hasExplicitConstructor;
import static org.apache.groovy.ast.tools.ConstructorNodeUtils.checkPropNamesS;
@@ -284,8 +286,15 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
tupleCtor.putNodeMetaData("_SKIPPABLE_ANNOTATIONS", Boolean.TRUE);
}
if (namedVariant) {
+ var pType = ClassHelper.MAP_TYPE.getPlainNodeReference();
+ // GROOVY-11644: check if named-param constructor would clash
+ if (cNode.getDeclaredConstructor(params(param(pType, "map"))) != null
+ || variants(signature).anyMatch(types -> types.length == 1 && types[0].equals(pType))) {
+ xform.addError(String.format("%s(namedVariant=true) specifies duplicate constructor: %s(%s)",
+ xform.getAnnotationName(), cNode.getNameWithoutPackage(), ClassNodeUtils.formatTypeName(pType)), anno.getLineNumber() > 0 ? anno : cNode);
+ }
BlockStatement inner = new BlockStatement();
- Parameter mapParam = param(ClassHelper.MAP_TYPE.getPlainNodeReference(), NAMED_ARGS);
+ Parameter mapParam = param(pType, NAMED_ARGS);
ArgumentListExpression args = new ArgumentListExpression();
List<String> propNames = new ArrayList<>();
Map<Parameter, Expression> seen = new HashMap<>();
@@ -398,4 +407,23 @@ public class TupleConstructorASTTransformation extends AbstractASTTransformation
}
return null;
}
+
+ private static Stream<ClassNode[]> variants(final Parameter[] parameters) {
+ int n = (int) Stream.of(parameters).filter(Parameter::hasInitialExpression).count();
+
+ return rangeClosed(0, n).mapToObj(i -> {
+ // drop parameters with value from right to left
+ ClassNode[] signature = new ClassNode[parameters.length - i];
+ int j = 1, index = 0;
+ for (Parameter parameter : parameters) {
+ if (j > n - i && parameter.hasInitialExpression()) {
+ // skip parameter with default argument
+ } else {
+ signature[index++] = parameter.getType();
+ }
+ if (parameter.hasInitialExpression()) j += 1;
+ }
+ return signature;
+ });
+ }
}
diff --git a/src/test/groovy/org/codehaus/groovy/classgen/RecordTest.groovy b/src/test/groovy/org/codehaus/groovy/classgen/RecordTest.groovy
index 13a7dfb48e..eaa7ca49e2 100644
--- a/src/test/groovy/org/codehaus/groovy/classgen/RecordTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/classgen/RecordTest.groovy
@@ -591,6 +591,28 @@ final class RecordTest {
new Person(name:'John Doe')
}
'''
+
+ // GROOVY-11644
+ assertScript shell, '''
+ record Person(@NamedParam(value='name', required=true) Map map) {
+ Person {
+ assert map.name instanceof String
+ && map.name.trim().size() > 1
+ }
+ String getName() {
+ return map.get('name')
+ }
+ }
+
+ def person = new Person(name:'Frank Grimes', hair:'brown')
+ assert person.name == 'Frank Grimes'
+ shouldFail {
+ new Person([:])
+ }
+ shouldFail {
+ new Person()
+ }
+ '''
}
@Test