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/09/12 14:36:38 UTC
[groovy] branch GROOVY_2_5_X updated: GROOVY-10116, GROOVY-10337: STC: honor explicit ctor call type arguments
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch GROOVY_2_5_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_2_5_X by this push:
new 7dcba88d89 GROOVY-10116, GROOVY-10337: STC: honor explicit ctor call type arguments
7dcba88d89 is described below
commit 7dcba88d89891d6f3896e34a05a6b4f4a30645ba
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Sep 12 08:13:48 2022 -0500
GROOVY-10116, GROOVY-10337: STC: honor explicit ctor call type arguments
2_5_X backport
---
.../transform/stc/StaticTypeCheckingSupport.java | 83 +++++-----
.../groovy/transform/stc/GenericsSTCTest.groovy | 167 +++++++++++++++++++--
2 files changed, 194 insertions(+), 56 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 cbd3a2bf63..d16a76116a 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -71,7 +71,6 @@ import java.util.UUID;
import java.util.WeakHashMap;
import java.util.regex.Matcher;
-import static java.lang.Math.min;
import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod;
import static org.apache.groovy.ast.tools.ExpressionUtils.isNullConstant;
import static org.codehaus.groovy.ast.ClassHelper.BigDecimal_TYPE;
@@ -449,7 +448,7 @@ public abstract class StaticTypeCheckingSupport {
ClassNode elementType = arrayType.getComponentType();
ClassNode argumentType = argumentTypes[argumentTypes.length - 1];
if (isNumberType(elementType) && isNumberType(argumentType) && !getWrapper(elementType).equals(getWrapper(argumentType))) return -1;
- return isAssignableTo(argumentType, elementType) ? min(getDistance(argumentType, arrayType), getDistance(argumentType, elementType)) : -1;
+ return isAssignableTo(argumentType, elementType) ? Math.min(getDistance(argumentType, arrayType), getDistance(argumentType, elementType)) : -1;
}
/**
@@ -475,10 +474,10 @@ public abstract class StaticTypeCheckingSupport {
ClassNode sourceComponent = type.getComponentType(), targetComponent = toBeAssignedTo.getComponentType();
return (isPrimitiveType(sourceComponent) == isPrimitiveType(targetComponent)) && isAssignableTo(sourceComponent, targetComponent);
}
- if (type.isDerivedFrom(GSTRING_TYPE) && STRING_TYPE.equals(toBeAssignedTo)) {
+ if (type.isDerivedFrom(GSTRING_TYPE) && toBeAssignedTo.equals(STRING_TYPE)) {
return true;
}
- if (toBeAssignedTo.isDerivedFrom(GSTRING_TYPE) && STRING_TYPE.equals(type)) {
+ if (type.equals(STRING_TYPE) && toBeAssignedTo.isDerivedFrom(GSTRING_TYPE)) {
return true;
}
if (implementsInterfaceOrIsSubclassOf(type, toBeAssignedTo)) {
@@ -1477,44 +1476,44 @@ public abstract class StaticTypeCheckingSupport {
return typeCheckMethodsWithGenerics(receiver, arguments, candidateMethod, false);
}
- private static boolean typeCheckMethodsWithGenerics(ClassNode receiver, ClassNode[] arguments, MethodNode candidateMethod, boolean isExtensionMethod) {
- boolean failure = false;
+ private static boolean typeCheckMethodsWithGenerics(final ClassNode receiver, final ClassNode[] arguments, final MethodNode candidateMethod, final boolean isExtensionMethod) {
+ Parameter[] parameters = candidateMethod.getParameters();
+ if (parameters.length == 0 || parameters.length > arguments.length) {
+ // this is a limitation that must be removed in a future version
+ // we cannot check generic type arguments if there are default parameters!
+ return true;
+ }
// correct receiver for inner class
// we assume the receiver is an instance of the declaring class of the
// candidate method, but findMethod returns also outer class methods
// for that receiver. For now we skip receiver based checks in that case
// TODO: correct generics for when receiver is to be skipped
- boolean skipBecauseOfInnerClassNotReceiver = isOuterClassOf(receiver, candidateMethod.getDeclaringClass());
-
- Parameter[] parameters = candidateMethod.getParameters();
- Map<GenericsTypeName, GenericsType> classGTs;
- if (skipBecauseOfInnerClassNotReceiver) {
- classGTs = Collections.EMPTY_MAP;
- } else {
- classGTs = GenericsUtils.extractPlaceholders(receiver);
- }
- if (parameters.length > arguments.length || parameters.length == 0) {
- // this is a limitation that must be removed in a future version
- // we cannot check generic type arguments if there are default parameters!
- return true;
- }
+ boolean skipBecauseOfInnerClassNotReceiver = !implementsInterfaceOrIsSubclassOf(receiver, candidateMethod.getDeclaringClass());
// we have here different generics contexts we have to deal with.
// There is firstly the context given through the class, and the method.
// The method context may hide generics given through the class, but use
// the non-hidden ones.
- Map<GenericsTypeName, GenericsType> resolvedMethodGenerics = new HashMap<GenericsTypeName, GenericsType>();
- if (!skipBecauseOfInnerClassNotReceiver) {
+ Map<GenericsTypeName, GenericsType> classGTs;
+ Map<GenericsTypeName, GenericsType> resolvedMethodGenerics = new HashMap<>();
+ if (skipBecauseOfInnerClassNotReceiver) {
+ classGTs = Collections.emptyMap();
+ } else {
+ classGTs = GenericsUtils.extractPlaceholders(receiver);
addMethodLevelDeclaredGenerics(candidateMethod, resolvedMethodGenerics);
+ // remove hidden generics
+ for (GenericsTypeName key : resolvedMethodGenerics.keySet()) {
+ classGTs.remove(key);
+ }
+ // use the remaining information to refine given generics
+ applyGenericsConnections(classGTs, resolvedMethodGenerics);
}
- // so first we remove hidden generics
- for (GenericsTypeName key : resolvedMethodGenerics.keySet()) classGTs.remove(key);
- // then we use the remaining information to refine the given generics
- applyGenericsConnections(classGTs, resolvedMethodGenerics);
- // and then start our checks with the receiver
+
+ boolean failure = false;
+ // start checks with the receiver
if (!skipBecauseOfInnerClassNotReceiver) {
- failure |= inferenceCheck(Collections.EMPTY_SET, resolvedMethodGenerics, candidateMethod.getDeclaringClass(), receiver, false);
+ failure = inferenceCheck(Collections.EMPTY_SET, resolvedMethodGenerics, candidateMethod.getDeclaringClass(), receiver, false);
}
// the outside context parts till now define placeholder we are not allowed to
// generalize, thus we save that for later use...
@@ -1522,26 +1521,20 @@ public abstract class StaticTypeCheckingSupport {
// first parameter. While we normally allow generalization for the first
// parameter, in case of an extension method we must not.
Set<GenericsTypeName> fixedGenericsPlaceHolders = extractResolvedPlaceHolders(resolvedMethodGenerics);
+ if ("<init>".equals(candidateMethod.getName())) {
+ fixedGenericsPlaceHolders.addAll(resolvedMethodGenerics.keySet());
+ }
+ for (int i = 0, n = arguments.length, nthParameter = parameters.length - 1; i < n; i += 1) {
+ ClassNode argumentType = arguments[i];
+ ClassNode parameterType = parameters[Math.min(i, nthParameter)].getOriginType();
+ failure |= inferenceCheck(fixedGenericsPlaceHolders, resolvedMethodGenerics, parameterType, argumentType, i >= nthParameter);
- for (int i = 0; i < arguments.length; i++) {
- int pindex = min(i, parameters.length - 1);
- ClassNode wrappedArgument = arguments[i];
- ClassNode type = parameters[pindex].getOriginType();
-
- failure |= inferenceCheck(fixedGenericsPlaceHolders, resolvedMethodGenerics, type, wrappedArgument, i >= parameters.length - 1);
-
- // set real fixed generics for extension methods
- if (isExtensionMethod && i == 0)
+ if (i == 0 && isExtensionMethod) // set real fixed generics for extension methods
fixedGenericsPlaceHolders = extractResolvedPlaceHolders(resolvedMethodGenerics);
}
return !failure;
}
- private static boolean isOuterClassOf(ClassNode receiver, ClassNode type) {
- if (implementsInterfaceOrIsSubclassOf(receiver, type)) return false;
- return true;
- }
-
private static Set<GenericsTypeName> extractResolvedPlaceHolders(Map<GenericsTypeName, GenericsType> resolvedMethodGenerics) {
if (resolvedMethodGenerics.isEmpty()) return Collections.EMPTY_SET;
Set<GenericsTypeName> result = new HashSet<GenericsTypeName>();
@@ -1571,6 +1564,8 @@ public abstract class StaticTypeCheckingSupport {
extractGenericsConnections(connections, wrappedArgument, type);
// each found connection must comply with already found connections
boolean failure = !compatibleConnections(connections, resolvedMethodGenerics, fixedGenericsPlaceHolders);
+
+ connections.keySet().removeAll(fixedGenericsPlaceHolders); // GROOVY-10337
// and then apply the found information to refine the method level
// information. This way the method level information slowly turns
// into information for the callsite
@@ -1686,8 +1681,8 @@ public abstract class StaticTypeCheckingSupport {
ClassNode replacementType = extractType(newValue);
if (oldValue.isCompatibleWith(replacementType)) {
entry.setValue(newValue);
- if (newValue.isPlaceholder()) {
- checkForMorePlaceholders = checkForMorePlaceholders || !equalIncludingGenerics(oldValue, newValue);
+ if (!checkForMorePlaceholders && newValue.isPlaceholder()) {
+ checkForMorePlaceholders = !equalIncludingGenerics(oldValue, newValue);
}
}
}
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index e98192c633..cc6a225b0e 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -2348,7 +2348,62 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
'''
}
+ @NotYetImplemented // GROOVY-5692, GROOVY-10006
void testCompatibleArgumentsForPlaceholders1() {
+ assertScript '''
+ def <T> T test(T one, T two) { }
+ def result = test(1,"II")
+ '''
+ assertScript '''
+ def <T> T test(T one, T two, T three) { }
+ def result = test(1,"II",Class)
+ '''
+ assertScript '''
+ def <T extends Number> T test(T one, T two) { }
+ def result = test(1L,2G)
+ '''
+ }
+
+ @NotYetImplemented // GROOVY-5692
+ void testCompatibleArgumentsForPlaceholders2() {
+ assertScript '''
+ def <T> boolean test(T one, List<T> many) { }
+ test(1,["II","III"])
+ '''
+ }
+
+ // GROOVY-10220
+ void testCompatibleArgumentsForPlaceholders3() {
+ assertScript '''
+ class C<S, T extends Number> {
+ }
+ class D<T> {
+ C<? extends T, Integer> f
+ D(C<? extends T, Integer> p) {
+ f = p
+ }
+ }
+
+ assert new D<String>(null).f == null
+ '''
+ }
+
+ // GROOVY-10235
+ void testCompatibleArgumentsForPlaceholders4() {
+ assertScript '''
+ import static java.util.concurrent.ConcurrentHashMap.newKeySet
+
+ void accept(Collection<Integer> integers) {
+ assert integers
+ }
+
+ Set<Integer> integers = newKeySet()
+ integers.add(42)
+ accept(integers)
+ '''
+ }
+
+ void testCompatibleArgumentsForPlaceholders5() {
assertScript '''
def <X> X foo(X x) {
}
@@ -2362,7 +2417,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
}
// GROOVY-10482
- void testCompatibleArgumentsForPlaceholders2() {
+ void testCompatibleArgumentsForPlaceholders6() {
['def', 'public', 'static', '@Deprecated'].each {
assertScript """
class Foo<X> {
@@ -2379,13 +2434,100 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
}
}
+ // GROOVY-10499
+ void testCompatibleArgumentsForPlaceholders7() {
+ ['?', 'Y', '? extends Y'].each {
+ assertScript """
+ class Foo<X> {
+ Foo(X x) {
+ }
+ }
+ class Bar<Y> {
+ Bar(Foo<${it}> foo, Y y) {
+ }
+ Y baz(Y y) {
+ }
+ }
+ def <Z> void test(Z z = null) {
+ new Bar<>(new Foo<Z>(z), z).baz(z) // Cannot find matching method Bar#baz(Z)
+ }
+ test()
+ """
+ }
+ }
+
+ // GROOVY-10100
+ void testCompatibleArgumentsForPlaceholders8() {
+ assertScript '''
+ import java.util.function.Function
+
+ class C<T> {
+ T m(Object... args) {
+ }
+ }
+ def <T extends Number> void test() {
+ C<T> c = new C<>()
+ Function<String[], T> f = c.&m
+ T t = f.apply(["string"] as String[]) // Cannot assign value of type java.lang.String[] to variable of type T
+ }
+ test()
+ '''
+ }
+
+ // GROOVY-10115
+ void testCompatibleArgumentsForPlaceholders9() {
+ assertScript '''
+ class C<X, T extends X> {
+ T t
+ C(T t) {
+ this.t = t
+ }
+ }
+ new C(null)
+ '''
+ }
+
+ // GROOVY-10116
+ void testCompatibleArgumentsForPlaceholders10() {
+ assertScript '''
+ class Foo<X,T> {
+ Foo(Bar<X,T> bar) {
+ }
+ }
+ class Bar<X,T> {
+ }
+ class Baz<T> {
+ void test() {
+ Foo<T,Long> x = new Foo<T,Long>(new Bar<T,Long>()) // Cannot call Foo#<init>(Bar<T,Long>) with arguments [Bar<T,Long>]
+ }
+ }
+ new Baz().test()
+ '''
+ }
+
+ @NotYetImplemented // GROOVY-10153
+ void testCompatibleArgumentsForPlaceholders11() {
+ ['A', 'B', 'C'].each { T ->
+ assertScript """
+ class A {}
+ class B extends A {}
+ class C extends B {}
+ class Foo<T extends A> {}
+
+ Foo<? super C> foo = new Foo<$T>()
+ // ^ lower bound is C (explicit); upper bound is A (implicit)
+ """
+ }
+ }
+
void testIncompatibleGenericsForTwoArguments() {
shouldFailWithMessages '''
public <T> void printEqual(T arg1, T arg2) {
println arg1 == arg2
}
printEqual(1, 'foo')
- ''', '#printEqual(T, T) with arguments [int, java.lang.String]'
+ ''',
+ '#printEqual(T, T) with arguments [int, java.lang.String]'
}
void testIncompatibleGenericsForTwoArgumentsUsingEmbeddedPlaceholder() {
@@ -2394,11 +2536,12 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
println arg1 == arg2
}
printEqual(1, ['foo'])
- ''', '#printEqual(T, java.util.List <T>) with arguments [int, java.util.List <java.lang.String>]'
+ ''',
+ '#printEqual(T, java.util.List <T>) with arguments [int, java.util.List <java.lang.String>]'
}
- // GROOVY-9902
- void testIncompatibleArgumentsForGenericArgument_IncludingDelegation() {
+ // GROOVY-9902: incomplete generics should not stop type checking
+ void testIncompatibleArgumentsForPlaceholders3() {
shouldFailWithMessages '''
class Holder<Unknown> {
TypedProperty<Number, Unknown> numberProperty = prop(Number)
@@ -2494,7 +2637,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
assert twoIntegers.getTop() == 2
def oneIntegerAgain = stack.push(1).push(2).pop()
- assert oneIntegerAgain.getTop() == 1 // BOOM!!!!
+ assert oneIntegerAgain.getTop() == 1 // Cannot find matching method IStack#getTop()
'''
}
@@ -2511,14 +2654,14 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
// GROOVY-5735
void testCorrespondingParameterType() {
assertScript '''
- public <T> void someMethod (java.lang.Class<T> clazz, T object) {}
+ public <T> void someMethod (java.lang.Class<T> clazz, T object) {}
- void method() {
- List<String> list = null
- someMethod(java.util.List.class, list)
- }
+ void method() {
+ List<String> list = null
+ someMethod(java.util.List.class, list)
+ }
- method()
+ method()
'''
}