You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2020/08/10 23:34:45 UTC
[groovy] branch GROOVY_3_0_X updated: Support coerce for
@NamedVariant
This is an automated email from the ASF dual-hosted git repository.
sunlan pushed a commit to branch GROOVY_3_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_3_0_X by this push:
new ffd4c98 Support coerce for @NamedVariant
ffd4c98 is described below
commit ffd4c98826a0d706652ed696c2d72bb95193c863
Author: MoonFruit <dk...@gmail.com>
AuthorDate: Thu Aug 6 17:27:04 2020 +0800
Support coerce for @NamedVariant
(cherry picked from commit dfd38f36b900df66b9135b6658492e5ab16be2ee)
---
src/main/java/groovy/transform/NamedVariant.java | 5 ++
.../codehaus/groovy/ast/tools/GeneralUtils.java | 4 +
.../transform/NamedVariantASTTransformation.java | 32 +++++---
src/test/groovy/transform/NamedVariantTest.groovy | 86 ++++++++++++++++++++++
4 files changed, 116 insertions(+), 11 deletions(-)
diff --git a/src/main/java/groovy/transform/NamedVariant.java b/src/main/java/groovy/transform/NamedVariant.java
index 04faeb5..8cd9abb 100644
--- a/src/main/java/groovy/transform/NamedVariant.java
+++ b/src/main/java/groovy/transform/NamedVariant.java
@@ -124,4 +124,9 @@ public @interface NamedVariant {
* @since 2.5.3
*/
boolean autoDelegate() default false;
+
+ /**
+ * If true, will use {@code as} to convert map parameter to required class
+ */
+ boolean coerce() default false;
}
diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
index 833e40c..4d1c417 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
@@ -119,6 +119,10 @@ public class GeneralUtils {
return args(Arrays.stream(names).map(GeneralUtils::varX).toArray(Expression[]::new));
}
+ public static CastExpression asX(final ClassNode type, final Expression expression) {
+ return CastExpression.asExpression(type, expression);
+ }
+
public static Statement assignS(final Expression target, final Expression value) {
return stmt(assignX(target, value));
}
diff --git a/src/main/java/org/codehaus/groovy/transform/NamedVariantASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/NamedVariantASTTransformation.java
index 419dada..6ce13c5 100644
--- a/src/main/java/org/codehaus/groovy/transform/NamedVariantASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/NamedVariantASTTransformation.java
@@ -55,6 +55,7 @@ import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE;
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.asX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
@@ -95,6 +96,7 @@ public class NamedVariantASTTransformation extends AbstractASTTransformation {
}
boolean autoDelegate = memberHasValue(anno, "autoDelegate", true);
+ boolean coerce = memberHasValue(anno, "coerce", true);
Parameter mapParam = param(GenericsUtils.nonGeneric(MAP_TYPE), "__namedArgs");
List<Parameter> genParams = new ArrayList<>();
genParams.add(mapParam);
@@ -113,17 +115,17 @@ public class NamedVariantASTTransformation extends AbstractASTTransformation {
if (!annoFound && autoDelegate) {
// assume the first param is the delegate by default
- processDelegateParam(mNode, mapParam, args, propNames, fromParams[0]);
+ processDelegateParam(mNode, mapParam, args, propNames, fromParams[0], coerce);
} else {
for (Parameter fromParam : fromParams) {
if (!annoFound) {
- if (!processImplicitNamedParam(mNode, mapParam, args, propNames, fromParam)) return;
+ if (!processImplicitNamedParam(mNode, mapParam, args, propNames, fromParam, coerce)) return;
} else if (AnnotatedNodeUtils.hasAnnotation(fromParam, NAMED_PARAM_TYPE)) {
- if (!processExplicitNamedParam(mNode, mapParam, inner, args, propNames, fromParam)) return;
+ if (!processExplicitNamedParam(mNode, mapParam, inner, args, propNames, fromParam, coerce)) return;
} else if (AnnotatedNodeUtils.hasAnnotation(fromParam, NAMED_DELEGATE_TYPE)) {
- if (!processDelegateParam(mNode, mapParam, args, propNames, fromParam)) return;
+ if (!processDelegateParam(mNode, mapParam, args, propNames, fromParam, coerce)) return;
} else {
- args.addExpression(varX(fromParam));
+ args.addExpression(asType(varX(fromParam), fromParam.getType(), coerce));
if (hasDuplicates(mNode, propNames, fromParam.getName())) return;
genParams.add(fromParam);
}
@@ -132,7 +134,7 @@ public class NamedVariantASTTransformation extends AbstractASTTransformation {
createMapVariant(mNode, anno, mapParam, genParams, cNode, inner, args, propNames);
}
- private boolean processImplicitNamedParam(final MethodNode mNode, final Parameter mapParam, final ArgumentListExpression args, final List<String> propNames, final Parameter fromParam) {
+ private boolean processImplicitNamedParam(final MethodNode mNode, final Parameter mapParam, final ArgumentListExpression args, final List<String> propNames, final Parameter fromParam, boolean coerce) {
boolean required = fromParam.hasInitialExpression();
String name = fromParam.getName();
if (hasDuplicates(mNode, propNames, name)) return false;
@@ -141,11 +143,11 @@ public class NamedVariantASTTransformation extends AbstractASTTransformation {
namedParam.addMember("type", classX(fromParam.getType()));
namedParam.addMember("required", constX(required, true));
mapParam.addAnnotation(namedParam);
- args.addExpression(propX(varX(mapParam), name));
+ args.addExpression(asType(propX(varX(mapParam), name), fromParam.getType(), coerce));
return true;
}
- private boolean processExplicitNamedParam(final MethodNode mNode, final Parameter mapParam, final BlockStatement inner, final ArgumentListExpression args, final List<String> propNames, final Parameter fromParam) {
+ private boolean processExplicitNamedParam(final MethodNode mNode, final Parameter mapParam, final BlockStatement inner, final ArgumentListExpression args, final List<String> propNames, final Parameter fromParam, boolean coerce) {
AnnotationNode namedParam = fromParam.getAnnotations(NAMED_PARAM_TYPE).get(0);
boolean required = memberHasValue(namedParam, "required", true);
if (getMemberStringValue(namedParam, "value") == null) {
@@ -166,13 +168,13 @@ public class NamedVariantASTTransformation extends AbstractASTTransformation {
inner.addStatement(new AssertStatement(boolX(callX(varX(mapParam), "containsKey", args(constX(name)))),
plusX(constX("Missing required named argument '" + name + "'. Keys found: "), callX(varX(mapParam), "keySet"))));
}
- args.addExpression(propX(varX(mapParam), name));
+ args.addExpression(asType(propX(varX(mapParam), name), fromParam.getType(), coerce));
mapParam.addAnnotation(namedParam);
fromParam.getAnnotations().remove(namedParam);
return true;
}
- private boolean processDelegateParam(final MethodNode mNode, final Parameter mapParam, final ArgumentListExpression args, final List<String> propNames, final Parameter fromParam) {
+ private boolean processDelegateParam(final MethodNode mNode, final Parameter mapParam, final ArgumentListExpression args, final List<String> propNames, final Parameter fromParam, boolean coerce) {
if (isInnerClass(fromParam.getType())) {
if (mNode.isStatic()) {
addError("Error during " + NAMED_VARIANT + " processing. Delegate type '" + fromParam.getType().getNameWithoutPackage() + "' is an inner class which is not supported.", mNode);
@@ -190,7 +192,7 @@ public class NamedVariantASTTransformation extends AbstractASTTransformation {
String name = pNode.getName();
// create entry [name: __namedArgs.getOrDefault('name', initialValue)]
Expression defaultValue = Optional.ofNullable(pNode.getInitialExpression()).orElseGet(() -> getDefaultExpression(pNode.getType()));
- entries.add(entryX(constX(name), callX(varX(mapParam), "getOrDefault", args(constX(name), defaultValue))));
+ entries.add(entryX(constX(name), asType(callX(varX(mapParam), "getOrDefault", args(constX(name), defaultValue)), pNode.getType(), coerce)));
// create annotation @NamedParam(value='name', type=DelegateType)
AnnotationNode namedParam = new AnnotationNode(NAMED_PARAM_TYPE);
namedParam.addMember("value", constX(name));
@@ -257,4 +259,12 @@ public class NamedVariantASTTransformation extends AbstractASTTransformation {
);
}
}
+
+ private Expression asType(Expression expression, ClassNode classNode, boolean coerce) {
+ if (coerce) {
+ return asX(classNode, expression);
+ } else {
+ return expression;
+ }
+ }
}
diff --git a/src/test/groovy/transform/NamedVariantTest.groovy b/src/test/groovy/transform/NamedVariantTest.groovy
new file mode 100644
index 0000000..3696ec0
--- /dev/null
+++ b/src/test/groovy/transform/NamedVariantTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.transform
+
+import java.lang.reflect.Modifier
+import groovy.test.GroovyTestCase
+
+/**
+ * Unit tests for the NamedVariant annotation
+ */
+class NamedVariantTest extends GroovyTestCase {
+ void testMethod() {
+ def tester = new GroovyClassLoader().parseClass(
+ '''class MyClass {
+ | @groovy.transform.NamedVariant
+ | void run(int number) {
+ | }
+ |}'''.stripMargin())
+ // Should have such method `void run(Map)`
+ def method = tester.getDeclaredMethod("run", Map)
+ assert method
+ assert Modifier.isPublic(method.modifiers)
+ assert method.returnType == void.class
+ }
+
+ void testMethodCall() {
+ def tester = new GroovyClassLoader().parseClass(
+ '''class MyClass {
+ | @groovy.transform.NamedVariant
+ | int run(int number) {
+ | number
+ | }
+ |}'''.stripMargin()).getConstructor().newInstance()
+
+ assert tester.run(number: 123) == 123
+ try {
+ tester.run(number: "123")
+ } catch (MissingMethodException ignored) {
+ return
+ }
+ fail("Should have thrown MissingMethodException")
+ }
+
+ void testCoerceMethodCall() {
+ def tester = new GroovyClassLoader().parseClass(
+ '''class MyClass {
+ | @groovy.transform.NamedVariant(coerce = true)
+ | int run(int number) {
+ | number
+ | }
+ |}'''.stripMargin()).getConstructor().newInstance()
+
+ assert tester.run(number: 123) == 123
+ assert tester.run(number: "123") == 123
+ }
+
+ void testStaticCoerceMethodCall() {
+ def tester = new GroovyClassLoader().parseClass(
+ '''@groovy.transform.CompileStatic
+ |class MyClass {
+ | @groovy.transform.NamedVariant(coerce = true)
+ | int run(int number) {
+ | number
+ | }
+ |}'''.stripMargin()).getConstructor().newInstance()
+
+ assert tester.run(number: 123) == 123
+ assert tester.run(number: "123") == 123
+ }
+}