This is an automated email from the ASF dual-hosted git repository. emilles pushed a commit to branch GROOVY_4_0_X in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 5748ecb8fcf9ba158e9872e3b1982e621fcc1d45 Author: Eric Milles <[email protected]> AuthorDate: Tue Feb 8 19:01:53 2022 -0600 GROOVY-8133: STC: support spread-dot for any iterable type, incl. Stream --- .../transform/stc/StaticTypeCheckingVisitor.java | 50 +++++++++++++--------- .../groovy/transform/stc/MethodCallsSTCTest.groovy | 29 ++++++++++++- .../stc/PrecompiledExtensionNotExtendingDSL.groovy | 31 +++++++------- .../stc/TypeCheckingExtensionsTest.groovy | 8 ++-- 4 files changed, 77 insertions(+), 41 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 e42bf45..b121c4b 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -218,7 +218,6 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.elvisX; import static org.codehaus.groovy.ast.tools.GeneralUtils.getGetterName; import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName; import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements; -import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX; import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; import static org.codehaus.groovy.ast.tools.GeneralUtils.thisPropX; import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; @@ -2481,9 +2480,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { } private static ClassNode wrapClosureType(final ClassNode returnType) { - ClassNode inferredType = CLOSURE_TYPE.getPlainNodeReference(); - inferredType.setGenericsTypes(new GenericsType[]{new GenericsType(wrapTypeIfNecessary(returnType))}); - return inferredType; + return makeClassSafe0(CLOSURE_TYPE, wrapTypeIfNecessary(returnType).asGenericsType()); } protected DelegationMetadata getDelegationMetadata(final ClosureExpression expression) { @@ -3307,16 +3304,16 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { if (objectExpression instanceof ConstructorCallExpression) { // GROOVY-10228 inferDiamondType((ConstructorCallExpression) objectExpression, receiver.getPlainNodeReference()); } - if (call.isSpreadSafe()) { // make sure receiver is array or collection then check element type - if (!receiver.isArray() && !implementsInterfaceOrIsSubclassOf(receiver, Collection_TYPE)) { - addStaticTypeError("Spread operator can only be used on collection types", objectExpression); + if (call.isSpreadSafe()) { + ClassNode componentType = inferComponentType(receiver, null); + if (componentType == null) { + addStaticTypeError("Spread-dot operator can only be used on iterable types", objectExpression); } else { - ClassNode componentType = inferComponentType(receiver, int_TYPE); - MethodCallExpression subcall = callX(castX(componentType, EmptyExpression.INSTANCE), name, call.getArguments()); + MethodCallExpression subcall = callX(varX("item", componentType), name, call.getArguments()); subcall.setLineNumber(call.getLineNumber()); subcall.setColumnNumber(call.getColumnNumber()); subcall.setImplicitThis(call.isImplicitThis()); visitMethodCallExpression(subcall); - // the inferred type here should be a list of what the subcall returns + // inferred type should be a list of what sub-call returns storeType(call, extension.buildListType(getType(subcall))); storeTargetMethod(call, subcall.getNodeMetaData(DIRECT_METHOD_CALL_TARGET)); } @@ -4546,23 +4543,36 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { return Number_TYPE; } - protected ClassNode inferComponentType(final ClassNode containerType, final ClassNode indexType) { - ClassNode componentType = containerType.getComponentType(); + protected ClassNode inferComponentType(final ClassNode receiverType, final ClassNode subscriptType) { + ClassNode componentType = receiverType.getComponentType(); if (componentType == null) { - // GROOVY-5521 - // try to identify a getAt method + MethodCallExpression mce; + if (subscriptType != null) { // GROOVY-5521: check for a suitable "getAt(T)" method + mce = callX(varX("#", receiverType), "getAt", varX("selector", subscriptType)); + } else { // GROOVY-8133: check for an "iterator()" method + mce = callX(varX("#", receiverType), "iterator"); + } + mce.setImplicitThis(false); // GROOVY-8943 + typeCheckingContext.pushErrorCollector(); - MethodCallExpression vcall = callX(localVarX("_hash_", containerType), "getAt", varX("_index_", indexType)); - vcall.setImplicitThis(false); // GROOVY-8943 try { - visitMethodCallExpression(vcall); + visitMethodCallExpression(mce); } finally { typeCheckingContext.popErrorCollector(); } - return getType(vcall); - } else { - return componentType; + + if (subscriptType != null) { + componentType = getType(mce); + } else { + ClassNode iteratorType = getType(mce); + if (isOrImplements(iteratorType, Iterator_TYPE) && (iteratorType.getGenericsTypes() != null + // ignore the iterator(Object) extension method, since it makes *everything* appear iterable + || !mce.<MethodNode>getNodeMetaData(DIRECT_METHOD_CALL_TARGET).getDeclaringClass().equals(OBJECT_TYPE))) { + componentType = Optional.ofNullable(iteratorType.getGenericsTypes()).map(gt -> getCombinedBoundType(gt[0])).orElse(OBJECT_TYPE); + } + } } + return componentType; } protected MethodNode findMethodOrFail(final Expression expr, final ClassNode receiver, final String name, final ClassNode... args) { diff --git a/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy b/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy index 481c496..dc0fb19 100644 --- a/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy +++ b/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy @@ -19,6 +19,7 @@ package groovy.transform.stc import org.codehaus.groovy.control.MultipleCompilationErrorsException +import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer import org.codehaus.groovy.control.customizers.ImportCustomizer /** @@ -1159,6 +1160,32 @@ class MethodCallsSTCTest extends StaticTypeCheckingTestCase { 'Cannot call closure that accepts [java.lang.String, java.lang.String, java.lang.String] with [java.lang.Object]' } + // GROOVY-8133 + void testSpreadDotOperator() { + assertScript ''' + def list = ['a','b','c'].stream()*.toUpperCase() + assert list == ['A', 'B', 'C'] + ''' + assertScript ''' + def list = 'a,b,c'.split(',')*.toUpperCase() + assert list == ['A', 'B', 'C'] + ''' + + shouldFailWithMessages ''' + def list = 'abc'*.toUpperCase() + assert list == ['A', 'B', 'C'] + ''', + 'Spread-dot operator can only be used on iterable types' + + config.compilationCustomizers + .find { it instanceof ASTTransformationCustomizer } + .annotationParameters = [extensions: PrecompiledExtensionNotExtendingDSL.name] + assertScript ''' + def list = 'abc'*.toUpperCase() + assert list == ['A', 'B', 'C'] + ''' + } + void testBoxingShouldCostMore() { assertScript ''' int foo(int x) { 1 } @@ -1411,7 +1438,7 @@ class MethodCallsSTCTest extends StaticTypeCheckingTestCase { void testStaticContextScoping() { assertScript ''' class A { - static List foo = 'a,b,c'.split(/,/).toList()*.trim() + static List foo = 'a,b,c'.split(/,/)*.trim() } assert A.foo == ['a','b','c'] ''' diff --git a/src/test/groovy/transform/stc/PrecompiledExtensionNotExtendingDSL.groovy b/src/test/groovy/transform/stc/PrecompiledExtensionNotExtendingDSL.groovy index 020304b..6598a4b 100644 --- a/src/test/groovy/transform/stc/PrecompiledExtensionNotExtendingDSL.groovy +++ b/src/test/groovy/transform/stc/PrecompiledExtensionNotExtendingDSL.groovy @@ -18,28 +18,27 @@ */ package groovy.transform.stc +import groovy.transform.AutoFinal +import groovy.transform.InheritConstructors +import org.codehaus.groovy.ast.ClassHelper +import org.codehaus.groovy.ast.ClassNode import org.codehaus.groovy.ast.MethodNode import org.codehaus.groovy.ast.expr.Expression import org.codehaus.groovy.transform.stc.AbstractTypeCheckingExtension -import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor -class PrecompiledExtensionNotExtendingDSL extends AbstractTypeCheckingExtension { - - - PrecompiledExtensionNotExtendingDSL( - final StaticTypeCheckingVisitor typeCheckingVisitor) { - super(typeCheckingVisitor) - } - - @Override - void setup() { - addStaticTypeError('Error thrown from extension in setup', context.enclosingClassNode) - } +import static org.codehaus.groovy.ast.tools.GenericsUtils.makeClassSafeWithGenerics +@AutoFinal @InheritConstructors +class PrecompiledExtensionNotExtendingDSL extends AbstractTypeCheckingExtension { @Override - void onMethodSelection(final Expression expression, final MethodNode target) { - if (target.name=='println') { + void onMethodSelection(Expression expression, MethodNode target) { + switch (target.name) { + case 'println': addStaticTypeError('Error thrown from extension in onMethodSelection', expression.arguments[0]) + break + case 'iterator': + ClassNode iteratorType = makeClassSafeWithGenerics(ClassHelper.Iterator_TYPE, ClassHelper.STRING_TYPE.asGenericsType()) + storeType(expression, iteratorType) // indicate "string.iterator()" returns Iterator<String> } } -} \ No newline at end of file +} diff --git a/src/test/groovy/transform/stc/TypeCheckingExtensionsTest.groovy b/src/test/groovy/transform/stc/TypeCheckingExtensionsTest.groovy index 3558c23..af5ddce 100644 --- a/src/test/groovy/transform/stc/TypeCheckingExtensionsTest.groovy +++ b/src/test/groovy/transform/stc/TypeCheckingExtensionsTest.groovy @@ -483,8 +483,8 @@ class TypeCheckingExtensionsTest extends StaticTypeCheckingTestCase { extension = 'groovy.transform.stc.PrecompiledExtension' shouldFailWithMessages ''' println 'Everything is ok' - ''', 'Error thrown from extension' - + ''', + 'Error thrown from extension' } void testPrecompiledExtensionNotExtendingTypeCheckingDSL() { @@ -495,7 +495,7 @@ class TypeCheckingExtensionsTest extends StaticTypeCheckingTestCase { extension = 'groovy.transform.stc.PrecompiledExtensionNotExtendingDSL' shouldFailWithMessages ''' println 'Everything is ok' - ''', 'Error thrown from extension in setup', 'Error thrown from extension in onMethodSelection' - + ''', + 'Error thrown from extension in onMethodSelection' } }
