Repository: groovy Updated Branches: refs/heads/master 55fd0ddaa -> f364a0c4b
GROOVY-8887: Support multi-assignment of tuples in STC(closes #824) Project: http://git-wip-us.apache.org/repos/asf/groovy/repo Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/f364a0c4 Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/f364a0c4 Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/f364a0c4 Branch: refs/heads/master Commit: f364a0c4bff71a3f9eb688285ef58c314a61ae8e Parents: 55fd0dd Author: Daniel Sun <sun...@apache.org> Authored: Sun Nov 18 00:36:19 2018 +0800 Committer: Daniel Sun <sun...@apache.org> Committed: Sun Nov 18 00:36:34 2018 +0800 ---------------------------------------------------------------------- .../org/codehaus/groovy/ast/ClassHelper.java | 25 +++++++++ .../stc/StaticTypeCheckingVisitor.java | 51 ++++++++++++++++++- src/test/groovy/bugs/Groovy8887.groovy | 53 ++++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/groovy/blob/f364a0c4/src/main/java/org/codehaus/groovy/ast/ClassHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java index 2e034fb..0bdf585 100644 --- a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java +++ b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java @@ -28,6 +28,24 @@ import groovy.lang.MetaClass; import groovy.lang.Range; import groovy.lang.Reference; import groovy.lang.Script; +import groovy.lang.Tuple; +import groovy.lang.Tuple0; +import groovy.lang.Tuple1; +import groovy.lang.Tuple10; +import groovy.lang.Tuple11; +import groovy.lang.Tuple12; +import groovy.lang.Tuple13; +import groovy.lang.Tuple14; +import groovy.lang.Tuple15; +import groovy.lang.Tuple16; +import groovy.lang.Tuple2; +import groovy.lang.Tuple3; +import groovy.lang.Tuple4; +import groovy.lang.Tuple5; +import groovy.lang.Tuple6; +import groovy.lang.Tuple7; +import groovy.lang.Tuple8; +import groovy.lang.Tuple9; import org.apache.groovy.util.Maps; import org.codehaus.groovy.classgen.asm.util.TypeUtil; import org.codehaus.groovy.runtime.GeneratedClosure; @@ -69,6 +87,12 @@ public class ClassHelper { Iterator.class, GeneratedClosure.class, GeneratedLambda.class, GroovyObjectSupport.class }; + public static final Class[] TUPLE_CLASSES = new Class[] { + Tuple0.class, Tuple1.class, Tuple2.class, Tuple3.class, Tuple4.class, Tuple5.class, Tuple6.class, + Tuple7.class, Tuple8.class, Tuple9.class, Tuple10.class, Tuple11.class, Tuple12.class, Tuple13.class, + Tuple14.class, Tuple15.class, Tuple16.class + }; + private static final String[] primitiveClassNames = new String[]{ "", "boolean", "char", "byte", "short", "int", "long", "double", "float", "void" @@ -79,6 +103,7 @@ public class ClassHelper { VOID_TYPE = makeCached(Void.TYPE), CLOSURE_TYPE = makeCached(Closure.class), GSTRING_TYPE = makeCached(GString.class), LIST_TYPE = makeWithoutCaching(List.class), + TUPLE_TYPE = makeWithoutCaching(Tuple.class), MAP_TYPE = makeWithoutCaching(Map.class), RANGE_TYPE = makeCached(Range.class), PATTERN_TYPE = makeCached(Pattern.class), STRING_TYPE = makeCached(String.class), SCRIPT_TYPE = makeCached(Script.class), REFERENCE_TYPE = makeWithoutCaching(Reference.class), http://git-wip-us.apache.org/repos/asf/groovy/blob/f364a0c4/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java ---------------------------------------------------------------------- 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 72ea3bc..f9b0a2a 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -153,6 +153,7 @@ import static org.codehaus.groovy.ast.ClassHelper.PATTERN_TYPE; import static org.codehaus.groovy.ast.ClassHelper.RANGE_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; @@ -1085,11 +1086,14 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { // multiple assignment check if (!(leftExpression instanceof TupleExpression)) return true; - if (!(rightExpression instanceof ListExpression)) { + Expression transformedRightExpression = transformRightExpressionToSupportMultipleAssignment(rightExpression); + if (null == transformedRightExpression) { addStaticTypeError("Multiple assignments without list expressions on the right hand side are unsupported in static type checking mode", rightExpression); return false; } + rightExpression = transformedRightExpression; + TupleExpression tuple = (TupleExpression) leftExpression; ListExpression list = (ListExpression) rightExpression; List<Expression> listExpressions = list.getExpressions(); @@ -1114,6 +1118,51 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { return true; } + private Expression transformRightExpressionToSupportMultipleAssignment(Expression rightExpression) { + if (rightExpression instanceof ListExpression) { + return rightExpression; + } + + ClassNode cn = null; + if (rightExpression instanceof MethodCallExpression || rightExpression instanceof VariableExpression) { + cn = rightExpression.getType(); + } + + if (null == cn) { + return null; + } + + Expression listExpression = transformToListExpression(rightExpression, cn); + if (listExpression != null) return listExpression; + + return null; + } + + private Expression transformToListExpression(Expression expression, ClassNode cn) { + if (null != cn && cn.isDerivedFrom(ClassHelper.TUPLE_TYPE)) { // just for performance to check + for (int i = 0, n = TUPLE_CLASSES.length; i < n; i++) { + 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++) { + // the index of element in tuple starts with 1 + MethodCallExpression mce = new MethodCallExpression(expression, "v" + (j + 1), ArgumentListExpression.EMPTY_ARGUMENTS); + ClassNode elementType = null != genericsTypes ? genericsTypes[j].getType() : ClassHelper.OBJECT_TYPE; + mce.setType(elementType); + storeType(mce, elementType); + listExpression.addExpression(mce); + } + + listExpression.setSourcePosition(expression); + + return listExpression; + } + } + } + return null; + } + private static ClassNode adjustTypeForSpreading(ClassNode inferredRightExpressionType, Expression leftExpression) { // imagine we have: list*.foo = 100 // then the assignment must be checked against [100], not 100 http://git-wip-us.apache.org/repos/asf/groovy/blob/f364a0c4/src/test/groovy/bugs/Groovy8887.groovy ---------------------------------------------------------------------- diff --git a/src/test/groovy/bugs/Groovy8887.groovy b/src/test/groovy/bugs/Groovy8887.groovy new file mode 100644 index 0000000..2c4e80c --- /dev/null +++ b/src/test/groovy/bugs/Groovy8887.groovy @@ -0,0 +1,53 @@ +/* + * 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 + +class Groovy8887 extends GroovyTestCase { + void testMultiAssignment() { + assertScript ''' + @groovy.transform.CompileStatic + class TtcTuple { + 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 + } + + Tuple2<String, Integer> findPersonInfo() { + Tuple2<String, Integer> t = new Tuple2<>('Daniel', 35) + + return t + } + } + + def tt = new TtcTuple() + tt.multiAssignedByMethodCall() + tt.multiAssignedByVariableAccess() + ''' + } +}