You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2019/12/02 01:41:30 UTC
[groovy] 01/03: GROOVY-9183: use property initial value as default
for map.getOrDefault
This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 4e28d41ddbaa761299127707cf33a8dcae2f407e
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Thu Nov 28 18:00:29 2019 -0600
GROOVY-9183: use property initial value as default for map.getOrDefault
if no initial value expression was supplied:
- primitive (or wrapper) types: zero
- reference types: null
---
.../transform/NamedVariantASTTransformation.java | 71 +++++++++--------
.../transform/NamedVariantTransformTest.groovy | 92 +++++++++++++++++++---
2 files changed, 121 insertions(+), 42 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/transform/NamedVariantASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/NamedVariantASTTransformation.java
index dcd444a..ae9ea5c 100644
--- a/src/main/java/org/codehaus/groovy/transform/NamedVariantASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/NamedVariantASTTransformation.java
@@ -24,17 +24,14 @@ import groovy.transform.NamedVariant;
import org.apache.groovy.ast.tools.AnnotatedNodeUtils;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotationNode;
-import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
-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.MapExpression;
import org.codehaus.groovy.ast.stmt.AssertStatement;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ForStatement;
@@ -45,13 +42,17 @@ import org.codehaus.groovy.control.SourceUnit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedConstructor;
import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod;
import static org.apache.groovy.ast.tools.ClassNodeUtils.isInnerClass;
import static org.apache.groovy.ast.tools.VisibilityUtils.getVisibility;
+import static org.codehaus.groovy.ast.ClassHelper.MAP_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
+import static org.codehaus.groovy.ast.ClassHelper.isNumberType;
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;
@@ -62,8 +63,11 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.castX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.entryX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getAllProperties;
import static org.codehaus.groovy.ast.tools.GeneralUtils.list2args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.mapX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.nullX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
import static org.codehaus.groovy.ast.tools.GeneralUtils.plusX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
@@ -72,33 +76,33 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
public class NamedVariantASTTransformation extends AbstractASTTransformation {
- private static final Class MY_CLASS = NamedVariant.class;
- private static final ClassNode MY_TYPE = make(MY_CLASS);
- private static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
+
+ private static final ClassNode NAMED_VARIANT_TYPE = make(NamedVariant.class);
+ private static final String NAMED_VARIANT = "@" + NAMED_VARIANT_TYPE.getNameWithoutPackage();
private static final ClassNode NAMED_PARAM_TYPE = makeWithoutCaching(NamedParam.class, false);
private static final ClassNode NAMED_DELEGATE_TYPE = makeWithoutCaching(NamedDelegate.class, false);
@Override
- public void visit(ASTNode[] nodes, SourceUnit source) {
+ public void visit(final ASTNode[] nodes, final SourceUnit source) {
init(nodes, source);
MethodNode mNode = (MethodNode) nodes[1];
AnnotationNode anno = (AnnotationNode) nodes[0];
- if (!MY_TYPE.equals(anno.getClassNode())) return;
+ if (!NAMED_VARIANT_TYPE.equals(anno.getClassNode())) return;
Parameter[] fromParams = mNode.getParameters();
if (fromParams.length == 0) {
- addError("Error during " + MY_TYPE_NAME + " processing. No-args method not supported.", mNode);
+ addError("Error during " + NAMED_VARIANT + " processing. No-args method not supported.", mNode);
return;
}
boolean autoDelegate = memberHasValue(anno, "autoDelegate", true);
- Parameter mapParam = param(GenericsUtils.nonGeneric(ClassHelper.MAP_TYPE), "__namedArgs");
- List<Parameter> genParams = new ArrayList<Parameter>();
+ Parameter mapParam = param(GenericsUtils.nonGeneric(MAP_TYPE), "__namedArgs");
+ List<Parameter> genParams = new ArrayList<>();
genParams.add(mapParam);
ClassNode cNode = mNode.getDeclaringClass();
- final BlockStatement inner = new BlockStatement();
+ BlockStatement inner = new BlockStatement();
ArgumentListExpression args = new ArgumentListExpression();
- final List<String> propNames = new ArrayList<String>();
+ List<String> propNames = new ArrayList<>();
// first pass, just check for absence of annotations of interest
boolean annoFound = false;
@@ -129,7 +133,7 @@ public class NamedVariantASTTransformation extends AbstractASTTransformation {
createMapVariant(mNode, anno, mapParam, genParams, cNode, inner, args, propNames);
}
- private boolean processImplicitNamedParam(MethodNode mNode, Parameter mapParam, ArgumentListExpression args, List<String> propNames, Parameter fromParam) {
+ private boolean processImplicitNamedParam(final MethodNode mNode, final Parameter mapParam, final ArgumentListExpression args, final List<String> propNames, final Parameter fromParam) {
boolean required = fromParam.hasInitialExpression();
String name = fromParam.getName();
if (hasDuplicates(mNode, propNames, name)) return false;
@@ -142,7 +146,7 @@ public class NamedVariantASTTransformation extends AbstractASTTransformation {
return true;
}
- private boolean processExplicitNamedParam(MethodNode mNode, Parameter mapParam, BlockStatement inner, ArgumentListExpression args, List<String> propNames, Parameter fromParam) {
+ private boolean processExplicitNamedParam(final MethodNode mNode, final Parameter mapParam, final BlockStatement inner, final ArgumentListExpression args, final List<String> propNames, final Parameter fromParam) {
AnnotationNode namedParam = fromParam.getAnnotations(NAMED_PARAM_TYPE).get(0);
boolean required = memberHasValue(namedParam, "required", true);
if (getMemberStringValue(namedParam, "value") == null) {
@@ -153,15 +157,15 @@ public class NamedVariantASTTransformation extends AbstractASTTransformation {
namedParam.addMember("type", classX(fromParam.getType()));
}
if (hasDuplicates(mNode, propNames, name)) return false;
- // TODO check specified type is assignable from declared param type?
- // ClassNode type = getMemberClassValue(namedParam, "type");
+ // TODO: Check specified type is assignable from declared param type?
+ //ClassNode type = getMemberClassValue(namedParam, "type");
if (required) {
if (fromParam.hasInitialExpression()) {
- addError("Error during " + MY_TYPE_NAME + " processing. A required parameter can't have an initial value.", mNode);
+ addError("Error during " + NAMED_VARIANT + " processing. A required parameter can't have an initial value.", mNode);
return false;
}
inner.addStatement(new AssertStatement(boolX(callX(varX(mapParam), "containsKey", args(constX(name)))),
- plusX(new ConstantExpression("Missing required named argument '" + name + "'. Keys found: "), callX(varX(mapParam), "keySet"))));
+ plusX(constX("Missing required named argument '" + name + "'. Keys found: "), callX(varX(mapParam), "keySet"))));
}
args.addExpression(propX(varX(mapParam), name));
mapParam.addAnnotation(namedParam);
@@ -169,62 +173,65 @@ public class NamedVariantASTTransformation extends AbstractASTTransformation {
return true;
}
- private boolean processDelegateParam(MethodNode mNode, Parameter mapParam, ArgumentListExpression args, List<String> propNames, Parameter fromParam) {
+ private boolean processDelegateParam(final MethodNode mNode, final Parameter mapParam, final ArgumentListExpression args, final List<String> propNames, final Parameter fromParam) {
if (isInnerClass(fromParam.getType())) {
if (mNode.isStatic()) {
- addError("Error during " + MY_TYPE_NAME + " processing. Delegate type '" + fromParam.getType().getNameWithoutPackage() + "' is an inner class which is not supported.", mNode);
+ addError("Error during " + NAMED_VARIANT + " processing. Delegate type '" + fromParam.getType().getNameWithoutPackage() + "' is an inner class which is not supported.", mNode);
return false;
}
}
- Set<String> names = new HashSet<String>();
+ Set<String> names = new HashSet<>();
List<PropertyNode> props = getAllProperties(names, fromParam.getType(), true, false, false, true, false, true);
for (String next : names) {
if (hasDuplicates(mNode, propNames, next)) return false;
}
- List<MapEntryExpression> entries = new ArrayList<MapEntryExpression>();
+ List<MapEntryExpression> entries = new ArrayList<>();
for (PropertyNode pNode : props) {
String name = pNode.getName();
- entries.add(new MapEntryExpression(constX(name), propX(varX(mapParam), name)));
+ // create entry [name: __namedArgs.getOrDefault('name', initialValue)]
+ Expression defaultValue = Optional.ofNullable(pNode.getInitialExpression()).orElseGet(() ->
+ isNumberType(pNode.getType()) ? castX(getWrapper(pNode.getType()), constX(0)) : nullX());
+ entries.add(entryX(constX(name), callX(varX(mapParam), "getOrDefault", args(constX(name), defaultValue))));
+ // create annotation @NamedParam(value='name', type=DelegateType)
AnnotationNode namedParam = new AnnotationNode(NAMED_PARAM_TYPE);
namedParam.addMember("value", constX(name));
namedParam.addMember("type", classX(pNode.getType()));
mapParam.addAnnotation(namedParam);
}
- Expression delegateMap = new MapExpression(entries);
+ Expression delegateMap = mapX(entries);
args.addExpression(castX(fromParam.getType(), delegateMap));
return true;
}
- private boolean hasDuplicates(MethodNode mNode, List<String> propNames, String next) {
+ private boolean hasDuplicates(final MethodNode mNode, final List<String> propNames, final String next) {
if (propNames.contains(next)) {
- addError("Error during " + MY_TYPE_NAME + " processing. Duplicate property '" + next + "' found.", mNode);
+ addError("Error during " + NAMED_VARIANT + " processing. Duplicate property '" + next + "' found.", mNode);
return true;
}
propNames.add(next);
return false;
}
- private void createMapVariant(MethodNode mNode, AnnotationNode anno, Parameter mapParam, List<Parameter> genParams, ClassNode cNode, BlockStatement inner, ArgumentListExpression args, List<String> propNames) {
+ private void createMapVariant(final MethodNode mNode, final AnnotationNode anno, final Parameter mapParam, final List<Parameter> genParams, final ClassNode cNode, final BlockStatement inner, final ArgumentListExpression args, final List<String> propNames) {
Parameter namedArgKey = param(STRING_TYPE, "namedArgKey");
inner.addStatement(
new ForStatement(
namedArgKey,
callX(varX(mapParam), "keySet"),
- new AssertStatement(boolX(callX(list2args(propNames), "contains", varX(namedArgKey))),
- plusX(new ConstantExpression("Unrecognized namedArgKey: "), varX(namedArgKey)))
+ new AssertStatement(boolX(callX(list2args(propNames), "contains", varX(namedArgKey))), plusX(constX("Unrecognized namedArgKey: "), varX(namedArgKey)))
));
Parameter[] genParamsArray = genParams.toArray(Parameter.EMPTY_ARRAY);
// TODO account for default params giving multiple signatures
if (cNode.hasMethod(mNode.getName(), genParamsArray)) {
- addError("Error during " + MY_TYPE_NAME + " processing. Class " + cNode.getNameWithoutPackage() +
+ addError("Error during " + NAMED_VARIANT + " processing. Class " + cNode.getNameWithoutPackage() +
" already has a named-arg " + (mNode instanceof ConstructorNode ? "constructor" : "method") +
" of type " + genParams, mNode);
return;
}
- final BlockStatement body = new BlockStatement();
+ BlockStatement body = new BlockStatement();
int modifiers = getVisibility(anno, mNode, mNode.getClass(), mNode.getModifiers());
if (mNode instanceof ConstructorNode) {
body.addStatement(stmt(ctorX(ClassNode.THIS, args)));
diff --git a/src/test/org/codehaus/groovy/transform/NamedVariantTransformTest.groovy b/src/test/org/codehaus/groovy/transform/NamedVariantTransformTest.groovy
index 3455a77..198f951 100644
--- a/src/test/org/codehaus/groovy/transform/NamedVariantTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/NamedVariantTransformTest.groovy
@@ -18,13 +18,18 @@
*/
package org.codehaus.groovy.transform
-import groovy.test.GroovyShellTestCase
+import groovy.transform.CompileStatic
+import org.junit.Test
+
+import static groovy.test.GroovyAssert.assertScript
/**
* Tests for the {@code @NamedVariant} transformation.
*/
-class NamedVariantTransformTest extends GroovyShellTestCase {
+@CompileStatic
+final class NamedVariantTransformTest {
+ @Test
void testNamedParam() {
assertScript '''
import groovy.transform.*
@@ -43,7 +48,7 @@ class NamedVariantTransformTest extends GroovyShellTestCase {
@NamedVariant
String foo(a, @NamedParam String b2, @NamedDelegate Color shade, int c, @NamedParam(required=true) d, @NamedDelegate Animal pet) {
- "$a $b2 $c $d ${pet.type?.toUpperCase()}:$pet.name $shade"
+ "$a $b2 $c $d ${pet.type?.toUpperCase()}:$pet.name $shade"
}
def result = foo(b2: 'b param', g: 12, b: 42, r: 12, 'foo', 42, d:true, type: 'Dog', name: 'Rover')
@@ -51,6 +56,7 @@ class NamedVariantTransformTest extends GroovyShellTestCase {
'''
}
+ @Test
void testNamedParamWithRename() {
assertScript '''
import groovy.transform.*
@@ -69,6 +75,7 @@ class NamedVariantTransformTest extends GroovyShellTestCase {
'''
}
+ @Test
void testNamedParamConstructor() {
assertScript """
import groovy.transform.*
@@ -77,9 +84,9 @@ class NamedVariantTransformTest extends GroovyShellTestCase {
class Color {
@NamedVariant
Color(@NamedParam int r, @NamedParam int g, @NamedParam int b) {
- this.r = r
- this.g = g
- this.b = b
+ this.r = r
+ this.g = g
+ this.b = b
}
private int r, g, b
}
@@ -88,6 +95,7 @@ class NamedVariantTransformTest extends GroovyShellTestCase {
"""
}
+ @Test
void testNamedParamConstructorVisibility() {
assertScript """
import groovy.transform.*
@@ -99,9 +107,9 @@ class NamedVariantTransformTest extends GroovyShellTestCase {
@VisibilityOptions(PUBLIC)
@NamedVariant
private Color(@NamedParam int r, @NamedParam int g, @NamedParam int b) {
- this.r = r
- this.g = g
- this.b = b
+ this.r = r
+ this.g = g
+ this.b = b
}
}
@@ -111,6 +119,7 @@ class NamedVariantTransformTest extends GroovyShellTestCase {
"""
}
+ @Test
void testNamedParamInnerClass() {
assertScript '''
import groovy.transform.*
@@ -145,17 +154,80 @@ class NamedVariantTransformTest extends GroovyShellTestCase {
'''
}
+ @Test
void testGeneratedMethodsSkipped() {
assertScript '''
import groovy.transform.*
import static org.codehaus.groovy.transform.NamedVariantTransformTest.*
@NamedVariant
- def baz(@NamedDelegate Storm st, @NamedDelegate Switch sw) { st.front + sw.back }
+ def baz(@NamedDelegate Storm storm_, @NamedDelegate Switch switch_) { storm_.front + switch_.back }
assert baz(front: 'Hello', back: 'World') == 'HelloWorld'
'''
}
+ @Test // GROOVY-9183
+ void testNamedDelegateWithPrimitiveValues() {
+ assertScript '''
+ import groovy.transform.*
+
+ class Color {
+ int r, g, b
+ }
+
+ @NamedVariant
+ Color makeColor(@NamedDelegate Color color) {
+ color
+ }
+
+ def color = makeColor(r: 128, g: 128)
+ assert color.r == 128
+ assert color.g == 128
+ assert color.b == 0
+ '''
+ }
+
+ @Test // GROOVY-9183
+ void testNamedDelegateWithPropertyDefaults() {
+ assertScript '''
+ import groovy.transform.*
+
+ class RowMapper {
+ final Settings settings
+
+ @NamedVariant
+ RowMapper(Settings settings) {
+ this.settings = settings
+ }
+
+ @NamedVariant
+ static RowMapper parse(@NamedDelegate Settings settings, Reader reader) {
+ // settings missing the initializer values from properties not passed as named arguments
+ new RowMapper(settings).parseImpl(reader)
+ }
+
+ private RowMapper parseImpl(Reader source) {
+ // do work here
+ return this
+ }
+
+ @Immutable
+ static class Settings {
+ String separator = ','
+ Boolean headers = true
+ Integer headersRow = 0
+ Integer firstDataRow = 1
+ }
+ }
+
+ def mapper = RowMapper.parse(separator: '\t', new StringReader(''))
+
+ assert mapper.settings.headers == true
+ assert mapper.settings.headersRow == 0
+ assert mapper.settings.firstDataRow == 1
+ '''
+ }
+
static class Storm { String front }
static class Switch { String back }
}