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 <eric.mil...@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) - ''' - } } -