GROOVY-11013, GROOVY-11072: apply type variable of SAM-type to `List<T>`
diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
index 7f2dfe5..610516a 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
@@ -59,6 +59,7 @@
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.plus;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUnboundedWildcard;
+import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.resolveClassNodeGenerics;
/**
* Utility methods to deal with parameterized types.
@@ -909,6 +910,7 @@
*
* @since 3.0.0
*/
+ @Deprecated(forRemoval = true, since = "5.0.0")
public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMap(final ClassNode declaringClass, final ClassNode actualReceiver) {
return correlateTypeParametersAndTypeArguments(declaringClass, actualReceiver, false);
}
@@ -924,10 +926,12 @@
*
* @since 3.0.0
*/
+ @Deprecated(forRemoval = true, since = "5.0.0")
public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMapOfExactType(final ClassNode declaringClass, final ClassNode actualReceiver) {
return correlateTypeParametersAndTypeArguments(declaringClass, actualReceiver, true);
}
+ @Deprecated(forRemoval = true, since = "5.0.0")
private static Map<GenericsType, GenericsType> correlateTypeParametersAndTypeArguments(final ClassNode declaringClass, final ClassNode actualReceiver, final boolean tryToFindExactType) {
ClassNode parameterizedType = findParameterizedTypeFromCache(declaringClass, actualReceiver, tryToFindExactType);
if (parameterizedType != null && parameterizedType.isRedirectNode() && !parameterizedType.isGenericsPlaceHolder()) { // GROOVY-10166
@@ -1049,14 +1053,20 @@
*/
public static Tuple2<ClassNode[], ClassNode> parameterizeSAM(final ClassNode samType) {
MethodNode abstractMethod = ClassHelper.findSAM(samType);
+ ClassNode declaringClass = abstractMethod.getDeclaringClass();
+ Map<GenericsType.GenericsTypeName, GenericsType> spec = extractPlaceholders(
+ samType.equals(declaringClass) ? samType : parameterizeType(samType, declaringClass));
- Map<GenericsType, GenericsType> generics = makeDeclaringAndActualGenericsTypeMapOfExactType(abstractMethod.getDeclaringClass(), samType);
- Function<ClassNode , ClassNode> resolver = t -> {
- return t.isGenericsPlaceHolder() ? findActualTypeByGenericsPlaceholderName(t.getUnresolvedName(), generics) : t;
- };
+ if (spec.isEmpty() && declaringClass.getGenericsTypes() != null) {
+ for (GenericsType tp : declaringClass.getGenericsTypes()) // apply erasure
+ spec.put(new GenericsType.GenericsTypeName(tp.getName()), erasure(tp));
+ } else {
+ // resolveClassNodeGenerics converts "T=? super Type" to Object, so convert "T=? super Type" to "T=Type"
+ spec.replaceAll((name, type) -> type.isWildcard() && type.getLowerBound() != null ? type.getLowerBound().asGenericsType() : type);
+ }
- ClassNode[] parameterTypes = Arrays.stream(abstractMethod.getParameters()).map(Parameter::getType).map(resolver).toArray(ClassNode[]::new);
- ClassNode returnType = resolver.apply(abstractMethod.getReturnType());
+ ClassNode[] parameterTypes = Arrays.stream(abstractMethod.getParameters()).map(p -> resolveClassNodeGenerics(spec, null, p.getType())).toArray(ClassNode[]::new);
+ ClassNode returnType = resolveClassNodeGenerics(spec, null, abstractMethod.getReturnType());
return new Tuple2<>(parameterTypes, returnType);
}
@@ -1068,6 +1078,7 @@
*
* @since 3.0.0
*/
+ @Deprecated(forRemoval = true, since = "5.0.0")
public static ClassNode findActualTypeByGenericsPlaceholderName(final String placeholderName, final Map<GenericsType, GenericsType> genericsPlaceholderAndTypeMap) {
Function<GenericsType, ClassNode> resolver = gt -> {
if (gt.isWildcard()) {
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
index d34c64f..df110a1 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
@@ -89,63 +89,39 @@
return arguments;
}
+ default ClassNode convertParameterType(final ClassNode parameterType, final ClassNode inferredType) {
+ return convertParameterType(parameterType, parameterType, inferredType);
+ }
+
default ClassNode convertParameterType(final ClassNode targetType, final ClassNode parameterType, final ClassNode inferredType) {
if (!getWrapper(inferredType).isDerivedFrom(getWrapper(parameterType))) {
throw new RuntimeParserException("The inferred type[" + inferredType.redirect() + "] is not compatible with the parameter type[" + parameterType.redirect() + "]", parameterType);
}
- ClassNode type;
- boolean isParameterTypePrimitive = isPrimitiveType(parameterType);
- boolean isInferredTypePrimitive = isPrimitiveType(inferredType);
- if (!isParameterTypePrimitive && isInferredTypePrimitive) {
- if (isDynamicTyped(parameterType) && isPrimitiveType(targetType) // (1)
- || !parameterType.equals(getUnwrapper(parameterType)) && !inferredType.equals(getWrapper(inferredType)) // (2)
- ) {
- // GROOVY-9790: bootstrap method initialization exception raised when lambda parameter type is wrong
- // (1) java.lang.invoke.LambdaConversionException: Type mismatch for instantiated parameter 0: class java.lang.Integer is not a subtype of int
- // (2) java.lang.BootstrapMethodError: bootstrap method initialization exception
- type = inferredType;
- } else {
+ ClassNode type = inferredType;
+ if (isPrimitiveType(parameterType)) {
+ if (!isPrimitiveType(inferredType)) {
+ // The non-primitive type and primitive type are not allowed to mix since Java 9+
+ // java.lang.invoke.LambdaConversionException: Type mismatch for instantiated parameter 0: class java.lang.Integer is not a subtype of int
+ type = getUnwrapper(inferredType);
+ }
+ } else if (isPrimitiveType(inferredType)) {
+ // GROOVY-9790: bootstrap method initialization exception raised when lambda parameter type is wrong
+ // (1) java.lang.invoke.LambdaConversionException: Type mismatch for instantiated parameter 0: class java.lang.Integer is not a subtype of int
+ // (2) java.lang.BootstrapMethodError: bootstrap method initialization exception
+ if (!(isDynamicTyped(parameterType) && isPrimitiveType(targetType)) // (1)
+ && (parameterType.equals(getUnwrapper(parameterType)) || inferredType.equals(getWrapper(inferredType)))) { // (2)
// The non-primitive type and primitive type are not allowed to mix since Java 9+
// java.lang.invoke.LambdaConversionException: Type mismatch for instantiated parameter 0: int is not a subtype of class java.lang.Object
type = getWrapper(inferredType);
}
- } else if (isParameterTypePrimitive && !isInferredTypePrimitive) {
- // The non-primitive type and primitive type are not allowed to mix since Java 9+
- // java.lang.invoke.LambdaConversionException: Type mismatch for instantiated parameter 0: class java.lang.Integer is not a subtype of int
- type = getUnwrapper(inferredType);
- } else {
- type = inferredType;
+ }
+ if (type.isGenericsPlaceHolder()) {
+ type = type.redirect();
}
return type;
}
- /**
- * @deprecated use {@link #convertParameterType(ClassNode, ClassNode, ClassNode)} instead
- */
- @Deprecated
- default ClassNode convertParameterType(final ClassNode parameterType, final ClassNode inferredType) {
- if (!getWrapper(inferredType.redirect()).isDerivedFrom(getWrapper(parameterType.redirect()))) {
- throw new RuntimeParserException("The inferred type[" + inferredType.redirect() + "] is not compatible with the parameter type[" + parameterType.redirect() + "]", parameterType);
- } else {
- boolean isParameterTypePrimitive = isPrimitiveType(parameterType);
- boolean isInferredTypePrimitive = isPrimitiveType(inferredType);
- ClassNode type;
- if (!isParameterTypePrimitive && isInferredTypePrimitive) {
- if (parameterType != getUnwrapper(parameterType) && inferredType != getWrapper(inferredType)) {
- type = inferredType;
- } else {
- type = getWrapper(inferredType);
- }
- } else if (isParameterTypePrimitive && !isInferredTypePrimitive) {
- type = getUnwrapper(inferredType);
- } else {
- type = inferredType;
- }
- return type;
- }
- }
-
default Parameter prependParameter(final List<Parameter> parameterList, final String parameterName, final ClassNode parameterType) {
Parameter parameter = new Parameter(parameterType, parameterName);
parameter.setClosureSharedVariable(false);
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
index faaead5..d28cfde 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
@@ -303,13 +303,11 @@
if (inferredParamTypes != null) {
for (int i = 0, n = parameters.length; i < n; i += 1) {
- ClassNode inferredParamType = inferredParamTypes[i];
+ ClassNode inferredParamType = i < inferredParamTypes.length ? inferredParamTypes[i] : parameters[i].getType();
if (inferredParamType == null) continue;
-
Parameter parameter = parameters[i];
- Parameter targetParameter = parameter;
- ClassNode type = convertParameterType(targetParameter.getType(), parameter.getType(), inferredParamType);
+ ClassNode type = convertParameterType(parameter.getType(), inferredParamType);
parameter.setOriginType(type);
parameter.setType(type);
}
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 fddbf43..3b4b14d 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -1917,7 +1917,7 @@
}
if (type.getGenericsTypes()[0] != gt[0]) { // convert T to X
- ClassNode cn = make(gt[0].getName()) , erasure = getCombinedBoundType(gt[0]);
+ ClassNode cn = make(gt[0].getName()) , erasure = getCombinedBoundType(gt[0]).redirect();
cn.setGenericsPlaceHolder(true);
cn.setGenericsTypes(gt);
cn.setRedirect(erasure);
diff --git a/src/test/groovy/transform/stc/LambdaTest.groovy b/src/test/groovy/transform/stc/LambdaTest.groovy
index 6a03bfe..790e289 100644
--- a/src/test/groovy/transform/stc/LambdaTest.groovy
+++ b/src/test/groovy/transform/stc/LambdaTest.groovy
@@ -774,17 +774,17 @@
void testFunctionalInterface4() {
assertScript shell, '''
class Value<V> {
- final V val
+ final V val;
Value(V v) {
this.val = v
}
String toString() {
val as String
}
- def <T> Value<T> replace(Supplier<T> supplier) {
+ def <Out> Value<Out> replace(Supplier<Out> supplier) {
new Value<>(supplier.get())
}
- def <T> Value<T> replace(Function<? super V, ? extends T> function) {
+ def <Out> Value<Out> replace(Function<? super V, ? extends Out> function) {
new Value<>(function.apply(val))
}
}
@@ -806,6 +806,44 @@
assert err =~ /Expected type java.util.List<java.lang.String> for lambda parameter: list/
}
+ @Test // GROOVY-11013
+ void testFunctionalInterface6() {
+ assertScript shell, '''
+ interface I<T> {
+ def m(List<T> list_of_t)
+ }
+
+ I<String> face = (List<String> list) -> null
+ '''
+ }
+
+ @Test // GROOVY-11072
+ void testFunctionalInterface7() {
+ assertScript shell, '''
+ class Model {
+ }
+ class Table<T extends Model> {
+ interface ChunkReader<T> {
+ void call(List<T> row)
+ }
+ void getAll(ChunkReader<T> reader) {
+ List<T> chunk = []
+ reader.call(chunk)
+ }
+ }
+ class TestModel extends Model {
+ int id = 0
+ }
+ class TestTable extends Table<TestModel> {
+ }
+
+ TestTable table = new TestTable()
+ table.getAll((List<TestModel> list) ->
+ list.each { TestModel tm -> println(tm.id) }
+ )
+ '''
+ }
+
@Test
void testFunctionWithUpdatingLocalVariable() {
assertScript shell, '''