This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new ce4d4bb GROOVY-10063: STC: multi-assign Tuple[1-16] for static call
or prop expr
ce4d4bb is described below
commit ce4d4bbc7d932f81d9db6ccb7fe9957e5ffa4e0d
Author: Eric Milles <[email protected]>
AuthorDate: Wed Apr 28 10:36:18 2021 -0500
GROOVY-10063: STC: multi-assign Tuple[1-16] for static call or prop expr
---
.../transform/stc/StaticTypeCheckingVisitor.java | 104 ++++++++-------------
src/test/groovy/bugs/Groovy8223.groovy | 48 ----------
src/test/groovy/bugs/Groovy8887.groovy | 69 --------------
.../groovy/transform/stc/STCAssignmentTest.groovy | 102 +++++++++++++++++++-
4 files changed, 140 insertions(+), 183 deletions(-)
diff --git
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
index ff6006c..f520295 100644
---
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -138,6 +138,7 @@ import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.IntStream;
+import static java.util.stream.Collectors.toList;
import static org.apache.groovy.util.BeanUtils.capitalize;
import static org.apache.groovy.util.BeanUtils.decapitalize;
import static org.codehaus.groovy.ast.ClassHelper.AUTOCLOSEABLE_TYPE;
@@ -165,7 +166,6 @@ import static
org.codehaus.groovy.ast.ClassHelper.RANGE_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.SET_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.Short_TYPE;
-import static org.codehaus.groovy.ast.ClassHelper.TUPLE_CLASSES;
import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.byte_TYPE;
@@ -331,12 +331,13 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
protected static final ClassNode MAP_ENTRY_TYPE =
ClassHelper.make(Map.Entry.class);
protected static final ClassNode ITERABLE_TYPE = ClassHelper.ITERABLE_TYPE;
- public static final Statement GENERATED_EMPTY_STATEMENT =
EmptyStatement.INSTANCE;
+ private static List<ClassNode> TUPLE_TYPES =
Arrays.stream(ClassHelper.TUPLE_CLASSES).map(ClassHelper::makeWithoutCaching).collect(toList());
- // Cache closure call methods
- public static final MethodNode CLOSURE_CALL_NO_ARG =
CLOSURE_TYPE.getDeclaredMethod("call", Parameter.EMPTY_ARRAY);
+ public static final MethodNode CLOSURE_CALL_NO_ARG =
CLOSURE_TYPE.getDeclaredMethod("call", Parameter.EMPTY_ARRAY);
public static final MethodNode CLOSURE_CALL_ONE_ARG =
CLOSURE_TYPE.getDeclaredMethod("call", new Parameter[]{new
Parameter(OBJECT_TYPE, "arg")});
- public static final MethodNode CLOSURE_CALL_VARGS =
CLOSURE_TYPE.getDeclaredMethod("call", new Parameter[]{new
Parameter(OBJECT_TYPE.makeArray(), "args")});
+ public static final MethodNode CLOSURE_CALL_VARGS =
CLOSURE_TYPE.getDeclaredMethod("call", new Parameter[]{new
Parameter(OBJECT_TYPE.makeArray(), "args")});
+
+ public static final Statement GENERATED_EMPTY_STATEMENT =
EmptyStatement.INSTANCE;
protected final ReturnAdder.ReturnStatementListener returnListener = new
ReturnAdder.ReturnStatementListener() {
@Override
@@ -1105,77 +1106,48 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
}
private boolean typeCheckMultipleAssignmentAndContinue(final Expression
leftExpression, Expression rightExpression) {
- // multiple assignment check
- if (!(leftExpression instanceof TupleExpression)) return true;
-
- Expression transformedRightExpression =
transformRightExpressionToSupportMultipleAssignment(rightExpression);
- if (transformedRightExpression == null) {
- addStaticTypeError("Multiple assignments without list expressions
on the right hand side are unsupported in static type checking mode",
rightExpression);
- return false;
- }
+ if (rightExpression instanceof VariableExpression || rightExpression
instanceof PropertyExpression || rightExpression instanceof MethodCall) {
+ ClassNode inferredType =
Optional.ofNullable(getType(rightExpression)).orElseGet(rightExpression::getType);
+ GenericsType[] genericsTypes = inferredType.getGenericsTypes();
+ ListExpression listExpression = new ListExpression();
+ listExpression.setSourcePosition(rightExpression);
- rightExpression = transformedRightExpression;
-
- TupleExpression tuple = (TupleExpression) leftExpression;
- ListExpression list = (ListExpression) rightExpression;
- List<Expression> listExpressions = list.getExpressions();
- List<Expression> tupleExpressions = tuple.getExpressions();
- if (listExpressions.size() < tupleExpressions.size()) {
- addStaticTypeError("Incorrect number of values. Expected:" +
tupleExpressions.size() + " Was:" + listExpressions.size(), list);
- return false;
- }
- for (int i = 0, tupleExpressionsSize = tupleExpressions.size(); i <
tupleExpressionsSize; i++) {
- Expression tupleExpression = tupleExpressions.get(i);
- Expression listExpression = listExpressions.get(i);
- ClassNode elemType = getType(listExpression);
- ClassNode tupleType = getType(tupleExpression);
- if (!isAssignableTo(elemType, tupleType)) {
- addStaticTypeError("Cannot assign value of type " +
prettyPrintType(elemType) + " to variable of type " +
prettyPrintType(tupleType), rightExpression);
- return false; // avoids too many errors
- } else {
- storeType(tupleExpression, elemType);
+ // convert Tuple[1-16] bearing expressions to mock list for
checking
+ for (int n = TUPLE_TYPES.indexOf(inferredType), i = 0; i < n; i +=
1) {
+ ClassNode type = (genericsTypes != null ?
genericsTypes[i].getType() : OBJECT_TYPE);
+ listExpression.addExpression(varX("v" + (i + 1), type));
+ }
+ if (!listExpression.getExpressions().isEmpty()) {
+ rightExpression = listExpression;
}
}
- return true;
- }
-
- private Expression
transformRightExpressionToSupportMultipleAssignment(final Expression
rightExpression) {
- if (rightExpression instanceof ListExpression) {
- return rightExpression;
+ if (!(rightExpression instanceof ListExpression)) {
+ addStaticTypeError("Multiple assignments without list or tuple on
the right-hand side are unsupported in static type checking mode",
rightExpression);
+ return false;
}
- ClassNode cn = null;
- if (rightExpression instanceof MethodCallExpression || rightExpression
instanceof ConstructorCallExpression || rightExpression instanceof
VariableExpression) {
- ClassNode inferredType = getType(rightExpression);
- cn = (inferredType == null ? rightExpression.getType() :
inferredType);
- }
+ TupleExpression tuple = (TupleExpression) leftExpression;
+ ListExpression values = (ListExpression) rightExpression;
+ List<Expression> tupleExpressions = tuple.getExpressions();
+ List<Expression> valueExpressions = values.getExpressions();
- if (cn == null) {
- return null;
+ if (tupleExpressions.size() > valueExpressions.size()) {
+ addStaticTypeError("Incorrect number of values. Expected:" +
tupleExpressions.size() + " Was:" + valueExpressions.size(), values);
+ return false;
}
- for (int i = 0, n = TUPLE_CLASSES.length; i < n; i += 1) {
- Class<?> tcn = TUPLE_CLASSES[i];
- if (tcn.equals(cn.getTypeClass())) {
- ListExpression listExpression = new ListExpression();
- GenericsType[] genericsTypes = cn.getGenericsTypes();
- for (int j = 0; j < i; j += 1) {
- // the index of element in tuple starts with 1
- MethodCallExpression mce = new
MethodCallExpression(rightExpression, "getV" + (j + 1),
ArgumentListExpression.EMPTY_ARGUMENTS);
- ClassNode elementType = (genericsTypes != null ?
genericsTypes[j].getType() : OBJECT_TYPE);
- mce.setType(elementType);
- storeType(mce, elementType);
- listExpression.addExpression(mce);
- }
-
- listExpression.setSourcePosition(rightExpression);
-
- return listExpression;
+ for (int i = 0, n = tupleExpressions.size(); i < n; i += 1) {
+ ClassNode valueType = getType(valueExpressions.get(i));
+ ClassNode targetType = getType(tupleExpressions.get(i));
+ if (!isAssignableTo(valueType, targetType)) {
+ addStaticTypeError("Cannot assign value of type " +
prettyPrintType(valueType) + " to variable of type " +
prettyPrintType(targetType), rightExpression);
+ return false;
}
+ storeType(tupleExpressions.get(i), valueType);
}
- return null;
+ return true;
}
private ClassNode adjustTypeForSpreading(final ClassNode
inferredRightExpressionType, final Expression leftExpression) {
@@ -1299,7 +1271,9 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
}
protected void typeCheckAssignment(final BinaryExpression
assignmentExpression, final Expression leftExpression, final ClassNode
leftExpressionType, final Expression rightExpression, final ClassNode
rightExpressionType) {
- if (!typeCheckMultipleAssignmentAndContinue(leftExpression,
rightExpression)) return;
+ if (leftExpression instanceof TupleExpression) {
+ if (!typeCheckMultipleAssignmentAndContinue(leftExpression,
rightExpression)) return;
+ }
// TODO: need errors for write-only too!
if (addedReadOnlyPropertyError(leftExpression)) return;
diff --git a/src/test/groovy/bugs/Groovy8223.groovy
b/src/test/groovy/bugs/Groovy8223.groovy
deleted file mode 100644
index efcacfd..0000000
--- a/src/test/groovy/bugs/Groovy8223.groovy
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.bugs
-
-import groovy.transform.CompileStatic
-import org.junit.Test
-
-import static groovy.test.GroovyAssert.assertScript
-
-@CompileStatic
-final class Groovy8223 {
- @Test
- void testMultiAssignment() {
- assertScript '''\
- @groovy.transform.CompileStatic
- class Blah {
- static Tuple2<String, Integer> dostuff() {
- new Tuple2<>("string", 55)
- }
- }
-
- @groovy.transform.CompileStatic
- def x() {
- def (String mystr, Integer myint) = Blah.dostuff()
- assert 'string' == mystr
- assert 55 == myint
- }
-
- x()
- '''
- }
-}
diff --git a/src/test/groovy/bugs/Groovy8887.groovy
b/src/test/groovy/bugs/Groovy8887.groovy
deleted file mode 100644
index b56ff9d..0000000
--- a/src/test/groovy/bugs/Groovy8887.groovy
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.bugs
-
-import groovy.test.GroovyTestCase
-
-class Groovy8887 extends GroovyTestCase {
- void testMultiAssignment() {
- assertScript '''
- @groovy.transform.CompileStatic
- class StcTuple {
- void multiAssignedByMethodCall() {
- def (String name, Integer age) = findPersonInfo()
-
- assert 'Daniel' == name
- assert 35 == age
- }
-
- void multiAssignedByVariableAccess() {
- Tuple2<String, Integer> personInfo = findPersonInfo()
- def (String name, Integer age) = personInfo
-
- assert 'Daniel' == name
- assert 35 == age
- }
-
- void multiAssignedByFactory() {
- def (String name, Integer age) = Tuple.tuple('Daniel', 35)
- assert 'Daniel' == name
- assert 35 == age
- }
-
- void multiAssignedByConstructor() {
- def (String name, Integer age) = new Tuple2<String,
Integer>('Daniel', 35)
- assert 'Daniel' == name
- assert 35 == age
- }
-
- Tuple2<String, Integer> findPersonInfo() {
- Tuple2<String, Integer> t = new Tuple2<>('Daniel', 35)
-
- return t
- }
- }
-
- def st = new StcTuple()
- st.multiAssignedByMethodCall()
- st.multiAssignedByVariableAccess()
- st.multiAssignedByFactory()
- st.multiAssignedByConstructor()
- '''
- }
-}
diff --git a/src/test/groovy/transform/stc/STCAssignmentTest.groovy
b/src/test/groovy/transform/stc/STCAssignmentTest.groovy
index b663eca..28307ab 100644
--- a/src/test/groovy/transform/stc/STCAssignmentTest.groovy
+++ b/src/test/groovy/transform/stc/STCAssignmentTest.groovy
@@ -359,7 +359,107 @@ class STCAssignmentTest extends
StaticTypeCheckingTestCase {
shouldFailWithMessages '''
def list = [1,2,3]
def (x,y) = list
- ''', 'Multiple assignments without list expressions on the right hand
side are unsupported in static type checking mode'
+ ''', 'Multiple assignments without list or tuple on the right-hand
side are unsupported in static type checking mode'
+ }
+
+ // GROOVY-8223, GROOVY-8887, GROOVY-10063
+ void testMultipleAssignmentFromTupleTypes() {
+ assertScript '''
+ def (String string) = Tuple.tuple('answer')
+ assert string == 'answer'
+ '''
+
+ assertScript '''
+ def (String string, Integer number) = Tuple.tuple('answer', 42)
+ assert string == 'answer'
+ assert number == 42
+ '''
+
+ shouldFailWithMessages '''
+ def (String string, Integer number) = Tuple.tuple('answer', '42')
+ ''',
+ 'Cannot assign value of type java.lang.String to variable of type
java.lang.Integer'
+
+ assertScript '''
+ def (int a, int b, int c, int d, int e, int f, int g, int h, int
i, int j, int k, int l, int m, int n, int o, int p) = Tuple.tuple(1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+ assert a == 1
+ assert b == 2
+ assert c == 3
+ assert d == 4
+ assert e == 5
+ assert f == 6
+ assert g == 7
+ assert h == 8
+ assert i == 9
+ assert j == 10
+ assert k == 11
+ assert l == 12
+ assert m == 13
+ assert n == 14
+ assert o == 15
+ assert p == 16
+ '''
+
+ assertScript '''
+ def (String string, Integer number) = new Tuple2<String,
Integer>('answer', 42)
+ assert string == 'answer'
+ assert number == 42
+ '''
+
+ assertScript '''
+ Tuple2<String, Integer> m() {
+ new Tuple2<>('answer', 42)
+ }
+
+ def (String string, Integer number) = m()
+ assert string == 'answer'
+ assert number == 42
+ '''
+
+ assertScript '''
+ Tuple2<String, Integer> m() {
+ new Tuple2<>('answer', 42)
+ }
+
+ def tuple = m()
+ def (String string, Integer number) = tuple
+ assert string == 'answer'
+ assert number == 42
+ '''
+
+ assertScript '''
+ static Tuple2<String, Integer> m() {
+ new Tuple2<>('answer', 42)
+ }
+
+ def (String string, Integer number) = m()
+ assert string == 'answer'
+ assert number == 42
+ '''
+
+ assertScript '''
+ class C {
+ static Tuple2<String, Integer> m() {
+ new Tuple2<>('answer', 42)
+ }
+ }
+
+ def (String string, Integer number) = C.m()
+ assert string == 'answer'
+ assert number == 42
+ '''
+
+ assertScript '''
+ class C {
+ Tuple2<String, Integer> getM() {
+ new Tuple2<>('answer', 42)
+ }
+ }
+
+ def (String string, Integer number) = new C().m
+ assert string == 'answer'
+ assert number == 42
+ '''
}
void testAssignmentToInterface() {