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/04/07 18:02:39 UTC
[groovy] branch GROOVY_2_5_X updated: GROOVY-9033: STC: resolve "def list = []" to List
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 316ea3e1ac GROOVY-9033: STC: resolve "def list = []" to List<Object> not List<E>
316ea3e1ac is described below
commit 316ea3e1ac1c38cf247047eff6c6898861f4cd65
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Wed Apr 6 15:36:08 2022 -0500
GROOVY-9033: STC: resolve "def list = []" to List<Object> not List<E>
GROOVY-10089, GROOVY-10235, GROOVY-10324, GROOVY-10367
2_5_X backport
---
.../transform/stc/StaticTypeCheckingSupport.java | 54 ++--
.../transform/stc/StaticTypeCheckingVisitor.java | 95 +++----
src/test/groovy/bugs/Groovy8609Bug.groovy | 176 ++++++------
.../groovy/transform/stc/GenericsSTCTest.groovy | 84 +++++-
.../transform/stc/TypeInferenceSTCTest.groovy | 313 +++++++++++++--------
.../classgen/asm/sc/BugsStaticCompileTest.groovy | 4 +-
.../asm/sc/TypeInferenceStaticCompileTest.groovy | 68 +----
7 files changed, 431 insertions(+), 363 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 95af881c87..67980dc670 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -1346,7 +1346,7 @@ public abstract class StaticTypeCheckingSupport {
* Given a generics type representing SomeClass<T,V> and a resolved placeholder map, returns a new generics type
* for which placeholders are resolved recursively.
*/
- protected static GenericsType fullyResolve(GenericsType gt, Map<GenericsTypeName, GenericsType> placeholders) {
+ protected static GenericsType fullyResolve(GenericsType gt, final Map<GenericsTypeName, GenericsType> placeholders) {
GenericsType fromMap = placeholders.get(new GenericsTypeName(gt.getName()));
if (gt.isPlaceholder() && fromMap != null) {
gt = fromMap;
@@ -1370,31 +1370,37 @@ public abstract class StaticTypeCheckingSupport {
}
protected static ClassNode fullyResolveType(final ClassNode type, final Map<GenericsTypeName, GenericsType> placeholders) {
- if (type.isUsingGenerics() && !type.isGenericsPlaceHolder()) {
- GenericsType[] gts = type.getGenericsTypes();
- if (gts != null) {
- GenericsType[] copy = new GenericsType[gts.length];
- for (int i = 0; i < gts.length; i++) {
- GenericsType genericsType = gts[i];
- if (genericsType.isPlaceholder() && placeholders.containsKey(new GenericsTypeName(genericsType.getName()))) {
- copy[i] = placeholders.get(new GenericsTypeName(genericsType.getName()));
- } else {
- copy[i] = fullyResolve(genericsType, placeholders);
+ if (type.isArray()) {
+ return fullyResolveType(type.getComponentType(), placeholders).makeArray();
+ }
+ if (type.isUsingGenerics()) {
+ if (type.isGenericsPlaceHolder()) {
+ GenericsType gt = placeholders.get(new GenericsTypeName(type.getUnresolvedName()));
+ if (gt != null) {
+ return gt.getType();
+ }
+ ClassNode cn = type.redirect();
+ return cn != type ? cn : OBJECT_TYPE;
+ } else {
+ GenericsType[] gts = type.getGenericsTypes();
+ if (gts != null) { final int n = gts.length;
+ GenericsType[] copy = new GenericsType[n];
+ for (int i = 0; i < n; i += 1) {
+ GenericsType gt = gts[i];
+ if (gt.isPlaceholder()) {
+ GenericsTypeName gtn = new GenericsTypeName(gt.getName());
+ copy[i] = placeholders.containsKey(gtn)
+ ? placeholders.get(gtn) : extractType(gt).asGenericsType();
+ } else {
+ copy[i] = fullyResolve(gt, placeholders);
+ }
}
+ gts = copy;
}
- gts = copy;
+ ClassNode cn = type.getPlainNodeReference();
+ cn.setGenericsTypes(gts);
+ return cn;
}
- ClassNode result = type.getPlainNodeReference();
- result.setGenericsTypes(gts);
- return result;
- } else if (type.isUsingGenerics() && OBJECT_TYPE.equals(type) && type.getGenericsTypes() != null) {
- // Object<T>
- GenericsType genericsType = placeholders.get(new GenericsTypeName(type.getGenericsTypes()[0].getName()));
- if (genericsType != null) {
- return genericsType.getType();
- }
- } else if (type.isArray()) {
- return fullyResolveType(type.getComponentType(), placeholders).makeArray();
}
return type;
}
@@ -1804,7 +1810,7 @@ public abstract class StaticTypeCheckingSupport {
public static ClassNode getCorrectedClassNode(ClassNode type, ClassNode superClass, boolean handlingGenerics) {
ClassNode corrected;
- if (handlingGenerics && missesGenericsTypes(type)) {
+ if (handlingGenerics && GenericsUtils.hasUnresolvedGenerics(type)) {
corrected = superClass.getPlainNodeReference();
} else {
corrected = GenericsUtils.correctToGenericsSpecRecurse(GenericsUtils.createGenericsSpec(type), superClass);
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 6a8a4ee826..37445a392f 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -185,7 +185,6 @@ 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.thisPropX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
-import static org.codehaus.groovy.ast.tools.GenericsUtils.toGenericTypesString;
import static org.codehaus.groovy.ast.tools.WideningCategories.isBigDecCategory;
import static org.codehaus.groovy.ast.tools.WideningCategories.isBigIntCategory;
import static org.codehaus.groovy.ast.tools.WideningCategories.isDouble;
@@ -836,20 +835,6 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
}
}
- if (lType.isUsingGenerics() && missesGenericsTypes(resultType) && isAssignment(op)) {
- // unchecked assignment
- // examples:
- // List<A> list = []
- // List<A> list = new LinkedList()
- // Iterable<A> list = new LinkedList()
-
- // in that case, the inferred type of the binary expression is the type of the RHS
- // "completed" with generics type information available in the LHS
- ClassNode completedType = GenericsUtils.parameterizeType(lType, resultType.getPlainNodeReference());
-
- resultType = completedType;
- }
-
if (isArrayOp(op)
&& !lType.isArray()
&& enclosingBinaryExpression != null
@@ -868,21 +853,42 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
addNoMatchingMethodError(lType, "putAt", arguments, enclosingBinaryExpression);
}
}
- boolean isEmptyDeclaration = expression instanceof DeclarationExpression && rightExpression instanceof EmptyExpression;
+
+ boolean isEmptyDeclaration = (expression instanceof DeclarationExpression && rightExpression instanceof EmptyExpression);
if (!isEmptyDeclaration && isAssignment(op)) {
- if (rightExpression instanceof ConstructorCallExpression) {
+ if (rightExpression instanceof ConstructorCallExpression)
inferDiamondType((ConstructorCallExpression) rightExpression, lType);
+
+ if (lType.isUsingGenerics() && missesGenericsTypes(resultType)) {
+ // unchecked assignment
+ // List<Type> list = new LinkedList()
+ // Iterable<Type> iter = new LinkedList()
+ // Collection<Type> coll = Collections.emptyList()
+ // Collection<Type> view = ConcurrentHashMap.newKeySet()
+
+ // the inferred type of the binary expression is the type of the RHS
+ // "completed" with generics type information available from the LHS
+ if (lType.equals(resultType)) {
+ if (!lType.isGenericsPlaceHolder()) resultType = lType;
+ } else if (!resultType.isGenericsPlaceHolder()) { // GROOVY-10235, GROOVY-10324, et al.
+ Map<GenericsTypeName, GenericsType> gt = new HashMap<>();
+ extractGenericsConnections(gt, resultType, resultType.redirect());
+ ClassNode sc = resultType;
+ do { sc = getNextSuperClass(sc, lType);
+ } while (sc != null && !sc.equals(lType));
+ extractGenericsConnections(gt, lType, sc);
+
+ resultType = applyGenericsContext(gt, resultType.redirect());
+ }
}
ClassNode originType = getOriginalDeclarationType(leftExpression);
typeCheckAssignment(expression, leftExpression, originType, rightExpression, resultType);
- // if assignment succeeds but result type is not a subtype of original type, then we are in a special cast handling
- // and we must update the result type
- if (!implementsInterfaceOrIsSubclassOf(getWrapper(resultType), getWrapper(originType))) {
+ // check for implicit conversion like "String a = 123", "int[] b = [1,2,3]", "List c = [].stream()", etc.
+ if (!implementsInterfaceOrIsSubclassOf(wrapTypeIfNecessary(resultType), wrapTypeIfNecessary(originType))) {
resultType = originType;
- } else if (lType.isUsingGenerics() && !lType.isEnum() && hasRHSIncompleteGenericTypeInfo(resultType)) {
- // for example, LHS is List<ConcreteClass> and RHS is List<T> where T is a placeholder
- resultType = lType;
+ } else if (isPrimitiveType(originType) && resultType.equals(getWrapper(originType))) {
+ resultType = originType; // retain primitive semantics
} else {
// GROOVY-7549: RHS type may not be accessible to enclosing class
int modifiers = resultType.getModifiers();
@@ -891,14 +897,13 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
&& !getOutermost(enclosingType).equals(getOutermost(resultType))
&& (Modifier.isPrivate(modifiers) || !Objects.equals(enclosingType.getPackageName(), resultType.getPackageName()))) {
resultType = originType; // TODO: Find accesible type in hierarchy of resultType?
+ } else if (GenericsUtils.hasUnresolvedGenerics(resultType)) { // GROOVY-9033, GROOVY-10089, et al.
+ Map<GenericsTypeName, GenericsType> enclosing = extractGenericsParameterMapOfThis(typeCheckingContext);
+ if (enclosing == null) enclosing = Collections.emptyMap();
+ resultType = fullyResolveType(resultType, enclosing);
}
}
- // make sure we keep primitive types
- if (isPrimitiveType(originType) && resultType.equals(getWrapper(originType))) {
- resultType = originType;
- }
-
// track conditional assignment
if (!isNullConstant(rightExpression)
&& leftExpression instanceof VariableExpression
@@ -930,8 +935,6 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
}
}
}
-
-
} else if (op == KEYWORD_INSTANCEOF) {
pushInstanceOfTypeInfo(leftExpression, rightExpression);
}
@@ -1249,9 +1252,9 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
private void checkTypeGenerics(ClassNode leftExpressionType, ClassNode wrappedRHS, Expression rightExpression) {
// last, check generic type information to ensure that inferred types are compatible
if (!leftExpressionType.isUsingGenerics()) return;
- // List<Foo> l = new List() is an example for incomplete generics type info
- // we assume arity related errors are already handled here.
- if (hasRHSIncompleteGenericTypeInfo(wrappedRHS)) return;
+ // example of incomplete type info: "List<Type> list = new LinkedList()"
+ // we assume arity related errors are already handled here
+ if (missesGenericsTypes(wrappedRHS)) return;
GenericsType gt = GenericsUtils.buildWildcardType(leftExpressionType);
if (UNKNOWN_PARAMETER_TYPE.equals(wrappedRHS) ||
@@ -1335,6 +1338,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
typeCheckingContext.popEnclosingBinaryExpression();
}
+ @Deprecated
protected static boolean hasRHSIncompleteGenericTypeInfo(final ClassNode inferredRightExpressionType) {
boolean replaceType = false;
GenericsType[] genericsTypes = inferredRightExpressionType.getGenericsTypes();
@@ -5403,7 +5407,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
return declaringClass;
}
- private Map<GenericsTypeName, GenericsType> resolvePlaceHoldersFromDeclaration(ClassNode receiver, ClassNode declaration, MethodNode method, boolean isStaticTarget) {
+ private static Map<GenericsTypeName, GenericsType> resolvePlaceHoldersFromDeclaration(ClassNode receiver, ClassNode declaration, MethodNode method, boolean isStaticTarget) {
Map<GenericsTypeName, GenericsType> resolvedPlaceholders;
if (isStaticTarget && CLASS_Type.equals(receiver) &&
receiver.isUsingGenerics() &&
@@ -5488,35 +5492,28 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
return resolvedPlaceholders;
}
- protected boolean typeCheckMethodsWithGenericsOrFail(ClassNode receiver, ClassNode[] arguments, MethodNode candidateMethod, Expression location) {
+ protected boolean typeCheckMethodsWithGenericsOrFail(final ClassNode receiver, final ClassNode[] arguments, final MethodNode candidateMethod, final Expression location) {
if (!typeCheckMethodsWithGenerics(receiver, arguments, candidateMethod)) {
- Map<GenericsTypeName, GenericsType> spec = GenericsUtils.extractPlaceholders(receiver);
+ Map<GenericsTypeName, GenericsType> generics = GenericsUtils.extractPlaceholders(receiver);
+ applyGenericsConnections(extractGenericsParameterMapOfThis(typeCheckingContext), generics);
+ addMethodLevelDeclaredGenerics(candidateMethod, generics);
Parameter[] parameters = candidateMethod.getParameters();
ClassNode[] paramTypes = new ClassNode[parameters.length];
for (int i = 0, n = parameters.length; i < n; i += 1) {
- paramTypes[i] = fullyResolveType(parameters[i].getType(), spec);
+ paramTypes[i] = fullyResolveType(parameters[i].getType(), generics);
// GROOVY-10010: check for List<String> parameter and ["foo","$bar"] argument
if (i < arguments.length && hasGStringStringError(paramTypes[i], arguments[i], location)) {
return false;
}
}
- addStaticTypeError("Cannot call " + toMethodGenericTypesString(candidateMethod) + receiver.toString(false) + "#" +
- toMethodParametersString(candidateMethod.getName(), paramTypes) +
- " with arguments " + formatArgumentList(arguments), location);
+ GenericsType[] mgt = candidateMethod.getGenericsTypes();
+ addStaticTypeError("Cannot call " + (mgt == null ? "" : GenericsUtils.toGenericTypesString(mgt)) + receiver.toString(false) + "#" +
+ toMethodParametersString(candidateMethod.getName(), paramTypes) + " with arguments " + formatArgumentList(arguments), location);
return false;
}
return true;
}
- private static String toMethodGenericTypesString(MethodNode node) {
- GenericsType[] genericsTypes = node.getGenericsTypes();
-
- if (genericsTypes == null)
- return "";
-
- return toGenericTypesString(genericsTypes);
- }
-
protected static String formatArgumentList(ClassNode[] nodes) {
if (nodes == null || nodes.length == 0) return "[] ";
StringBuilder sb = new StringBuilder(24 * nodes.length);
diff --git a/src/test/groovy/bugs/Groovy8609Bug.groovy b/src/test/groovy/bugs/Groovy8609Bug.groovy
index bfc021fe85..593d6d9c36 100644
--- a/src/test/groovy/bugs/Groovy8609Bug.groovy
+++ b/src/test/groovy/bugs/Groovy8609Bug.groovy
@@ -18,128 +18,122 @@
*/
package groovy.bugs
-import gls.CompilableTestSupport
+final class Groovy8609Bug extends GroovyTestCase {
-class Groovy8609Bug extends CompilableTestSupport {
void testUpperBoundWithGenerics() {
assertScript '''
- @groovy.transform.CompileStatic
- public class A<T extends List<E>, E extends Map<String, Integer>> {
- E getFirstRecord(T recordList) {
- return recordList.get(0)
- }
-
- static void main(args) {
- def list = new ArrayList<HashMap<String, Integer>>()
- def record = new HashMap<String, Integer>()
- list.add(record)
- def a = new A<ArrayList<HashMap<String, Integer>>, HashMap<String, Integer>>()
- assert record.is(a.getFirstRecord(list))
+ @groovy.transform.CompileStatic
+ public class A<T extends List<E>, E extends Map<String, Integer>> {
+ E getFirstRecord(T recordList) {
+ return recordList.get(0)
+ }
+
+ static void main(args) {
+ def list = new ArrayList<HashMap<String, Integer>>()
+ def record = new HashMap<String, Integer>()
+ list.add(record)
+ def a = new A<ArrayList<HashMap<String, Integer>>, HashMap<String, Integer>>()
+ assert record.is(a.getFirstRecord(list))
+ }
}
- }
'''
}
void testUpperBoundWithoutGenerics() {
assertScript '''
- @groovy.transform.CompileStatic
- public class A<T extends List<E>, E extends Map> {
- E getFirstRecord(T recordList) {
- return recordList.get(0);
- }
-
- static void main(args) {
- def list = new ArrayList<HashMap<String, Integer>>()
- def record = new HashMap<String, Integer>()
- list.add(record)
- def a = new A<ArrayList<HashMap<String, Integer>>, HashMap<String, Integer>>()
- assert record.is(a.getFirstRecord(list))
+ @groovy.transform.CompileStatic
+ public class A<T extends List<E>, E extends Map> {
+ E getFirstRecord(T recordList) {
+ return recordList.get(0);
+ }
+
+ static void main(args) {
+ def list = new ArrayList<HashMap<String, Integer>>()
+ def record = new HashMap<String, Integer>()
+ list.add(record)
+ def a = new A<ArrayList<HashMap<String, Integer>>, HashMap<String, Integer>>()
+ assert record.is(a.getFirstRecord(list))
+ }
}
- }
'''
}
void testNoUpperBound() {
assertScript '''
- @groovy.transform.CompileStatic
- public class A<T extends List<E>, E> {
- E getFirstRecord(T recordList) {
- return recordList.get(0);
- }
-
- static void main(args) {
- def list = new ArrayList<HashMap<String, Integer>>()
- def record = new HashMap<String, Integer>()
- list.add(record)
- def a = new A<ArrayList<HashMap<String, Integer>>, HashMap<String, Integer>>()
- assert record.is(a.getFirstRecord(list))
+ @groovy.transform.CompileStatic
+ public class A<T extends List<E>, E> {
+ E getFirstRecord(T recordList) {
+ return recordList.get(0);
+ }
+
+ static void main(args) {
+ def list = new ArrayList<HashMap<String, Integer>>()
+ def record = new HashMap<String, Integer>()
+ list.add(record)
+ def a = new A<ArrayList<HashMap<String, Integer>>, HashMap<String, Integer>>()
+ assert record.is(a.getFirstRecord(list))
+ }
}
- }
'''
}
void testUpperBoundWithGenericsThroughWrongType() {
- def errMsg = shouldFail '''
- @groovy.transform.CompileStatic
- public class A<T extends List<E>, E extends Map<String, Integer>> {
- E getFirstRecord(T recordList) {
- return recordList.get(0)
- }
-
- static void main(args) {
- def list = new ArrayList<TreeMap<String, Integer>>()
- def record = new TreeMap<String, Integer>()
- list.add(record)
- def a = new A<ArrayList<HashMap<String, Integer>>, HashMap<String, Integer>>()
- assert record.is(a.getFirstRecord(list))
+ def err = shouldFail '''
+ @groovy.transform.CompileStatic
+ public class A<T extends List<E>, E extends Map<String, Integer>> {
+ E getFirstRecord(T recordList) {
+ return recordList.get(0)
+ }
+
+ static void main(args) {
+ def list = new ArrayList<TreeMap<String, Integer>>()
+ def record = new TreeMap<String, Integer>()
+ list.add(record)
+ def a = new A<ArrayList<HashMap<String, Integer>>, HashMap<String, Integer>>()
+ assert record.is(a.getFirstRecord(list))
+ }
}
- }
'''
-
- assert errMsg.contains('[Static type checking] - Cannot call A <ArrayList, HashMap>#getFirstRecord(T) with arguments [java.util.ArrayList <TreeMap>]')
+ assert err.contains('#getFirstRecord(java.util.ArrayList <HashMap>)')
}
void testUpperBoundWithGenericsThroughWrongType2() {
- def errMsg = shouldFail '''
- @groovy.transform.CompileStatic
- public class A<T extends List<E>, E extends Map<String, Integer>> {
- E getFirstRecord(T recordList) {
- return recordList.get(0)
- }
-
- static void main(args) {
- def list = new ArrayList<HashMap<String, Long>>()
- def record = new HashMap<String, Long>()
- list.add(record)
- def a = new A<ArrayList<HashMap<String, Integer>>, HashMap<String, Integer>>()
- assert record.is(a.getFirstRecord(list))
+ def err = shouldFail '''
+ @groovy.transform.CompileStatic
+ public class A<T extends List<E>, E extends Map<String, Integer>> {
+ E getFirstRecord(T recordList) {
+ return recordList.get(0)
+ }
+
+ static void main(args) {
+ def list = new ArrayList<HashMap<String, Long>>()
+ def record = new HashMap<String, Long>()
+ list.add(record)
+ def a = new A<ArrayList<HashMap<String, Integer>>, HashMap<String, Integer>>()
+ assert record.is(a.getFirstRecord(list))
+ }
}
- }
'''
-
- // TODO we should print generics details, e.g. [Static type checking] - Cannot call A <ArrayList, HashMap<String, Integer>>#getFirstRecord(T) with arguments [java.util.ArrayList <HashMap<String, Long>>]
- assert errMsg.contains('[Static type checking] - Cannot call A <ArrayList, HashMap>#getFirstRecord(T) with arguments [java.util.ArrayList <HashMap>]')
+ assert err.contains('#getFirstRecord(java.util.ArrayList <HashMap>)')
}
void testUpperBoundWithGenericsThroughWrongType3() {
- def errMsg = shouldFail '''
- @groovy.transform.CompileStatic
- public class A<T extends List<E>, E extends Map<String, Integer>> {
- E getFirstRecord(T recordList) {
- return recordList.get(0)
- }
-
- static void main(args) {
- def list = new ArrayList<HashMap<StringBuffer, Integer>>()
- def record = new HashMap<StringBuffer, Integer>()
- list.add(record)
- def a = new A<ArrayList<HashMap<String, Integer>>, HashMap<String, Integer>>()
- assert record.is(a.getFirstRecord(list))
+ def err = shouldFail '''
+ @groovy.transform.CompileStatic
+ public class A<T extends List<E>, E extends Map<String, Integer>> {
+ E getFirstRecord(T recordList) {
+ return recordList.get(0)
+ }
+
+ static void main(args) {
+ def list = new ArrayList<HashMap<StringBuffer, Integer>>()
+ def record = new HashMap<StringBuffer, Integer>()
+ list.add(record)
+ def a = new A<ArrayList<HashMap<String, Integer>>, HashMap<String, Integer>>()
+ assert record.is(a.getFirstRecord(list))
+ }
}
- }
'''
-
- // TODO we should print generics details, e.g. [Static type checking] - Cannot call A <ArrayList, HashMap<String, Integer>>#getFirstRecord(T) with arguments [java.util.ArrayList <HashMap<StringBuffer, Integer>>]
- assert errMsg.contains('[Static type checking] - Cannot call A <ArrayList, HashMap>#getFirstRecord(T) with arguments [java.util.ArrayList <HashMap>]')
+ assert err.contains('#getFirstRecord(java.util.ArrayList <HashMap>)')
}
}
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 6516ecb130..b8e6237387 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -198,16 +198,16 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
// GROOVY-10051
void testReturnTypeInferenceWithMethodGenericsAndBounds() {
assertScript '''
- abstract class State<H extends Handle> {
- // Why not return HandleContainer<H>? I can't really say.
- def <T extends Handle> HandleContainer<T> getHandleContainer(key) {
- }
+ interface Handle {
+ Result getResult()
}
class HandleContainer<H extends Handle> {
H handle
}
- interface Handle {
- Result getResult()
+ abstract class State<H extends Handle> {
+ // Why not return HandleContainer<H>? I can't really say.
+ def <T extends Handle> HandleContainer<T> getHandleContainer(key) {
+ }
}
class Result {
int itemCount
@@ -230,6 +230,53 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
assert getStrings(null,[]).isEmpty()
'''
+
+ assertScript '''
+ interface Handle {
+ int getCount()
+ }
+ class HandleContainer<H extends Handle> {
+ H handle
+ }
+ interface Input {
+ HandleContainer<? extends Handle> getResult(key)
+ }
+ interface State {
+ def <X extends Handle> HandleContainer<X> getResult(key)
+ }
+
+ void test(Input input, State state) {
+ def container = state.getResult('k') ?: input.getResult('k')
+ Handle handle = container.handle
+ Integer count = handle.count
+ assert count == 1
+ }
+
+ Handle h = {->1}
+ def c = new HandleContainer(handle: h)
+ test({k->c} as Input, {k->c} as State)
+ '''
+ }
+
+ // GROOVY-9033
+ void testReturnTypeInferenceWithMethodGenerics8() {
+ shouldFailWithMessages '''
+ List<String> test() {
+ def x = [].each { }
+ x.add(new Object())
+ return x // List<E>
+ }
+ ''',
+ 'Incompatible generic argument types.' // Cannot assign java.util.List<java.lang.Object> to: java.util.List<java.lang.String>
+
+ assertScript '''
+ @ASTTest(phase=INSTRUCTION_SELECTION, value={
+ def type = node.getNodeMetaData(INFERRED_TYPE)
+ assert type.genericsTypes[0].toString() == 'java.lang.String'
+ assert type.genericsTypes[1].toString() == 'java.util.List<java.lang.Object>' // not List<E>
+ })
+ def map = [ key: [] ]
+ '''
}
void testDiamondInferrenceFromConstructor1() {
@@ -256,6 +303,31 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
'''
}
+ // GROOVY-10324
+ void testDiamondInferrenceFromConstructor20() {
+ assertScript '''
+ class C<T> {
+ }
+ def <X> X m(C<X> c) {
+ }
+ List<String> x = m(new C<>())
+ '''
+ }
+
+ // GROOVY-10367
+ void testDiamondInferrenceFromConstructor26() {
+ assertScript '''
+ @groovy.transform.TupleConstructor(defaults=false)
+ class C<X, Y extends X> { // works without Y
+ X x
+ }
+ def <Z extends Number> void test(Z z) {
+ z = new C<>(z).x // Cannot assign value of type Object to variable of type Z
+ }
+ test(null)
+ '''
+ }
+
// GROOVY-10280
void testTypeArgumentPropagation() {
assertScript '''
diff --git a/src/test/groovy/transform/stc/TypeInferenceSTCTest.groovy b/src/test/groovy/transform/stc/TypeInferenceSTCTest.groovy
index 8fe23b8a1f..ac10b0501c 100644
--- a/src/test/groovy/transform/stc/TypeInferenceSTCTest.groovy
+++ b/src/test/groovy/transform/stc/TypeInferenceSTCTest.groovy
@@ -35,10 +35,10 @@ import org.codehaus.groovy.transform.stc.StaticTypesMarker
class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
void testStringToInteger() {
- assertScript """
- def name = "123" // we want type inference
- name.toInteger() // toInteger() is defined by DGM
- """
+ assertScript '''
+ def name = "123" // we want type inference
+ name.toInteger() // toInteger() is defined by DGM
+ '''
}
// GROOVY-9935
@@ -62,76 +62,72 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
'''
}
- void testAnnotationOnSingleMethod() {
- GroovyShell shell = new GroovyShell()
- shell.evaluate '''
- // calling a method which has got some dynamic stuff in it
-
- import groovy.transform.TypeChecked
- import groovy.xml.MarkupBuilder
+ void testDynamicMethodWithinTypeCheckedClass() {
+ assertScript '''
+ import groovy.transform.*
- class Greeter {
- @TypeChecked
- String greeting(String name) {
- generateMarkup(name.toUpperCase())
+ class C {
+ String m(String s) {
+ generateMarkup(s.toUpperCase())
}
- // MarkupBuilder is dynamic so we won't do typechecking here
- String generateMarkup(String name) {
+ // MarkupBuilder is dynamic so skip type-checking
+ @TypeChecked(TypeCheckingMode.SKIP)
+ String generateMarkup(String s) {
def sw = new StringWriter()
- def mkp = new MarkupBuilder()
- mkp.html {
+ def mb = new groovy.xml.MarkupBuilder(sw)
+ mb.html {
body {
- div name
+ div s
}
}
- sw
+ sw.toString()
}
}
- def g = new Greeter()
- g.greeting("Guillaume")
-
+ def c = new C()
+ def xml = c.m('x')
+ // TODO: check XML
'''
}
void testInstanceOf() {
- assertScript """
- Object o
- if (o instanceof String) o.toUpperCase()
- """
+ assertScript '''
+ Object o
+ if (o instanceof String) o.toUpperCase()
+ '''
}
void testEmbeddedInstanceOf() {
- assertScript """
- Object o
- if (o instanceof Object) {
- if (o instanceof String) {
- o.toUpperCase()
+ assertScript '''
+ Object o
+ if (o instanceof Object) {
+ if (o instanceof String) {
+ o.toUpperCase()
+ }
}
- }
- """
+ '''
}
void testEmbeddedInstanceOf2() {
- assertScript """
- Object o
- if (o instanceof String) {
- if (true) {
- o.toUpperCase()
+ assertScript '''
+ Object o
+ if (o instanceof String) {
+ if (true) {
+ o.toUpperCase()
+ }
}
- }
- """
+ '''
}
void testEmbeddedInstanceOf3() {
shouldFailWithMessages '''
- Object o
- if (o instanceof String) {
- if (o instanceof Object) { // causes the inferred type of 'o' to be overwritten
- o.toUpperCase()
+ Object o
+ if (o instanceof String) {
+ if (o instanceof Object) { // causes the inferred type of 'o' to be overwritten
+ o.toUpperCase()
+ }
}
- }
''', 'Cannot find matching method java.lang.Object#toUpperCase()'
}
@@ -151,7 +147,7 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
if (o instanceof String) {
o.toUpperCase()
} else {
- o.toUpperCase() // ensure that type information is lost in else()
+ o.toUpperCase() // ensure that type information is lost in else
}
''', 'Cannot find matching method java.lang.Object#toUpperCase()'
}
@@ -166,6 +162,7 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
}
class Task<R extends Face> implements java.util.concurrent.Callable<String> {
R request
+
@Override
String call() {
if (request instanceof Impl) {
@@ -193,7 +190,6 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
void foo2() { println 'ok 2' }
}
-
def o = new A()
if (o instanceof A) {
@@ -207,7 +203,6 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
if (o instanceof A || o instanceof B) {
o.foo()
}
-
'''
}
@@ -216,16 +211,11 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
class A {
int foo() { 1 }
}
-
class B {
int foo2() { 2 }
}
-
-
def o = new A()
-
int result = o instanceof A?o.foo():(o instanceof B?o.foo2():3)
-
'''
}
@@ -516,7 +506,7 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
'''
}
- void testShouldNotFailWithWithAndExplicitTypedIt() {
+ void testShouldFailWithWithAndWrongExplicitIt() {
shouldFailWithMessages '''
class A {
int x
@@ -556,8 +546,7 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
'''
}
-
- void testCallMethodInWithContextAndShadowing() {
+ void testCallMethodInWithContextAndShadowing() {
// make sure that the method which is found in 'with' is actually the one from class A
// which returns a String
assertScript '''
@@ -572,6 +561,7 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
method().toUpperCase()
}
'''
+
// check that if we switch signatures, it fails
shouldFailWithMessages '''
class A {
@@ -636,8 +626,6 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
}
'''
assert method.code.statements[0].expression.leftExpression.getNodeMetaData(StaticTypesMarker.DECLARATION_INFERRED_TYPE) == ClassHelper.make(HashSet)
-
-
}
void testChooseMethodWithTypeInference() {
@@ -685,11 +673,9 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
}
void testFlowTypingWithStringVariable() {
- // as anything can be assigned to a string, flow typing engine
- // could "erase" the type of the original variable although is must not
assertScript '''
- String str = new Object() // type checker will not complain, anything assignable to a String
- str.toUpperCase() // should not complain
+ String s = new Object() // anything assignable to String
+ s.toUpperCase()
'''
}
@@ -698,7 +684,7 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
def o
o = 1L
o = 2
- @ASTTest(phase=INSTRUCTION_SELECTION, value= {
+ @ASTTest(phase=INSTRUCTION_SELECTION, value={
assert node.rightExpression.accessedVariable.getNodeMetaData(DECLARATION_INFERRED_TYPE) == long_TYPE
})
def z = o
@@ -756,7 +742,8 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
}
void testSwitchCaseAnalysis() {
- assertScript '''import org.codehaus.groovy.ast.tools.WideningCategories.LowestUpperBoundClassNode as LUB
+ assertScript '''
+ import org.codehaus.groovy.ast.tools.WideningCategories.LowestUpperBoundClassNode as LUB
def method(int x) {
def returnValue= new Date()
@@ -768,10 +755,10 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
returnValue = 1;
break;
}
- @ASTTest(phase=INSTRUCTION_SELECTION,value={
- def ift = node.getNodeMetaData(INFERRED_TYPE)
- assert ift instanceof LUB
- assert ift.name == 'java.io.Serializable'
+ @ASTTest(phase=INSTRUCTION_SELECTION, value={
+ def type = node.getNodeMetaData(INFERRED_TYPE)
+ assert type instanceof LUB
+ assert type.name == 'java.io.Serializable'
})
def val = returnValue
@@ -780,32 +767,33 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
'''
}
- void testGroovy6215() {
+ // GROOVY-6215
+ void testSwitchCaseAnalysis2() {
assertScript '''
- def processNumber(int x) {
- def value = getValueForNumber(x)
- value
- }
+ def processNumber(int x) {
+ def value = getValueForNumber(x)
+ value
+ }
- def getValueForNumber(int x) {
- def valueToReturn
- switch(x) {
- case 1:
- valueToReturn = 'One'
- break
- case 2:
- valueToReturn = []
- valueToReturn << 'Two'
- break
- }
- valueToReturn
+ def getValueForNumber(int x) {
+ def valueToReturn
+ switch(x) {
+ case 1:
+ valueToReturn = 'One'
+ break
+ case 2:
+ valueToReturn = []
+ valueToReturn << 'Two'
+ break
}
- def v1 = getValueForNumber(1)
- def v2 = getValueForNumber(2)
- def v3 = getValueForNumber(3)
- assert v1 == 'One'
- assert v2 == ['Two']
- assert v3 == null
+ valueToReturn
+ }
+ def v1 = getValueForNumber(1)
+ def v2 = getValueForNumber(2)
+ def v3 = getValueForNumber(3)
+ assert v1 == 'One'
+ assert v2 == ['Two']
+ assert v3 == null
'''
}
@@ -823,8 +811,8 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
assertScript """
$orig b = 65 as $orig
@ASTTest(phase=INSTRUCTION_SELECTION, value={
- def rit = node.rightExpression.getNodeMetaData(INFERRED_TYPE)
- assert rit == make($dest)
+ def type = node.rightExpression.getNodeMetaData(INFERRED_TYPE)
+ assert type == make($dest)
})
def pp = ++b
println '++${orig} -> ' + pp.class + ' ' + pp
@@ -847,8 +835,8 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
assertScript """
$orig b = 65 as $orig
@ASTTest(phase=INSTRUCTION_SELECTION, value={
- def rit = node.rightExpression.getNodeMetaData(INFERRED_TYPE)
- assert rit == make($dest)
+ def type = node.rightExpression.getNodeMetaData(INFERRED_TYPE)
+ assert type == make($dest)
})
def pp = b++
println '${orig}++ -> ' + pp.class + ' ' + pp
@@ -860,29 +848,29 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
// GROOVY-6522
void testInferenceWithImplicitClosureCoercion() {
assertScript '''
-interface CustomCallable<T> {
- T call()
-}
+ interface CustomCallable<T> {
+ T call()
+ }
-class Thing {
- static <T> T customType(CustomCallable<T> callable) {
- callable.call()
- }
+ class Thing {
+ static <T> T customType(CustomCallable<T> callable) {
+ callable.call()
+ }
- @ASTTest(phase=INSTRUCTION_SELECTION,value={
- lookup('test').each {
- def call = it.expression
- def irt = call.getNodeMetaData(INFERRED_TYPE)
- assert irt == LIST_TYPE
- }
- })
- static void run() {
- test: customType { [] } // return type is not inferred - fails compile
- }
-}
+ @ASTTest(phase=INSTRUCTION_SELECTION, value={
+ lookup('test').each {
+ def call = it.expression
+ def type = call.getNodeMetaData(INFERRED_TYPE)
+ assert type == LIST_TYPE
+ }
+ })
+ static void run() {
+ test: customType { [] } // return type is not inferred - fails compile
+ }
+ }
-Thing.run()
-'''
+ Thing.run()
+ '''
}
void testInferenceWithImplicitClosureCoercionAndArrayReturn() {
@@ -892,7 +880,7 @@ Thing.run()
public <T> T[] intArray(ArrayFactory<T> f) {
f.array()
}
- @ASTTest(phase=INSTRUCTION_SELECTION,value={
+ @ASTTest(phase=INSTRUCTION_SELECTION, value={
assert node.getNodeMetaData(INFERRED_TYPE) == Integer_TYPE.makeArray()
})
def array = intArray { new Integer[8] }
@@ -908,10 +896,10 @@ Thing.run()
f.list()
}
- @ASTTest(phase=INSTRUCTION_SELECTION,value={
- def irt = node.getNodeMetaData(INFERRED_TYPE)
- assert irt == LIST_TYPE
- assert irt.genericsTypes[0].type == Integer_TYPE
+ @ASTTest(phase=INSTRUCTION_SELECTION, value={
+ def type = node.getNodeMetaData(INFERRED_TYPE)
+ assert type == LIST_TYPE
+ assert type.genericsTypes[0].type == Integer_TYPE
})
def res = list { new LinkedList<Integer>() }
assert res.size() == 0
@@ -952,7 +940,7 @@ Thing.run()
void testShouldInferPrimitiveBoolean() {
assertScript '''
def foo(Boolean o) {
- @ASTTest(phase=INSTRUCTION_SELECTION,value={
+ @ASTTest(phase=INSTRUCTION_SELECTION, value={
assert node.getNodeMetaData(INFERRED_TYPE) == boolean_TYPE
})
boolean b = o
@@ -1019,6 +1007,60 @@ Thing.run()
}
}
+ // GROOVY-5655
+ void testByteArrayInference() {
+ assertScript '''
+ @ASTTest(phase=INSTRUCTION_SELECTION, value={
+ assert node.getNodeMetaData(INFERRED_TYPE) == byte_TYPE.makeArray()
+ })
+ def b = "foo".bytes
+ new String(b)
+ '''
+ }
+
+ // GROOVY-
+ void testGetAnnotationFails() {
+ assertScript '''
+ import groovy.transform.*
+ import java.lang.annotation.*
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target([ElementType.FIELD])
+ @interface Ann1 {}
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target([ElementType.FIELD])
+ @interface Ann2 {}
+
+ class A {
+ @Ann2
+ String field
+ }
+
+ @ASTTest(phase=INSTRUCTION_SELECTION, value={
+ lookup('second').each {
+ assert it.expression.getNodeMetaData(INFERRED_TYPE).name == 'Ann2'
+ }
+ })
+ def doit(obj, String propName) {
+ def field = obj.getClass().getDeclaredField(propName)
+ if (field) {
+ @ASTTest(phase=INSTRUCTION_SELECTION, value={
+ assert node.getNodeMetaData(INFERRED_TYPE).name == 'Ann1'
+ })
+ def annotation = field.getAnnotation Ann1
+ if(true) {
+ second: annotation = field.getAnnotation Ann2
+ }
+ return annotation
+ }
+ return null
+ }
+
+ assert Ann2.isAssignableFrom(doit(new A(), "field").class)
+ '''
+ }
+
// GROOVY-9847
void testShouldKeepInferredTypeWhenPrivateInnerClass() {
assertScript '''
@@ -1082,6 +1124,29 @@ Thing.run()
}
meth()
-'''
+ '''
+ }
+
+ // GROOVY-10089
+ void testInferredTypeForMapOfList() {
+ assertScript '''
+ void test(... attributes) {
+ List one = [
+ [id:'x', options:[count:1]]
+ ]
+ List two = attributes.collect {
+ def node = Collections.singletonMap('children', one)
+ if (node) {
+ node = node.get('children').find { child -> child['id'] == 'x' }
+ }
+ // inferred type of node must be something like Map<String,List<...>>
+
+ [id: it['id'], name: node['name'], count: node['options']['count']]
+ // ^^^^^^^^^^^^^^^ GroovyCastException (map ctor for Collection)
+ }
+ }
+
+ test( [id:'x'] )
+ '''
}
}
diff --git a/src/test/org/codehaus/groovy/classgen/asm/sc/BugsStaticCompileTest.groovy b/src/test/org/codehaus/groovy/classgen/asm/sc/BugsStaticCompileTest.groovy
index bb63f47165..69765c6999 100644
--- a/src/test/org/codehaus/groovy/classgen/asm/sc/BugsStaticCompileTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/asm/sc/BugsStaticCompileTest.groovy
@@ -1202,7 +1202,7 @@ final class BugsStaticCompileTest extends BugsSTCTest implements StaticCompilati
def ift = node.getNodeMetaData(INFERRED_TYPE)
assert ift == make(Set)
assert ift.isUsingGenerics()
- assert ift.genericsTypes[0].type==STRING_TYPE
+ assert ift.genericsTypes[0].name == 'java.lang.String'
})
def set = map.keySet()
def key = set[0]
@@ -1214,7 +1214,7 @@ final class BugsStaticCompileTest extends BugsSTCTest implements StaticCompilati
def ift = node.getNodeMetaData(INFERRED_TYPE)
assert ift == make(Set)
assert ift.isUsingGenerics()
- assert ift.genericsTypes[0].name=='K'
+ assert ift.genericsTypes[0].name == 'java.lang.Object'
})
def set = map.keySet()
def key = set[0]
diff --git a/src/test/org/codehaus/groovy/classgen/asm/sc/TypeInferenceStaticCompileTest.groovy b/src/test/org/codehaus/groovy/classgen/asm/sc/TypeInferenceStaticCompileTest.groovy
index 40fc4e7184..afe168980f 100644
--- a/src/test/org/codehaus/groovy/classgen/asm/sc/TypeInferenceStaticCompileTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/asm/sc/TypeInferenceStaticCompileTest.groovy
@@ -21,73 +21,7 @@ package org.codehaus.groovy.classgen.asm.sc
import groovy.transform.stc.TypeInferenceSTCTest
/**
- * Unit tests for static type checking : type inference.
+ * Unit tests for static compilation : type inference.
*/
class TypeInferenceStaticCompileTest extends TypeInferenceSTCTest implements StaticCompilationTestSupport {
-
- // GROOVY-5655
- void testByteArrayInference() {
- assertScript '''
- @ASTTest(phase=INSTRUCTION_SELECTION, value={
- assert node.getNodeMetaData(INFERRED_TYPE) == byte_TYPE.makeArray()
- })
- def b = "foo".bytes
- new String(b)
- '''
- }
-
- @Override
- void testShouldNotThrowIncompatibleArgToFunVerifyError() {
- try {
- super.testShouldNotThrowIncompatibleArgToFunVerifyError()
- } finally {
-// println astTrees
- }
- }
-
- // GROOVY-
- void testGetAnnotationFail() {
- assertScript '''import groovy.transform.*
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- @Retention(RetentionPolicy.RUNTIME)
- @Target([ElementType.FIELD])
- @interface Ann1 {}
- @Retention(RetentionPolicy.RUNTIME)
- @Target([ElementType.FIELD])
- @interface Ann2 {}
-
- class A {
- @Ann2
- String field
- }
-
- @ASTTest(phase=INSTRUCTION_SELECTION,value={
- lookup('second').each {
- assert it.expression.getNodeMetaData(INFERRED_TYPE).name == 'Ann2'
- }
- })
- def doit(obj, String propName) {
- def field = obj.getClass().getDeclaredField propName
- println field
- if(field) {
- @ASTTest(phase=INSTRUCTION_SELECTION,value={
- assert node.getNodeMetaData(INFERRED_TYPE).name == 'Ann1'
- })
- def annotation = field.getAnnotation Ann1
- if(true) {
- second: annotation = field.getAnnotation Ann2
- }
- return annotation
- }
- return null
- }
-
- assert Ann2.isAssignableFrom (doit(new A(), "field").class)
- '''
- }
}
-