You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/03/31 01:34:10 UTC
[groovy] branch GROOVY_3_0_X updated: GROOVY-8409: STC: resolve generics contexts separately for return type
This is an automated email from the ASF dual-hosted git repository.
emilles 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 d37a593 GROOVY-8409: STC: resolve generics contexts separately for return type
d37a593 is described below
commit d37a59341154bd0d02937621d11ee90b983837c1
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Wed Mar 30 20:31:24 2022 -0500
GROOVY-8409: STC: resolve generics contexts separately for return type
// Function also uses "T" for its input
def <T> T m(Function<Type, T> f) {
T result = f.apply(new Type())
return result
}
3_0_X backport
---
.../transform/stc/StaticTypeCheckingSupport.java | 2 +-
.../transform/stc/StaticTypeCheckingVisitor.java | 156 +++++++++++----------
src/test/groovy/bugs/Groovy8409Bug.groovy | 63 ++++-----
.../groovy/transform/stc/GenericsSTCTest.groovy | 16 +++
.../asm/sc/GenericsStaticCompileTest.groovy | 6 -
5 files changed, 126 insertions(+), 117 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
index e782330..367b8cd 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -1787,7 +1787,7 @@ public abstract class StaticTypeCheckingSupport {
return newSpec;
}
- private static GenericsType[] applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final GenericsType[] gts) {
+ static GenericsType[] applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final GenericsType[] gts) {
if (gts == null || spec == null || spec.isEmpty()) return gts;
GenericsType[] newGTs = new GenericsType[gts.length];
for (int i = 0, n = gts.length; i < n; i += 1) {
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 f65bbbc..fcfc80f 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -5223,43 +5223,43 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
}
/**
- * If a method call returns a parameterized type, then we can perform additional inference on the
- * return type, so that the type gets actual type parameters. For example, the method
- * Arrays.asList(T...) is generified with type T which can be deduced from actual type
- * arguments.
+ * If a method call returns a parameterized type, then perform additional
+ * inference on the return type, so that the type gets actual type arguments.
+ * For example, the method {@code Arrays.asList(T...)} is parameterized with
+ * {@code T}, which can be deduced type arguments or call arguments.
*
- * @param method the method node
- * @param arguments the method call arguments
- * @return parameterized, infered, class node
+ * @param method the method node
+ * @param arguments the method call arguments
+ * @param receiver the object expression type
*/
protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final MethodNode method, final Expression arguments) {
return inferReturnTypeGenerics(receiver, method, arguments, null);
}
/**
- * If a method call returns a parameterized type, then we can perform additional inference on the
- * return type, so that the type gets actual type parameters. For example, the method
- * Arrays.asList(T...) is generified with type T which can be deduced from actual type
- * arguments.
+ * If a method call returns a parameterized type, then perform additional
+ * inference on the return type, so that the type gets actual type arguments.
+ * For example, the method {@code Arrays.asList(T...)} is parameterized with
+ * {@code T}, which can be deduced type arguments or call arguments.
*
* @param method the method node
* @param arguments the method call arguments
- * @param explicitTypeHints explicit type hints as found for example in Collections.<String>emptyList()
- * @return parameterized, infered, class node
+ * @param receiver the object expression type
+ * @param explicitTypeHints type arguments (optional), for example {@code Collections.<String>emptyList()}
*/
protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final MethodNode method, final Expression arguments, final GenericsType[] explicitTypeHints) {
- ClassNode returnType = method.getReturnType();
- if (getGenericsWithoutArray(returnType) == null) {
+ ClassNode returnType = method instanceof ConstructorNode ? method.getDeclaringClass() : method.getReturnType();
+ if (!GenericsUtils.hasUnresolvedGenerics(returnType)) {
+ // GROOVY-7538: replace "Type<?>" with "Type<? extends/super X>" for any "Type<T extends/super X>"
+ if (getGenericsWithoutArray(returnType) != null) returnType = boundUnboundedWildcards(returnType);
+
return returnType;
}
+
if (method instanceof ExtensionMethodNode) {
- // check if the placeholder corresponds to the placeholder of the first parameter
- ExtensionMethodNode emn = (ExtensionMethodNode) method;
- MethodNode dgm = emn.getExtensionMethodNode();
ArgumentListExpression args = new ArgumentListExpression();
VariableExpression vexp = varX("$self", receiver);
args.addExpression(vexp);
- vexp.setNodeMetaData(ExtensionMethodDeclaringClass.class, emn.getDeclaringClass());
if (arguments instanceof ArgumentListExpression) {
for (Expression argument : (ArgumentListExpression) arguments) {
args.addExpression(argument);
@@ -5267,70 +5267,78 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
} else {
args.addExpression(arguments);
}
- return inferReturnTypeGenerics(receiver, dgm, args, explicitTypeHints);
+ vexp.setNodeMetaData(ExtensionMethodDeclaringClass.class, method.getDeclaringClass());
+ return inferReturnTypeGenerics(receiver, ((ExtensionMethodNode) method).getExtensionMethodNode(), args, explicitTypeHints);
}
- Map<GenericsTypeName, GenericsType> resolvedPlaceholders = resolvePlaceHoldersFromDeclaration(receiver, getDeclaringClass(method, arguments), method, method.isStatic());
- resolvePlaceholdersFromExplicitTypeHints(method, explicitTypeHints, resolvedPlaceholders);
- if (resolvedPlaceholders.isEmpty()) {
- return boundUnboundedWildcards(returnType);
- }
- Map<GenericsTypeName, GenericsType> placeholdersFromContext = extractGenericsParameterMapOfThis(typeCheckingContext);
- applyGenericsConnections(placeholdersFromContext, resolvedPlaceholders);
-
- // then resolve receivers from method arguments
- List<Expression> expressions = InvocationWriter.makeArgumentList(arguments).getExpressions();
- Parameter[] parameters = method.getParameters();
- boolean isVargs = isVargs(parameters);
- int paramLength = parameters.length;
- if (expressions.size() >= paramLength) {
- for (int i = 0; i < paramLength; i += 1) {
- if (isNullConstant(expressions.get(i)))
- continue; // GROOVY-9984: skip null
- boolean lastArg = (i == paramLength - 1);
- ClassNode paramType = parameters[i].getType();
- ClassNode argumentType = getDeclaredOrInferredType(expressions.get(i));
- while (paramType.isArray() && argumentType.isArray()) {
- paramType = paramType.getComponentType();
- argumentType = argumentType.getComponentType();
- }
- if (isUsingGenericsOrIsArrayUsingGenerics(paramType)) {
- if (argumentType.isDerivedFrom(CLOSURE_TYPE)) {
- MethodNode sam = findSAM(paramType);
- if (sam != null) { // implicit closure coercion in action!
- argumentType = !paramType.isUsingGenerics() ? paramType
- : convertClosureTypeToSAMType(expressions.get(i), argumentType, sam, paramType,
- applyGenericsContextToParameterClass(resolvedPlaceholders, paramType));
- }
- }
- if (isVargs && lastArg && paramType.isArray() && !argumentType.isArray()) {
- paramType = paramType.getComponentType();
- }
- argumentType = wrapTypeIfNecessary(argumentType);
- Map<GenericsTypeName, GenericsType> connections = new HashMap<>();
- extractGenericsConnections(connections, argumentType, paramType);
- extractGenericsConnectionsForSuperClassAndInterfaces(resolvedPlaceholders, connections);
+ Map<GenericsTypeName, GenericsType> context = method.isStatic() || method instanceof ConstructorNode
+ ? null : extractPlaceHolders(null, receiver, getDeclaringClass(method, arguments));
+ GenericsType[] methodGenericTypes = method instanceof ConstructorNode ? method.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, method.getGenericsTypes());
+
+ // 1) resolve type parameters of method
+
+ if (methodGenericTypes != null) {
+ Map<GenericsTypeName, GenericsType> resolvedPlaceholders = new HashMap<>();
+ for (GenericsType gt : methodGenericTypes) resolvedPlaceholders.put(new GenericsTypeName(gt.getName()), gt);
+
+ Parameter[] parameters = method.getParameters();
+ final int nParameters = parameters.length;
- applyGenericsConnections(connections, resolvedPlaceholders);
- applyGenericsConnections(placeholdersFromContext, resolvedPlaceholders);
+ if (explicitTypeHints != null) { // resolve type parameters from type arguments
+ int n = methodGenericTypes.length;
+ if (n == explicitTypeHints.length) {
+ for (int i = 0; i < n; i += 1) {
+ resolvedPlaceholders.put(new GenericsTypeName(methodGenericTypes[i].getName()), explicitTypeHints[i]);
+ }
}
- }
- }
+ } else if (nParameters > 0) { // resolve type parameters from call arguments
+ List<Expression> expressions = InvocationWriter.makeArgumentList(arguments).getExpressions();
+ boolean isVargs = isVargs(parameters);
+ if (expressions.size() >= nParameters) {
+ for (int i = 0; i < nParameters; i += 1) {
+ if (isNullConstant(expressions.get(i)))
+ continue; // GROOVY-9984: skip null
+ boolean lastArg = (i == nParameters - 1);
+ ClassNode paramType = parameters[i].getType();
+ ClassNode argumentType = getDeclaredOrInferredType(expressions.get(i));
+ while (paramType.isArray() && argumentType.isArray()) {
+ paramType = paramType.getComponentType();
+ argumentType = argumentType.getComponentType();
+ }
+ if (isUsingGenericsOrIsArrayUsingGenerics(paramType)) {
+ if (argumentType.isDerivedFrom(CLOSURE_TYPE)) {
+ MethodNode sam = findSAM(paramType);
+ if (sam != null) { // implicit closure coercion in action!
+ argumentType = !paramType.isUsingGenerics() ? paramType
+ : convertClosureTypeToSAMType(expressions.get(i), argumentType, sam, paramType,
+ applyGenericsContextToParameterClass(resolvedPlaceholders, paramType));
+ }
+ }
+ if (isVargs && lastArg && paramType.isArray() && !argumentType.isArray()) {
+ paramType = paramType.getComponentType();
+ }
- return applyGenericsContext(resolvedPlaceholders, returnType);
- }
+ Map<GenericsTypeName, GenericsType> connections = new HashMap<>();
+ extractGenericsConnections(connections, wrapTypeIfNecessary(argumentType), paramType);
+ extractGenericsConnectionsForSuperClassAndInterfaces(resolvedPlaceholders, connections);
- private static void resolvePlaceholdersFromExplicitTypeHints(final MethodNode method, final GenericsType[] explicitTypeHints, final Map<GenericsTypeName, GenericsType> resolvedPlaceholders) {
- if (explicitTypeHints != null) {
- GenericsType[] methodGenericTypes = method.getGenericsTypes();
- if (methodGenericTypes != null && methodGenericTypes.length == explicitTypeHints.length) {
- for (int i = 0, n = methodGenericTypes.length; i < n; i += 1) {
- GenericsType methodGenericType = methodGenericTypes[i];
- GenericsType explicitTypeHint = explicitTypeHints[i];
- resolvedPlaceholders.put(new GenericsTypeName(methodGenericType.getName()), explicitTypeHint);
+ applyGenericsConnections(connections, resolvedPlaceholders);
+ }
+ }
}
}
+ returnType = applyGenericsContext(resolvedPlaceholders, returnType);
}
+
+ // 2) resolve type parameters of method's enclosing context
+
+ if (context != null) returnType = applyGenericsContext(context, returnType);
+
+ // 3) resolve bounds of type parameters from calling context
+
+ returnType = applyGenericsContext(extractGenericsParameterMapOfThis(typeCheckingContext), returnType);
+
+ return returnType;
}
/**
diff --git a/src/test/groovy/bugs/Groovy8409Bug.groovy b/src/test/groovy/bugs/Groovy8409Bug.groovy
index d122ae5..a24fce2 100644
--- a/src/test/groovy/bugs/Groovy8409Bug.groovy
+++ b/src/test/groovy/bugs/Groovy8409Bug.groovy
@@ -18,52 +18,43 @@
*/
package groovy.bugs
-import gls.CompilableTestSupport
-import groovy.test.NotYetImplemented
+import org.junit.Test
-class Groovy8409Bug extends CompilableTestSupport {
- @NotYetImplemented
+import static groovy.test.GroovyAssert.assertScript
+
+final class Groovy8409Bug {
+
+ @Test
void test() {
assertScript '''
- import groovy.transform.CompileStatic
- import java.util.function.BiFunction
-
- @CompileStatic
- class Groovy8409Bug {
-
- static <T> T actionWrapperT(BiFunction<Date, URL, T> action) {
- T result = action.apply(new Date(), new URL('http://www.example.com'))
- // do something else here
- return result
+ @groovy.transform.CompileStatic
+ class Groovy8409Bug {
+ static <T> T actionWrapperT(java.util.function.BiFunction<Date, URL, T> action) {
+ T result = action.apply(new Date(), new URL('http://www.example.com'))
+ // do something else here
+ return result
+ }
+ static void main(String[] args) {
+ Groovy8409Bug t = actionWrapperT { Date date, URL url -> new Groovy8409Bug() }
+ }
}
-
- static void main(String[] args) {
- Groovy8409Bug t = actionWrapperT { Date date, URL url -> new Groovy8409Bug() }
- }
-
- }
'''
}
+ @Test
void testWorkaround() {
assertScript '''
- import groovy.transform.CompileStatic
- import java.util.function.BiFunction
-
- @CompileStatic
- class Groovy8409Bug {
-
- static <X> X actionWrapperT(BiFunction<Date, URL, X> action) {
- X result = action.apply(new Date(), new URL('http://www.example.com'))
- // do something else here
- return result
- }
-
- static void main(String[] args) {
- Groovy8409Bug t = actionWrapperT { Date date, URL url -> new Groovy8409Bug() }
+ @groovy.transform.CompileStatic
+ class Groovy8409Bug {
+ static <X> X actionWrapperT(java.util.function.BiFunction<Date, URL, X> action) {
+ X result = action.apply(new Date(), new URL('http://www.example.com'))
+ // do something else here
+ return result
+ }
+ static void main(String[] args) {
+ Groovy8409Bug t = actionWrapperT { Date date, URL url -> new Groovy8409Bug() }
+ }
}
-
- }
'''
}
}
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 2ad5bfb..1515770 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -3101,6 +3101,22 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
'''
}
+ // GROOVY-10557
+ void testReturnTypeInferenceWithClosure2() {
+ assertScript '''
+ class C {
+ def <T> T m(java.util.function.Function<Reader,T> f) {
+ new StringReader("").withCloseable { reader ->
+ f.apply(reader)
+ }
+ }
+ }
+ Object result = new C().m { it.text.empty }
+ // ^^ StringReader
+ assert result == Boolean.TRUE
+ '''
+ }
+
// GROOVY-6129
void testShouldNotThrowNPE() {
assertScript '''
diff --git a/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy b/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy
index 3a667c5..4a599b2 100644
--- a/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy
@@ -18,16 +18,10 @@
*/
package org.codehaus.groovy.classgen.asm.sc
-import groovy.test.NotYetImplemented
import groovy.transform.stc.GenericsSTCTest
/**
* Unit tests for static compilation : generics.
*/
class GenericsStaticCompileTest extends GenericsSTCTest implements StaticCompilationTestSupport {
-
- @Override @NotYetImplemented // GROOVY-8409
- void testReturnTypeInferenceWithMethodGenerics14() {
- super.testReturnTypeInferenceWithMethodGenerics14()
- }
}