This is an automated email from the ASF dual-hosted git repository. emilles pushed a commit to branch GROOVY-11615 in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 47c8e448eab32500b55772df2437c9f81be6ad96 Author: Eric Milles <eric.mil...@thomsonreuters.com> AuthorDate: Wed Jun 18 14:32:13 2025 -0500 GROOVY-11615: STC: receiver type args in param can help with argument STC can work out `Set<String>` instead of `Set<#T>` for this: `def set_of_string = list_of_string.stream().collect(toSet())` --- .../transform/stc/StaticTypeCheckingVisitor.java | 53 ++++++++++++++++++---- .../groovy/transform/stc/GenericsSTCTest.groovy | 19 ++++++-- 2 files changed, 58 insertions(+), 14 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 218529fa53..6a5bed4ab0 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -237,7 +237,6 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.nullX; 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; -import static org.codehaus.groovy.ast.tools.GenericsUtils.makeClassSafe0; import static org.codehaus.groovy.ast.tools.ParameterUtils.isVargs; import static org.codehaus.groovy.ast.tools.WideningCategories.isBigDecCategory; import static org.codehaus.groovy.ast.tools.WideningCategories.isBigIntCategory; @@ -1916,7 +1915,7 @@ out: if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0 switch (pexp.getPropertyAsString()) { case "key": pexp.putNodeMetaData(READONLY_PROPERTY,Boolean.TRUE); // GROOVY-10326 - return makeClassSafe0(LIST_TYPE, gts[0]); + return GenericsUtils.makeClassSafe0(LIST_TYPE, gts[0]); case "value": GenericsType v = gts[1]; if (!v.isWildcard() @@ -1924,7 +1923,7 @@ out: if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0 && typeCheckingContext.isTargetOfEnclosingAssignment(pexp)) { v = GenericsUtils.buildWildcardType(v.getType()); // GROOVY-10325 } - return makeClassSafe0(LIST_TYPE, v); + return GenericsUtils.makeClassSafe0(LIST_TYPE, v); default: addStaticTypeError("Spread operator on map only allows one of [key,value]", pexp); } @@ -2181,7 +2180,7 @@ out: if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0 } else if (isOrImplements(collectionType, MAP_TYPE)) { // GROOVY-6240 ClassNode col = GenericsUtils.parameterizeType(collectionType, MAP_TYPE); - componentType = makeClassSafe0(MAP_ENTRY_TYPE, col.getGenericsTypes()); + componentType = GenericsUtils.makeClassSafe0(MAP_ENTRY_TYPE, col.getGenericsTypes()); } else if (isOrImplements(collectionType, STREAM_TYPE)) { // GROOVY-10476 ClassNode col = GenericsUtils.parameterizeType(collectionType, STREAM_TYPE); @@ -2732,7 +2731,7 @@ out: if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0 } private static ClassNode wrapClosureType(final ClassNode returnType) { - return makeClassSafe0(CLOSURE_TYPE, wrapTypeIfNecessary(returnType).asGenericsType()); + return GenericsUtils.makeClassSafe0(CLOSURE_TYPE, wrapTypeIfNecessary(returnType).asGenericsType()); } private static MethodNode findPropertyMethod(final ClassNode type, final String name) { @@ -5188,7 +5187,7 @@ trying: for (ClassNode[] signature : signatures) { if (node instanceof ClassExpression) { type = ((ClassExpression) node).getType(); - return makeClassSafe0(CLASS_Type, new GenericsType(type)); + return GenericsUtils.makeClassSafe0(CLASS_Type, new GenericsType(type)); } if (node instanceof VariableExpression) { @@ -5282,7 +5281,7 @@ trying: for (ClassNode[] signature : signatures) { } else { type = wrapTypeIfNecessary(lowestUpperBound(fromType, toType)); } - return makeClassSafe0(RANGE_TYPE, new GenericsType(type)); + return GenericsUtils.makeClassSafe0(RANGE_TYPE, new GenericsType(type)); } if (node instanceof SpreadExpression) { @@ -5631,7 +5630,7 @@ trying: for (ClassNode[] signature : signatures) { } extractGenericsConnections(connections, returnType, samParamsAndReturnType.getV2()); } else { - extractGenericsConnections(connections, wrapTypeIfNecessary(argumentType), paramType); + extractGenericsConnections(connections, applyKnownTypeArgs(argumentType, paramType), paramType); } connections.forEach((gtn, gt) -> resolvedPlaceholders.merge(gtn, gt, (gt1, gt2) -> { @@ -5663,6 +5662,40 @@ trying: for (ClassNode[] signature : signatures) { return resolvedPlaceholders; } + /** + * For an expression like "list.stream().collect(Collectors.toSet())" the + * return type is dependent on the return type of {@code toSet} which can + * be partially resolved from the receiver type {@code Stream<SomeType>}. + * <p> + * {@code Collector<#T,?,Set<#T>> -> Collector<SomeType,?,Set<SomeType>>} + * + * @see #adjustForTargetType(ClassNode,ClassNode) + */ + private static ClassNode applyKnownTypeArgs(final ClassNode sourceType, final ClassNode targetType) { + ClassNode resultType = wrapTypeIfNecessary(sourceType); + + if (!targetType.isGenericsPlaceHolder()) { + // GROOVY-11615: receiver type arguments from target type + var spec = new HashMap<GenericsTypeName, GenericsType>(); + extractGenericsConnections(spec, targetType, sourceType); + + for (var iter = spec.entrySet().iterator(); iter.hasNext(); ) { + var entry = iter.next(); + var value = entry.getValue(); + if (!entry.getKey().getName().startsWith("#") // type that requires target information + || GenericsUtils.hasUnresolvedGenerics(GenericsUtils.makeClassSafe0(CLASS_Type, value))) { + iter.remove(); + } else if (value.getLowerBound() != null) { + entry.setValue(new GenericsType(value.getLowerBound())); + } + } + + resultType = applyGenericsContext(spec, resultType); + } + + return resultType; + } + /** * Given method call like "m(Collections.emptyList())", the type of the call * argument is {@code List<T>} without explicit type arguments. Now, knowing @@ -5757,7 +5790,7 @@ trying: for (ClassNode[] signature : signatures) { if (target instanceof ExtensionMethodNode && !((ExtensionMethodNode) target).isStaticExtension()) { params = ((ExtensionMethodNode) target).getExtensionMethodNode().getParameters(); } else if (!target.isStatic() && source.getExpression() instanceof ClassExpression) { - ClassNode thisType = ((ClassExpression) source.getExpression()).getType(); + ClassNode thisType = source.getExpression().getType(); // there is an implicit parameter for "String::length" int n = target.getParameters().length; params = new Parameter[n + 1]; @@ -5919,7 +5952,7 @@ out: for (ClassNode type : todo) { } Map<GenericsTypeName, GenericsType> spec = extractPlaceHoldersVisibleToDeclaration(r, m, null); GenericsType[] gt = applyGenericsContext(spec, m.getGenericsTypes()); // class params in bounds - GenericsUtils.extractPlaceholders(makeClassSafe0(OBJECT_TYPE, gt), spec); + GenericsUtils.extractPlaceholders(GenericsUtils.makeClassSafe0(OBJECT_TYPE, gt), spec); Parameter[] parameters = m.getParameters(); ClassNode[] paramTypes = new ClassNode[parameters.length]; diff --git a/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy index 4e7ebc3833..7d566a03a0 100644 --- a/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy +++ b/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy @@ -594,21 +594,32 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { } } - // GROOVY-8409, GROOVY-10067 + // GROOVY-8409, GROOVY-10067, GROOVY-10074, etc. void testReturnTypeInferenceWithMethodGenerics15() { shouldFailWithMessages ''' - List<CharSequence> list = ['x'].collect() // GROOVY-10074 + List<CharSequence> list = ['x'].collect() ''', 'Incompatible generic argument types. Cannot assign java.util.List<java.lang.String> to: java.util.List<java.lang.CharSequence>' shouldFailWithMessages ''' - List<CharSequence> list = ['x'].stream().toList() // TODO: fix type param bound of StreamGroovyMethods#toList(Stream<T>) + List<CharSequence> list = ['x'].stream().toList() ''', 'Incompatible generic argument types. Cannot assign java.util.List<java.lang.String> to: java.util.List<java.lang.CharSequence>' - assertScript ''' + shouldFailWithMessages ''' import static java.util.stream.Collectors.toList List<CharSequence> list = ['x'].stream().collect(toList()) + ''', + 'Incompatible generic argument types. Cannot assign java.util.List<java.lang.String> to: java.util.List<java.lang.CharSequence>' + + assertScript ''' + import static java.util.stream.Collectors.toList + List<? extends CharSequence> list = ['x'].stream().collect(toList()) + ''' + + assertScript ''' + import static java.util.stream.Collectors.toList + List<String> list = ['x'].stream().collect(toList()) ''' }