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
commit 934fe100091bc2aab6302bb98133d74fe24b4e55 Author: Eric Milles <[email protected]> AuthorDate: Tue Sep 13 12:04:49 2022 -0500 GROOVY-10229, GROOVY-10230, GROOVY-10330, GROOVY-10358: LUB: generics 2_5_X backport --- .../groovy/ast/tools/WideningCategories.java | 527 ++++++++++----------- .../transform/stc/StaticTypeCheckingSupport.java | 16 +- src/spec/test/typing/TypeCheckingTest.groovy | 3 +- src/test/gls/generics/GenericsBytecodeTest.groovy | 28 +- .../groovy/transform/stc/ClosuresSTCTest.groovy | 2 +- .../groovy/transform/stc/GenericsSTCTest.groovy | 27 +- .../transform/stc/TernaryOperatorSTCTest.groovy | 80 +++- .../groovy/ast/tools/WideningCategoriesTest.groovy | 77 +-- 8 files changed, 416 insertions(+), 344 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java b/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java index a4ea1aef6a..5c11d0637f 100644 --- a/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java +++ b/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java @@ -18,7 +18,6 @@ */ package org.codehaus.groovy.ast.tools; -import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.GenericsType; import org.codehaus.groovy.ast.MethodNode; @@ -45,12 +44,14 @@ import static org.codehaus.groovy.ast.ClassHelper.byte_TYPE; import static org.codehaus.groovy.ast.ClassHelper.char_TYPE; import static org.codehaus.groovy.ast.ClassHelper.double_TYPE; import static org.codehaus.groovy.ast.ClassHelper.float_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.getNextSuperClass; import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper; import static org.codehaus.groovy.ast.ClassHelper.getWrapper; import static org.codehaus.groovy.ast.ClassHelper.int_TYPE; import static org.codehaus.groovy.ast.ClassHelper.isNumberType; import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType; import static org.codehaus.groovy.ast.ClassHelper.long_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching; import static org.codehaus.groovy.ast.ClassHelper.short_TYPE; /** @@ -70,44 +71,24 @@ import static org.codehaus.groovy.ast.ClassHelper.short_TYPE; */ public class WideningCategories { - private static final List<ClassNode> EMPTY_CLASSNODE_LIST = Collections.emptyList(); - private static final Map<ClassNode, Integer> NUMBER_TYPES_PRECEDENCE = Collections.unmodifiableMap(new HashMap<ClassNode, Integer>() { private static final long serialVersionUID = -5178744121420941913L; { - put(ClassHelper.double_TYPE, 0); - put(ClassHelper.float_TYPE, 1); - put(ClassHelper.long_TYPE, 2); - put(ClassHelper.int_TYPE, 3); - put(ClassHelper.short_TYPE, 4); - put(ClassHelper.byte_TYPE, 5); - }}); - - /** - * A comparator which is used in case we generate a virtual lower upper bound class node. In that case, - * since a concrete implementation should be used at compile time, we must ensure that interfaces are - * always sorted. It is not important what sort is used, as long as the result is constant. - */ - private static final Comparator<ClassNode> INTERFACE_CLASSNODE_COMPARATOR = new Comparator<ClassNode>() { - public int compare(final ClassNode o1, final ClassNode o2) { - int interfaceCountForO1 = o1.getInterfaces().length; - int interfaceCountForO2 = o2.getInterfaces().length; - if (interfaceCountForO1 > interfaceCountForO2) return -1; - if (interfaceCountForO1 < interfaceCountForO2) return 1; - int methodCountForO1 = o1.getMethods().size(); - int methodCountForO2 = o2.getMethods().size(); - if (methodCountForO1 > methodCountForO2) return -1; - if (methodCountForO1 < methodCountForO2) return 1; - return o1.getName().compareTo(o2.getName()); + put(double_TYPE, 0); + put(float_TYPE, 1); + put(long_TYPE, 2); + put(int_TYPE, 3); + put(short_TYPE, 4); + put(byte_TYPE, 5); } - }; + }); /** * Used to check if a type is an int or Integer. * @param type the type to check */ - public static boolean isInt(ClassNode type) { + public static boolean isInt(final ClassNode type) { return int_TYPE == type; } @@ -115,7 +96,7 @@ public class WideningCategories { * Used to check if a type is an double or Double. * @param type the type to check */ - public static boolean isDouble(ClassNode type) { + public static boolean isDouble(final ClassNode type) { return double_TYPE == type; } @@ -123,7 +104,7 @@ public class WideningCategories { * Used to check if a type is a float or Float. * @param type the type to check */ - public static boolean isFloat(ClassNode type) { + public static boolean isFloat(final ClassNode type) { return float_TYPE == type; } @@ -131,49 +112,51 @@ public class WideningCategories { * It is of an int category, if the provided type is a * byte, char, short, int. */ - public static boolean isIntCategory(ClassNode type) { - return type==byte_TYPE || type==char_TYPE || - type==int_TYPE || type==short_TYPE; + public static boolean isIntCategory(final ClassNode type) { + return type == byte_TYPE || type == char_TYPE || type == int_TYPE || type == short_TYPE; } + /** * It is of a long category, if the provided type is a - * long, its wrapper or if it is a long category. + * long, its wrapper or if it is a long category. */ - public static boolean isLongCategory(ClassNode type) { - return type==long_TYPE || isIntCategory(type); + public static boolean isLongCategory(final ClassNode type) { + return type == long_TYPE || isIntCategory(type); } + /** * It is of a BigInteger category, if the provided type is a - * long category or a BigInteger. + * long category or a BigInteger. */ - public static boolean isBigIntCategory(ClassNode type) { - return type==BigInteger_TYPE || isLongCategory(type); + public static boolean isBigIntCategory(final ClassNode type) { + return type == BigInteger_TYPE || isLongCategory(type); } + /** * It is of a BigDecimal category, if the provided type is a - * BigInteger category or a BigDecimal. + * BigInteger category or a BigDecimal. */ - public static boolean isBigDecCategory(ClassNode type) { - return type==BigDecimal_TYPE || isBigIntCategory(type); + public static boolean isBigDecCategory(final ClassNode type) { + return type == BigDecimal_TYPE || isBigIntCategory(type); } + /** * It is of a double category, if the provided type is a * BigDecimal, a float, double. C(type)=double */ - public static boolean isDoubleCategory(ClassNode type) { - return type==float_TYPE || type==double_TYPE || - isBigDecCategory(type); + public static boolean isDoubleCategory(final ClassNode type) { + return type == float_TYPE || type == double_TYPE || isBigDecCategory(type); } /** * It is of a floating category, if the provided type is a * a float, double. C(type)=float */ - public static boolean isFloatingCategory(ClassNode type) { - return type==float_TYPE || type==double_TYPE; + public static boolean isFloatingCategory(final ClassNode type) { + return type == float_TYPE || type == double_TYPE; } - public static boolean isNumberCategory(ClassNode type) { + public static boolean isNumberCategory(final ClassNode type) { return isBigDecCategory(type) || type.isDerivedFrom(Number_TYPE); } @@ -184,9 +167,11 @@ public class WideningCategories { * @param nodes the list of nodes for which to find the first common super type. * @return first common supertype */ - public static ClassNode lowestUpperBound(List<ClassNode> nodes) { - if (nodes.size()==1) return nodes.get(0); - return lowestUpperBound(nodes.get(0), lowestUpperBound(nodes.subList(1, nodes.size()))); + public static ClassNode lowestUpperBound(final List<ClassNode> nodes) { + int n = nodes.size(); + if (n == 1) return nodes.get(0); + if (n == 2) return lowestUpperBound(nodes.get(0), nodes.get(1)); + return lowestUpperBound(nodes.get(0), lowestUpperBound(nodes.subList(1, n))); } /** @@ -206,36 +191,29 @@ public class WideningCategories { * @param b second class node * @return first common supertype */ - public static ClassNode lowestUpperBound(ClassNode a, ClassNode b) { + public static ClassNode lowestUpperBound(final ClassNode a, final ClassNode b) { ClassNode lub = lowestUpperBound(a, b, null, null); - if (lub==null || !lub.isUsingGenerics()) return lub; - // types may be parameterized. If so, we must ensure that generic type arguments + if (lub == null || !lub.isUsingGenerics() + || lub.isGenericsPlaceHolder()) { // GROOVY-10330 + return lub; + } + // types may be parameterized; if so, ensure that generic type arguments // are made compatible - if (lub instanceof LowestUpperBoundClassNode) { - // no parent super class representing both types could be found - // or both class nodes implement common interfaces which may have - // been parameterized differently. - // We must create a classnode for which the "superclass" is potentially parameterized - // plus the interfaces - ClassNode superClass = lub.getSuperClass(); - ClassNode psc = superClass.isUsingGenerics()?parameterizeLowestUpperBound(superClass, a, b, lub):superClass; - - ClassNode[] interfaces = lub.getInterfaces(); - ClassNode[] pinterfaces = new ClassNode[interfaces.length]; - for (int i = 0, interfacesLength = interfaces.length; i < interfacesLength; i++) { - final ClassNode icn = interfaces[i]; - if (icn.isUsingGenerics()) { - pinterfaces[i] = parameterizeLowestUpperBound(icn, a, b, lub); - } else { - pinterfaces[i] = icn; + ClassNode superClass = lub.getUnresolvedSuperClass(); + if (superClass.redirect().getGenericsTypes() != null) { + superClass = parameterizeLowestUpperBound(superClass, a, b, lub); + } + ClassNode[] interfaces = lub.getInterfaces().clone(); + for (int i = 0, n = interfaces.length; i < n; i += 1) { + ClassNode icn = interfaces[i]; + if (icn.redirect().getGenericsTypes() != null) { + interfaces[i] = parameterizeLowestUpperBound(icn, a, b, lub); } } - - return new LowestUpperBoundClassNode(((LowestUpperBoundClassNode)lub).name, psc, pinterfaces); + return new LowestUpperBoundClassNode(((LowestUpperBoundClassNode)lub).name, superClass, interfaces); } else { return parameterizeLowestUpperBound(lub, a, b, lub); - } } @@ -258,72 +236,63 @@ public class WideningCategories { // it according to the types provided by the two class nodes ClassNode holderForA = findGenericsTypeHolderForClass(a, lub); ClassNode holderForB = findGenericsTypeHolderForClass(b, lub); - // let's compare their generics type + // let's compare their generics GenericsType[] agt = holderForA == null ? null : holderForA.getGenericsTypes(); GenericsType[] bgt = holderForB == null ? null : holderForB.getGenericsTypes(); - if (agt==null || bgt==null || agt.length!=bgt.length) { + if (agt == null || bgt == null || agt.length != bgt.length + || Arrays.toString(agt).equals(Arrays.toString(bgt))) { return lub; } - GenericsType[] lubgt = new GenericsType[agt.length]; - for (int i = 0; i < agt.length; i++) { - ClassNode t1 = agt[i].getType(); - ClassNode t2 = bgt[i].getType(); + int n = agt.length; GenericsType[] lubGTs = new GenericsType[n]; + for (int i = 0; i < n; i += 1) { + ClassNode t1 = upperBound(agt[i]); + ClassNode t2 = upperBound(bgt[i]); ClassNode basicType; if (areEqualWithGenerics(t1, isPrimitiveType(a)?getWrapper(a):a) && areEqualWithGenerics(t2, isPrimitiveType(b)?getWrapper(b):b)) { - // we are facing a self referencing type ! - basicType = fallback; + // "String implements Comparable<String>" and "StringBuffer implements Comparable<StringBuffer>" + basicType = fallback; // do not loop } else { - basicType = lowestUpperBound(t1, t2); + basicType = lowestUpperBound(t1, t2); } - if (t1.equals(t2)) { - lubgt[i] = new GenericsType(basicType); + if (agt[i].isWildcard() || bgt[i].isWildcard() || !t1.equals(t2)) { + lubGTs[i] = GenericsUtils.buildWildcardType(basicType); } else { - lubgt[i] = GenericsUtils.buildWildcardType(basicType); + lubGTs[i] = basicType.asGenericsType(); } } - ClassNode plain = lub.getPlainNodeReference(); - plain.setGenericsTypes(lubgt); - return plain; + return GenericsUtils.makeClassSafe0(lub, lubGTs); } - private static ClassNode findGenericsTypeHolderForClass(ClassNode source, ClassNode type) { + private static ClassNode findGenericsTypeHolderForClass(ClassNode source, final ClassNode target) { if (isPrimitiveType(source)) source = getWrapper(source); - if (source.equals(type)) return source; - if (type.isInterface()) { - for (ClassNode interfaceNode : source.getAllInterfaces()) { - if (interfaceNode.equals(type)) { - ClassNode parameterizedInterface = GenericsUtils.parameterizeType(source, interfaceNode); - return parameterizedInterface; + if (source.equals(target)) { + return source; + } + if (target.isInterface() ? source.implementsInterface(target) : source.isDerivedFrom(target)) { + ClassNode sc; + do { + sc = getNextSuperClass(source, target); + if (GenericsUtils.hasUnresolvedGenerics(sc)) { + sc = GenericsUtils.correctToGenericsSpecRecurse(GenericsUtils.createGenericsSpec(source), sc); } - } - } - ClassNode superClass = source.getUnresolvedSuperClass(); - // copy generic type information if available - if (superClass!=null && superClass.isUsingGenerics()) { - Map<GenericsType.GenericsTypeName, GenericsType> genericsTypeMap = GenericsUtils.extractPlaceholders(source); - GenericsType[] genericsTypes = superClass.getGenericsTypes(); - if (genericsTypes!=null) { - GenericsType[] copyTypes = new GenericsType[genericsTypes.length]; - for (int i = 0; i < genericsTypes.length; i++) { - GenericsType genericsType = genericsTypes[i]; - GenericsType.GenericsTypeName gtn = new GenericsType.GenericsTypeName(genericsType.getName()); - if (genericsType.isPlaceholder() && genericsTypeMap.containsKey(gtn)) { - copyTypes[i] = genericsTypeMap.get(gtn); - } else { - copyTypes[i] = genericsType; - } - } - superClass = superClass.getPlainNodeReference(); - superClass.setGenericsTypes(copyTypes); - } + } while (!(source = sc).equals(target)); + + return sc; } - if (superClass!=null) return findGenericsTypeHolderForClass(superClass, type); return null; } - private static ClassNode lowestUpperBound(ClassNode a, ClassNode b, List<ClassNode> interfacesImplementedByA, List<ClassNode> interfacesImplementedByB) { + private static ClassNode upperBound(final GenericsType gt) { + if (gt.isWildcard()) { + ClassNode[] ub = gt.getUpperBounds(); + if (ub != null) return ub[0]; + } + return gt.getType(); + } + + private static ClassNode lowestUpperBound(final ClassNode a, final ClassNode b, Set<ClassNode> interfacesImplementedByA, Set<ClassNode> interfacesImplementedByB) { // first test special cases - if (a==null || b==null) { + if (a == null || b == null) { // this is a corner case, you should not // compare two class nodes if one of them is null return null; @@ -335,7 +304,7 @@ public class WideningCategories { // one of the objects is at the top of the hierarchy GenericsType[] gta = a.getGenericsTypes(); GenericsType[] gtb = b.getGenericsTypes(); - if (gta !=null && gtb !=null && gta.length==1 && gtb.length==1) { + if (gta != null && gtb != null && gta.length == 1 && gtb.length == 1) { if (gta[0].getName().equals(gtb[0].getName())) { return a; } @@ -362,20 +331,18 @@ public class WideningCategories { if (isPrimitiveA && isPrimitiveB) { Integer pa = NUMBER_TYPES_PRECEDENCE.get(a); Integer pb = NUMBER_TYPES_PRECEDENCE.get(b); - if (pa!=null && pb!=null) { - if (pa<=pb) return a; - return b; + if (pa != null && pb != null) { + return (pa <= pb ? a : b); } - return a.equals(b)?a:lowestUpperBound(getWrapper(a), getWrapper(b), null, null); + return a.equals(b) ? a : lowestUpperBound(getWrapper(a), getWrapper(b), null, null); } if (isNumberType(a.redirect()) && isNumberType(b.redirect())) { ClassNode ua = getUnwrapper(a); ClassNode ub = getUnwrapper(b); Integer pa = NUMBER_TYPES_PRECEDENCE.get(ua); Integer pb = NUMBER_TYPES_PRECEDENCE.get(ub); - if (pa!=null && pb!=null) { - if (pa<=pb) return a; - return b; + if (pa != null && pb != null) { + return (pa <= pb ? a : b); } } @@ -390,19 +357,16 @@ public class WideningCategories { if (a.implementsInterface(b)) { return b; } - // each interface may have one or more "extends", so we must find those - // which are common - ClassNode[] interfacesFromA = a.getInterfaces(); - ClassNode[] interfacesFromB = b.getInterfaces(); - Set<ClassNode> common = new HashSet<ClassNode>(); - Collections.addAll(common, interfacesFromA); - Set<ClassNode> fromB = new HashSet<ClassNode>(); - Collections.addAll(fromB, interfacesFromB); - common.retainAll(fromB); - - if (common.size()==1) { + if (interfacesImplementedByA == null) + interfacesImplementedByA = GeneralUtils.getInterfacesAndSuperInterfaces(a); + if (interfacesImplementedByB == null) + interfacesImplementedByB = GeneralUtils.getInterfacesAndSuperInterfaces(b); + + // each interface may have one or more "extends", so we must find those which are common + Collection<ClassNode> common = keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB); + if (common.size() == 1) { return common.iterator().next(); - } else if (common.size()>1) { + } else if (common.size() > 1) { return buildTypeWithInterfaces(a, b, common); } @@ -426,63 +390,53 @@ public class WideningCategories { // no interface in common return OBJECT_TYPE; } - if (matchingInterfaces.size()==1) { + if (matchingInterfaces.size() == 1) { // a single match, which should be returned return matchingInterfaces.get(0); } - return buildTypeWithInterfaces(a,b, matchingInterfaces); + return buildTypeWithInterfaces(a, b, matchingInterfaces); } // both classes do not represent interfaces if (a.equals(b)) { - return buildTypeWithInterfaces(a,b, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB)); + return buildTypeWithInterfaces(a, b, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB)); } // test if one class inherits from the other if (a.isDerivedFrom(b) || b.isDerivedFrom(a)) { - return buildTypeWithInterfaces(a,b, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB)); + return buildTypeWithInterfaces(a, b, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB)); } - // Look at the super classes ClassNode sa = a.getUnresolvedSuperClass(); ClassNode sb = b.getUnresolvedSuperClass(); - // extract implemented interfaces before "going up" - Set<ClassNode> ifa = new HashSet<ClassNode>(); - extractInterfaces(a, ifa); - Set<ClassNode> ifb = new HashSet<ClassNode>(); - extractInterfaces(b, ifb); - interfacesImplementedByA = interfacesImplementedByA==null?new LinkedList<ClassNode>(ifa):interfacesImplementedByA; - interfacesImplementedByB = interfacesImplementedByB==null?new LinkedList<ClassNode>(ifb):interfacesImplementedByB; + if (interfacesImplementedByA == null) + interfacesImplementedByA = GeneralUtils.getInterfacesAndSuperInterfaces(a); + if (interfacesImplementedByB == null) + interfacesImplementedByB = GeneralUtils.getInterfacesAndSuperInterfaces(b); - // check if no superclass is defined - // meaning that we reached the top of the object hierarchy - if (sa==null || sb==null) return buildTypeWithInterfaces(OBJECT_TYPE, OBJECT_TYPE, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB)); + // check if no superclass is defined, meaning that we reached the top of the object hierarchy + if (sa == null || sb == null) { + return buildTypeWithInterfaces(OBJECT_TYPE, OBJECT_TYPE, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB)); + } + if (GenericsUtils.hasUnresolvedGenerics(sa)) + sa = GenericsUtils.correctToGenericsSpecRecurse(GenericsUtils.createGenericsSpec(a), sa); + if (GenericsUtils.hasUnresolvedGenerics(sb)) + sb = GenericsUtils.correctToGenericsSpecRecurse(GenericsUtils.createGenericsSpec(b), sb); - // if one superclass is derived (or equals) another - // then it is the common super type + // if one superclass is derived (or equals) another then it is the common super type if (sa.isDerivedFrom(sb) || sb.isDerivedFrom(sa)) { return buildTypeWithInterfaces(sa, sb, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB)); } - // superclasses are on distinct hierarchy branches, so we - // recurse on them + // superclasses are on distinct hierarchy branches, so we recurse on them return lowestUpperBound(sa, sb, interfacesImplementedByA, interfacesImplementedByB); } - private static void extractInterfaces(ClassNode node, Set<ClassNode> interfaces) { - if (node==null) return; - Collections.addAll(interfaces, node.getInterfaces()); - extractInterfaces(node.getSuperClass(), interfaces); - } - /** - * Given the list of interfaces implemented by two class nodes, returns the list of the most specific common - * implemented interfaces. - * @param fromA - * @param fromB - * @return the list of the most specific common implemented interfaces + * Given the interfaces implemented by two types, returns a list of the most + * specific common implemented interfaces. */ - private static List<ClassNode> keepLowestCommonInterfaces(List<ClassNode> fromA, List<ClassNode> fromB) { - if (fromA==null||fromB==null) return EMPTY_CLASSNODE_LIST; + private static List<ClassNode> keepLowestCommonInterfaces(final Set<ClassNode> fromA, final Set<ClassNode> fromB) { + if (fromA == null || fromB == null) return Collections.EMPTY_LIST; Set<ClassNode> common = new HashSet<ClassNode>(fromA); common.retainAll(fromB); List<ClassNode> result = new ArrayList<ClassNode>(common.size()); @@ -492,16 +446,15 @@ public class WideningCategories { return result; } - private static void addMostSpecificInterface(ClassNode interfaceNode, List<ClassNode> nodes) { + private static void addMostSpecificInterface(final ClassNode interfaceNode, final List<ClassNode> nodes) { if (nodes.isEmpty()) nodes.add(interfaceNode); - for (int i = 0, nodesSize = nodes.size(); i < nodesSize; i++) { - final ClassNode node = nodes.get(i); - if (node.equals(interfaceNode)||node.implementsInterface(interfaceNode)) { + for (int i = 0, n = nodes.size(); i < n; i += 1) { ClassNode node = nodes.get(i); + if (node.equals(interfaceNode) || node.implementsInterface(interfaceNode)) { // a more specific interface exists in the list, keep it return; } if (interfaceNode.implementsInterface(node)) { - // the interface beeing added is more specific than the one in the list, replace it + // the interface being added is more specific than the one in the list, replace it nodes.set(i, interfaceNode); return; } @@ -517,7 +470,7 @@ public class WideningCategories { for (ClassNode interfaceNode : interfaces) { if (type.implementsInterface(interfaceNode)) result.add(interfaceNode); } - if (result.isEmpty() && interfaces.length>0) { + if (result.isEmpty() && interfaces.length > 0) { // none if the direct interfaces match, but we must check "upper" in the hierarchy for (ClassNode interfaceNode : interfaces) { extractMostSpecificImplementedInterfaces(type, interfaceNode, result); @@ -534,52 +487,70 @@ public class WideningCategories { * @param interfaces interfaces both class nodes share, which their lowest common super class do not implement. * @return the class node representing the lowest upper bound */ - private static ClassNode buildTypeWithInterfaces(ClassNode baseType1, ClassNode baseType2, Collection<ClassNode> interfaces) { - boolean noInterface = interfaces.isEmpty(); - if (noInterface) { - if (baseType1.equals(baseType2)) return baseType1; - if (baseType1.isDerivedFrom(baseType2)) return baseType2; + private static ClassNode buildTypeWithInterfaces(final ClassNode baseType1, final ClassNode baseType2, final Collection<ClassNode> interfaces) { + if (interfaces.isEmpty()) { if (baseType2.isDerivedFrom(baseType1)) return baseType1; + if (baseType1.isDerivedFrom(baseType2)) return baseType2; } - if (OBJECT_TYPE.equals(baseType1) && OBJECT_TYPE.equals(baseType2) && interfaces.size()==1) { - if (interfaces instanceof List) { - return ((List<ClassNode>) interfaces).get(0); - } + + if (baseType1.equals(OBJECT_TYPE) && baseType2.equals(OBJECT_TYPE) && interfaces.size() == 1) { return interfaces.iterator().next(); } - LowestUpperBoundClassNode type; - ClassNode superClass; - String name; + + String name; ClassNode superClass; if (baseType1.equals(baseType2)) { - if (OBJECT_TYPE.equals(baseType1)) { - superClass = baseType1; + superClass = baseType1; + if (baseType1.equals(OBJECT_TYPE)) { name = "Virtual$Object"; } else { - superClass = baseType1; - name = "Virtual$"+baseType1.getName(); + name = "Virtual$" + baseType1.getName(); } } else { - superClass = OBJECT_TYPE; if (baseType1.isDerivedFrom(baseType2)) { superClass = baseType2; } else if (baseType2.isDerivedFrom(baseType1)) { superClass = baseType1; + } else { + superClass = OBJECT_TYPE; } - name = "CommonAssignOf$"+baseType1.getName()+"$"+baseType2.getName(); + name = "CommonAssignOf$" + baseType1.getName() + "$" + baseType2.getName(); } - Iterator<ClassNode> itcn = interfaces.iterator(); - while (itcn.hasNext()) { - ClassNode next = itcn.next(); - if (superClass.isDerivedFrom(next) || superClass.implementsInterface(next)) { - itcn.remove(); + + for (Iterator<ClassNode> it = interfaces.iterator(); it.hasNext(); ) { + if (GeneralUtils.isOrImplements(superClass, it.next())) { + it.remove(); } } + + int nInterfaces = interfaces.size(); + if (nInterfaces == 0) return superClass; + if (nInterfaces == 1 && superClass.equals(OBJECT_TYPE)) return interfaces.iterator().next(); + ClassNode[] interfaceArray = interfaces.toArray(ClassNode.EMPTY_ARRAY); Arrays.sort(interfaceArray, INTERFACE_CLASSNODE_COMPARATOR); - type = new LowestUpperBoundClassNode(name, superClass, interfaceArray); - return type; + return new LowestUpperBoundClassNode(name, superClass, interfaceArray); } + /** + * A comparator which is used in case we generate a virtual lower upper bound class node. In that case, + * since a concrete implementation should be used at compile time, we must ensure that interfaces are + * always sorted. It is not important what sort is used, as long as the result is constant. + */ + private static final Comparator<ClassNode> INTERFACE_CLASSNODE_COMPARATOR = new Comparator<ClassNode>() { + @Override + public int compare(final ClassNode o1, final ClassNode o2) { + int interfaceCountForO1 = o1.getInterfaces().length; + int interfaceCountForO2 = o2.getInterfaces().length; + if (interfaceCountForO1 > interfaceCountForO2) return -1; + if (interfaceCountForO1 < interfaceCountForO2) return 1; + int methodCountForO1 = o1.getMethods().size(); + int methodCountForO2 = o2.getMethods().size(); + if (methodCountForO1 > methodCountForO2) return -1; + if (methodCountForO1 < methodCountForO2) return 1; + return o1.getName().compareTo(o2.getName()); + } + }; + /** * This {@link ClassNode} specialization is used when the lowest upper bound of two types * cannot be represented by an existing type. For example, if B extends A, C extends A @@ -592,62 +563,68 @@ public class WideningCategories { * */ public static class LowestUpperBoundClassNode extends ClassNode { - private static final Comparator<ClassNode> CLASS_NODE_COMPARATOR = new Comparator<ClassNode>() { - public int compare(final ClassNode o1, final ClassNode o2) { - String n1 = o1 instanceof LowestUpperBoundClassNode?((LowestUpperBoundClassNode)o1).name:o1.getName(); - String n2 = o2 instanceof LowestUpperBoundClassNode?((LowestUpperBoundClassNode)o2).name:o2.getName(); - return n1.compareTo(n2); - } - }; - private final ClassNode compileTimeClassNode; private final String name; private final String text; - private final ClassNode upper; private final ClassNode[] interfaces; + private final ClassNode compileTimeClassNode; - public LowestUpperBoundClassNode(String name, ClassNode upper, ClassNode... interfaces) { - super(name, ACC_PUBLIC|ACC_FINAL, upper, interfaces, null); + public LowestUpperBoundClassNode(final String name, final ClassNode upper, final ClassNode... interfaces) { + super(name, ACC_PUBLIC | ACC_FINAL, upper, interfaces, null); + this.name = name; this.upper = upper; this.interfaces = interfaces; - boolean usesGenerics; - Arrays.sort(interfaces, CLASS_NODE_COMPARATOR); - compileTimeClassNode = upper.equals(OBJECT_TYPE) && interfaces.length>0?interfaces[0]:upper; - this.name = name; - usesGenerics = upper.isUsingGenerics(); - List<GenericsType[]> genericsTypesList = new LinkedList<GenericsType[]>(); + Arrays.sort(interfaces, new Comparator<ClassNode>() { + @Override + public int compare(final ClassNode cn1, final ClassNode cn2) { + String n1 = cn1 instanceof LowestUpperBoundClassNode ? ((LowestUpperBoundClassNode) cn1).name : cn1.getName(); + String n2 = cn2 instanceof LowestUpperBoundClassNode ? ((LowestUpperBoundClassNode) cn2).name : cn2.getName(); + return n1.compareTo(n2); + } + }); + compileTimeClassNode = upper.equals(OBJECT_TYPE) && interfaces.length > 0 ? interfaces[0] : upper; + + StringBuilder sb = new StringBuilder("("); + if (!upper.equals(OBJECT_TYPE)) sb.append(upper.getText()); + for (ClassNode i : interfaces) { + if (sb.length() > 1) { + sb.append(" or "); + } + sb.append(i.getText()); + } + sb.append(")"); + this.text = sb.toString(); + + boolean usesGenerics = upper.isUsingGenerics(); + List<GenericsType[]> genericsTypesList = new ArrayList<GenericsType[]>(); genericsTypesList.add(upper.getGenericsTypes()); - for (ClassNode anInterface : interfaces) { + for (ClassNode anInterface : interfaces) { usesGenerics |= anInterface.isUsingGenerics(); genericsTypesList.add(anInterface.getGenericsTypes()); - for (MethodNode methodNode : anInterface.getMethods()) { + for (MethodNode methodNode : anInterface.getMethods()) { MethodNode method = addMethod(methodNode.getName(), methodNode.getModifiers(), methodNode.getReturnType(), methodNode.getParameters(), methodNode.getExceptions(), methodNode.getCode()); method.setDeclaringClass(anInterface); // important for static compilation! } - } + } setUsingGenerics(usesGenerics); if (usesGenerics) { - List<GenericsType> asArrayList = new ArrayList<GenericsType>(); - for (GenericsType[] genericsTypes : genericsTypesList) { - if (genericsTypes!=null) { - Collections.addAll(asArrayList, genericsTypes); + List<GenericsType> flatList = new ArrayList<GenericsType>(); + for (GenericsType[] gts : genericsTypesList) { + if (gts != null) { + Collections.addAll(flatList, gts); } } - setGenericsTypes(asArrayList.toArray(GenericsType.EMPTY_ARRAY)); - } - StringBuilder sb = new StringBuilder(); - if (!upper.equals(OBJECT_TYPE)) sb.append(upper.getName()); - for (ClassNode anInterface : interfaces) { - if (sb.length()>0) { - sb.append(" or "); - } - sb.append(anInterface.getName()); + setGenericsTypes(flatList.toArray(GenericsType.EMPTY_ARRAY)); } - this.text = sb.toString(); } public String getLubName() { - return this.name; + return name; + } + + @Override + public String getText() { + return text; } @Override @@ -662,15 +639,7 @@ public class WideningCategories { @Override public int hashCode() { - int result = super.hashCode(); -// result = 31 * result + (compileTimeClassNode != null ? compileTimeClassNode.hashCode() : 0); - result = 31 * result + (name != null ? name.hashCode() : 0); - return result; - } - - @Override - public String getText() { - return text; + return (31 * super.hashCode()) + (name != null ? name.hashCode() : 0); } @Override @@ -682,21 +651,18 @@ public class WideningCategories { ubs = new ClassNode[interfaces.length + 1]; ubs[0] = upper; System.arraycopy(interfaces, 0, ubs, 1, interfaces.length); } - GenericsType gt = new GenericsType(ClassHelper.makeWithoutCaching("?"), ubs, null); + GenericsType gt = new GenericsType(makeWithoutCaching("?"), ubs, null); gt.setWildcard(true); return gt; } @Override public ClassNode getPlainNodeReference() { - ClassNode[] intf = interfaces==null?null:new ClassNode[interfaces.length]; - if (intf!=null) { - for (int i = 0; i < interfaces.length; i++) { - intf[i] = interfaces[i].getPlainNodeReference(); - } + ClassNode[] faces = interfaces.clone(); + for (int i = 0; i < interfaces.length; i += 1) { + faces[i] = interfaces[i].getPlainNodeReference(); } - LowestUpperBoundClassNode plain = new LowestUpperBoundClassNode(name, upper.getPlainNodeReference(), intf); - return plain; + return new LowestUpperBoundClassNode(name, upper.getPlainNodeReference(), faces); } } @@ -706,39 +672,42 @@ public class WideningCategories { * @param b * @return true if the class nodes are equal, false otherwise */ - private static boolean areEqualWithGenerics(ClassNode a, ClassNode b) { + private static boolean areEqualWithGenerics(final ClassNode a, final ClassNode b) { if (a==null) return b==null; if (!a.equals(b)) return false; if (a.isUsingGenerics() && !b.isUsingGenerics()) return false; GenericsType[] gta = a.getGenericsTypes(); GenericsType[] gtb = b.getGenericsTypes(); - if (gta==null && gtb!=null) return false; - if (gtb==null && gta!=null) return false; - if (gta!=null && gtb!=null) { - if (gta.length!=gtb.length) return false; - for (int i = 0; i < gta.length; i++) { - GenericsType ga = gta[i]; - GenericsType gb = gtb[i]; - boolean result = ga.isPlaceholder()==gb.isPlaceholder() && ga.isWildcard()==gb.isWildcard(); - result = result && ga.getName().equals(gb.getName()); - result = result && areEqualWithGenerics(ga.getType(), gb.getType()); - result = result && areEqualWithGenerics(ga.getLowerBound(), gb.getLowerBound()); - if (result) { - ClassNode[] upA = ga.getUpperBounds(); - if (upA!=null) { - ClassNode[] upB = gb.getUpperBounds(); - if (upB==null || upB.length!=upA.length) return false; - for (int j = 0; j < upA.length; j++) { - if (!areEqualWithGenerics(upA[j],upB[j])) return false; - } - } + if (gta == null && gtb != null) return false; + if (gtb == null && gta != null) return false; + if (gta != null && gtb != null) { + if (gta.length != gtb.length) return false; + for (int i = 0, n = gta.length; i < n; i += 1) { + GenericsType gta_i = gta[i]; + GenericsType gtb_i = gtb[i]; + ClassNode[] upperA = gta_i.getUpperBounds(); + ClassNode[] upperB = gtb_i.getUpperBounds(); + if (gta_i.isPlaceholder() != gtb_i.isPlaceholder() + || gta_i.isWildcard() != gtb_i.isWildcard() + || !gta_i.getName().equals(gtb_i.getName()) + || !areEqualWithGenerics(gta_i.getType(), gtb_i.getType()) + || !areEqualWithGenerics(gta_i.getLowerBound(), gtb_i.getLowerBound()) + || (upperA == null ? upperB != null : !areEqualWithGenerics(upperA, upperB))) { + return false; } - if (!result) return false; } } return true; } - + + private static boolean areEqualWithGenerics(final ClassNode[] upperA, final ClassNode[] upperB) { + int n; if ((n = upperA.length) != upperB.length) return false; + for (int i = 0; i < n; i += 1) { + if (!areEqualWithGenerics(upperA[i], upperB[i])) return false; + } + return true; + } + /** * Determines if the source class implements an interface or subclasses the target type. * This method takes the {@link org.codehaus.groovy.ast.tools.WideningCategories.LowestUpperBoundClassNode lowest 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 45ecca7e51..0d8ebbdf86 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java @@ -109,7 +109,6 @@ import static org.codehaus.groovy.ast.ClassHelper.make; import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching; import static org.codehaus.groovy.ast.ClassHelper.short_TYPE; import static org.codehaus.groovy.ast.ClassHelper.void_WRAPPER_TYPE; -import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean; import static org.codehaus.groovy.syntax.Types.ASSIGN; import static org.codehaus.groovy.syntax.Types.BITWISE_AND; import static org.codehaus.groovy.syntax.Types.BITWISE_AND_EQUAL; @@ -672,6 +671,11 @@ public abstract class StaticTypeCheckingSupport { return checkCompatibleAssignmentTypes(left, right, rightExpression, true); } + /** + * Everything that can be done by {@code castToType} should be allowed for assignment. + * + * @see org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation#castToType(Object,Class) + */ public static boolean checkCompatibleAssignmentTypes(ClassNode left, ClassNode right, Expression rightExpression, boolean allowConstructorCoercion) { boolean rightExpressionIsNull = isNullConstant(rightExpression); if (rightExpressionIsNull && !isPrimitiveType(left)) { @@ -864,7 +868,7 @@ public abstract class StaticTypeCheckingSupport { return !Double.valueOf(val).equals(number); } default: // double - return false; // no possible loose here + return false; // no possible loss here } } return true; // possible loss of precision @@ -930,7 +934,7 @@ public abstract class StaticTypeCheckingSupport { if (type.isArray() && superOrInterface.isArray()) { return implementsInterfaceOrIsSubclassOf(type.getComponentType(), superOrInterface.getComponentType()); } - if (GROOVY_OBJECT_TYPE.equals(superOrInterface) && !type.isInterface() && isBeingCompiled(type)) { + if (superOrInterface.equals(GROOVY_OBJECT_TYPE) && !type.isInterface() && isBeingCompiled(type)) { return true; } return false; @@ -1069,7 +1073,7 @@ public abstract class StaticTypeCheckingSupport { * @return zero or more results */ public static List<MethodNode> chooseBestMethod(final ClassNode receiver, final Collection<MethodNode> methods, final ClassNode... argumentTypes) { - if (!asBoolean(methods)) { + if (methods == null || methods.isEmpty()) { return Collections.emptyList(); } if (isUsingUncheckedGenerics(receiver)) { @@ -1655,7 +1659,7 @@ public abstract class StaticTypeCheckingSupport { } static void applyGenericsConnections(final Map<GenericsTypeName, GenericsType> connections, final Map<GenericsTypeName, GenericsType> resolvedPlaceholders) { - if (!asBoolean(connections)) return; + if (connections == null || connections.isEmpty()) return; int count = 0; while (count++ < 10000) { @@ -1947,7 +1951,7 @@ public abstract class StaticTypeCheckingSupport { } ClassNode newType = type.getPlainNodeReference(); GenericsType[] gt = type.getGenericsTypes(); - if (asBoolean(spec)) { + if (spec != null) { gt = applyGenericsContext(spec, gt); } newType.setGenericsTypes(gt); diff --git a/src/spec/test/typing/TypeCheckingTest.groovy b/src/spec/test/typing/TypeCheckingTest.groovy index 180d0b75a5..6d2018bca3 100644 --- a/src/spec/test/typing/TypeCheckingTest.groovy +++ b/src/spec/test/typing/TypeCheckingTest.groovy @@ -610,7 +610,8 @@ import static org.codehaus.groovy.ast.tools.WideningCategories.lowestUpperBound it.exit() // <9> } // end::least_upper_bound_collection_inference[] - ''', '[Static type checking] - Cannot find matching method Greeter or Salute#exit()' + ''', + '[Static type checking] - Cannot find matching method (Greeter or Salute)#exit()' } void testInstanceOfInference() { diff --git a/src/test/gls/generics/GenericsBytecodeTest.groovy b/src/test/gls/generics/GenericsBytecodeTest.groovy index 897149cda8..cd6bfc14fd 100644 --- a/src/test/gls/generics/GenericsBytecodeTest.groovy +++ b/src/test/gls/generics/GenericsBytecodeTest.groovy @@ -205,13 +205,37 @@ class GenericsBytecodeTest extends GenericsTestBase { ] } + // GROOVY-10229 + void testWildcard3() { + createClassInfo ''' + @groovy.transform.CompileStatic + class C { + Map<String,?> a() { + } + Map<String,List<?>> b() { + def c = { + [ + a(), a() + ] + } + return null + } + } + ''' + assert signatures == [ + 'a()Ljava/util/Map;' : '()Ljava/util/Map<Ljava/lang/String;*>;', + 'b()Ljava/util/Map;' : '()Ljava/util/Map<Ljava/lang/String;Ljava/util/List<*>;>;', + 'doCall()Ljava/util/List;' : '()Ljava/util/List<Ljava/util/Map<Ljava/lang/String;+Ljava/lang/Object;>;>;', + 'doCall(Ljava/lang/Object;)Ljava/util/List;': '(Ljava/lang/Object;)Ljava/util/List<Ljava/util/Map<Ljava/lang/String;+Ljava/lang/Object;>;>;' + ] + } + void testParameterAsParameterForReturnTypeAndFieldClass() { createClassInfo """ class B<T> { private T owner; Class<T> getOwnerClass(){} - - } + } """ assert signatures == [ "class" : "<T:Ljava/lang/Object;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;", diff --git a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy index 4ddd49edf9..87fd1cf2cc 100644 --- a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy +++ b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy @@ -321,7 +321,7 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { { -> x = 123 } x.charAt(0) // available in String but not available in Integer ''', - 'Cannot find matching method java.io.Serializable or java.lang.Comparable' + 'Cannot find matching method (java.io.Serializable or java.lang.Comparable',')#charAt(int)' } // GROOVY-9516 diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy index 991dd48adf..026c3751e4 100644 --- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy +++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy @@ -906,7 +906,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { ''' } - @NotYetImplemented // GROOVY-10230 + // GROOVY-10230 void testDiamondInferrenceFromConstructor23() { assertScript ''' class A { @@ -2417,6 +2417,8 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { // GROOVY-10235 void testCompatibleArgumentsForPlaceholders4() { + if (!GroovyAssert.isAtLeastJdk('1.8')) return + assertScript ''' import static java.util.concurrent.ConcurrentHashMap.newKeySet @@ -2486,7 +2488,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { // GROOVY-10100 void testCompatibleArgumentsForPlaceholders8() { assertScript ''' - import java.util.function.Function + import org.apache.groovy.internal.util.Function class C<T> { T m(Object... args) { @@ -3222,10 +3224,9 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { // GROOVY-10557 void testReturnTypeInferenceWithClosure2() { assertScript ''' + import org.apache.groovy.internal.util.Function + class C { - interface Function<T, R> { - R apply(T t) - } def <T> T m(Function<Reader,T> fn) { new StringReader("").withCloseable { reader -> fn.apply(reader) @@ -3747,11 +3748,9 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { // GROOVY-6731 void testContravariantMethodResolution() { assertScript ''' - interface Function<T, R> { - R apply(T t) - } + import org.apache.groovy.internal.util.Function - public <I, O> void transform(Function<? super I, ? extends O> function) { + def <I, O> void transform(Function<? super I, ? extends O> function) { function.apply('') } @@ -3768,11 +3767,9 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { void testContravariantMethodResolutionWithImplicitCoercion() { assertScript ''' - interface Function<T, R> { - R apply(T t) - } + import org.apache.groovy.internal.util.Function - public <I, O> void transform(Function<? super I, ? extends O> function) { + def <I, O> void transform(Function<? super I, ? extends O> function) { function.apply('') } @@ -3926,9 +3923,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { // GROOVY-9635 void testCovariantReturnTypeInferredFromMethod3() { assertScript ''' - interface Function<T, R> { - R apply(T t) - } + import org.apache.groovy.internal.util.Function class C<R extends Number> { def <V> V m(Function<C, V> f) { // R from C is confused with R->V from Function diff --git a/src/test/groovy/transform/stc/TernaryOperatorSTCTest.groovy b/src/test/groovy/transform/stc/TernaryOperatorSTCTest.groovy index b477460409..5545f0a2c6 100644 --- a/src/test/groovy/transform/stc/TernaryOperatorSTCTest.groovy +++ b/src/test/groovy/transform/stc/TernaryOperatorSTCTest.groovy @@ -18,8 +18,6 @@ */ package groovy.transform.stc -import groovy.transform.NotYetImplemented - /** * Unit tests for static type checking : ternary operator. */ @@ -133,7 +131,41 @@ class TernaryOperatorSTCTest extends StaticTypeCheckingTestCase { ''' } - @NotYetImplemented // GROOVY-10357 + // GROOVY-10330 + void testTypeParameterTypeParameter1() { + assertScript ''' + import org.apache.groovy.internal.util.Function + + class C<T> { + T y + void m(T x, Function<T, T> f) { + assert f.apply(x) == 'foo' + } + void test(T x, Function<T, T> f) { + @ASTTest(phase=INSTRUCTION_SELECTION, value={ + def type = node.getNodeMetaData(INFERRED_TYPE) + assert type.isGenericsPlaceHolder() + assert type.unresolvedName == 'T' + }) + def z = true ? x : y + m(z, f) + } + } + new C<String>().test('FOO', { it.toLowerCase() }) + ''' + } + + // GROOVY-10363 + void testTypeParameterTypeParameter2() { + assertScript ''' + def <X extends java.util.concurrent.Callable<Number>> X m(X x, X y) { + X ecks = true ? x : y // infers as Callable<Object> + } + assert m(null,null) == null + ''' + } + + // GROOVY-10357 void testAbstractMethodDefault() { assertScript ''' import org.apache.groovy.internal.util.Function @@ -145,13 +177,53 @@ class TernaryOperatorSTCTest extends StaticTypeCheckingTestCase { def a = new A() { @Override long m(Function<Boolean,Integer> f) { - f(true).longValue() + f.apply(true).longValue() } } assert a.m() == 1L ''' } + // GROOVY-10358 + void testCommonInterface() { + assertScript ''' + interface I { + int m(int i) + } + abstract class A implements I { + } + class B<T> extends A { + int m(int i) { + i + 1 + } + } + class C<T> extends A { + int m(int i) { + i - 1 + } + } + + C<String> c = null; int i = 1 + int x = (false ? c : new B<String>()).m(i) // Cannot find matching method A#m(int) + assert x == 2 + ''' + } + + // GROOVY-10603 + void testCommonInterface2() { + assertScript ''' + interface I {} + interface J extends I {} + class Foo implements I {} + class Bar implements J {} + + I test(Foo x, Bar y) { + true ? x : y // Cannot return value of type GroovyObject for method returning I + } + test(null, null) + ''' + } + // GROOVY-5523 void testNull1() { assertScript ''' diff --git a/src/test/org/codehaus/groovy/ast/tools/WideningCategoriesTest.groovy b/src/test/org/codehaus/groovy/ast/tools/WideningCategoriesTest.groovy index 4be116256e..7ba751d062 100644 --- a/src/test/org/codehaus/groovy/ast/tools/WideningCategoriesTest.groovy +++ b/src/test/org/codehaus/groovy/ast/tools/WideningCategoriesTest.groovy @@ -69,7 +69,7 @@ final class WideningCategoriesTest extends GenericsTestCase { void testBuildCommonTypeWithTwoIncompatibleInterfaces() { ClassNode a = make(Set) - ClassNode b = make(Map) + ClassNode b = MAP_TYPE assert lowestUpperBound(a,b) == OBJECT_TYPE assert lowestUpperBound(b,a) == OBJECT_TYPE } @@ -82,7 +82,7 @@ final class WideningCategoriesTest extends GenericsTestCase { } void testBuildCommonTypeWithOneClassAndNoImplementedInterface() { - ClassNode a = make(Map) + ClassNode a = MAP_TYPE ClassNode b = make(HashSet) assert lowestUpperBound(a,b) == OBJECT_TYPE assert lowestUpperBound(b,a) == OBJECT_TYPE @@ -91,8 +91,8 @@ final class WideningCategoriesTest extends GenericsTestCase { void testBuildCommonTypeWithTwoClassesWithoutSuperClass() { ClassNode a = make(ClassA) ClassNode b = make(ClassB) - assert lowestUpperBound(a,b) == make(GroovyObject) // GroovyObject because Groovy classes implicitly implement GroovyObject - assert lowestUpperBound(b,a) == make(GroovyObject) + assert lowestUpperBound(a,b) == GROOVY_OBJECT_TYPE // GroovyObject because Groovy classes implicitly implement GroovyObject + assert lowestUpperBound(b,a) == GROOVY_OBJECT_TYPE } void testBuildCommonTypeWithIdenticalPrimitiveTypes() { @@ -127,7 +127,14 @@ final class WideningCategoriesTest extends GenericsTestCase { assert lowestUpperBound(b,a) == make(HashSet) } - void testBuildCommonTypeWithTwoInterfacesSharingOneParent() { + void testBuildCommonTypeWithTwoInterfacesSharingOneParent0() { + ClassNode a = make(Set).plainNodeReference + ClassNode b = LIST_TYPE.plainNodeReference + assert lowestUpperBound(a,b).toString(false) == 'java.util.Collection <java.lang.Object>' + assert lowestUpperBound(b,a).toString(false) == 'java.util.Collection <java.lang.Object>' + } + + void testBuildCommonTypeWithTwoInterfacesSharingOneParent1() { ClassNode a = make(InterfaceCA) ClassNode b = make(InterfaceDA) assert lowestUpperBound(a,b) == make(InterfaceA) @@ -151,15 +158,15 @@ final class WideningCategoriesTest extends GenericsTestCase { void testBuildCommonTypeFromTwoClassesInDifferentBranches() { ClassNode a = make(ClassA1) ClassNode b = make(ClassB1) - assert lowestUpperBound(a,b) == make(GroovyObject) - assert lowestUpperBound(b,a) == make(GroovyObject) + assert lowestUpperBound(a,b) == GROOVY_OBJECT_TYPE + assert lowestUpperBound(b,a) == GROOVY_OBJECT_TYPE } void testBuildCommonTypeFromTwoClassesInDifferentBranchesAndOneCommonInterface() { ClassNode a = make(ClassA1_Serializable) ClassNode b = make(ClassB1_Serializable) - assert lowestUpperBound(a,b).interfaces as Set == [make(Serializable), make(GroovyObject)] as Set - assert lowestUpperBound(b,a).interfaces as Set == [make(Serializable), make(GroovyObject)] as Set + assert lowestUpperBound(a,b).interfaces as Set == [make(Serializable), GROOVY_OBJECT_TYPE] as Set + assert lowestUpperBound(b,a).interfaces as Set == [make(Serializable), GROOVY_OBJECT_TYPE] as Set } void testBuildCommonTypeFromTwoClassesWithCommonSuperClassAndOneCommonInterface() { @@ -178,22 +185,22 @@ final class WideningCategoriesTest extends GenericsTestCase { // GROOVY-8111 void testBuildCommonTypeFromTwoClassesWithTwoCommonInterfacesOneIsSelfReferential() { ClassNode a = boolean_TYPE - ClassNode b = extractTypesFromCode("${getClass().getName()}.Pair<String,String> type").type + ClassNode b = extractTypesFromCode("${this.class.name}.Pair<String,String> type").type ClassNode lub = lowestUpperBound(a, b) assert lub.superClass == OBJECT_TYPE - assert lub.interfaces as Set == [make(Comparable), make(Serializable)] as Set + assert lub.interfaces as Set == [COMPARABLE_TYPE, make(Serializable)] as Set lub = lowestUpperBound(b, a) assert lub.superClass == OBJECT_TYPE - assert lub.interfaces as Set == [make(Comparable), make(Serializable)] as Set + assert lub.interfaces as Set == [COMPARABLE_TYPE, make(Serializable)] as Set } void testStringWithGString() { ClassNode a = make(String) ClassNode b = make(GString) ClassNode type = lowestUpperBound(a,b) - assert type.interfaces as Set == [make(CharSequence), make(Comparable), make(Serializable)] as Set + assert type.interfaces as Set == [make(CharSequence), COMPARABLE_TYPE, make(Serializable)] as Set } void testDistinctPrimitiveTypes() { @@ -210,44 +217,44 @@ final class WideningCategoriesTest extends GenericsTestCase { } void testLUBWithTwoInterfacesAndSameGenericArg() { - ClassNode a = extractTypesFromCode("List<String> type").type - ClassNode b = extractTypesFromCode("List<String> type").type + ClassNode a = extractTypesFromCode('List<String> type').type + ClassNode b = extractTypesFromCode('List<String> type').type ClassNode lub = lowestUpperBound(a,b) - assert lub == make(List) + assert lub == LIST_TYPE assert lub.genericsTypes.length == 1 assert lub.genericsTypes[0].type == STRING_TYPE } void testLUBWithTwoInterfacesAndCommonSuperClassGenericArg() { - ClassNode a = extractTypesFromCode("List<Integer> type").type - ClassNode b = extractTypesFromCode("List<Long> type").type + ClassNode a = extractTypesFromCode('List<Integer> type').type + ClassNode b = extractTypesFromCode('List<Long> type').type ClassNode lub = lowestUpperBound(a,b) - assert lub == make(List) + assert lub == LIST_TYPE assert lub.genericsTypes.length == 1 assert lub.genericsTypes[0].wildcard assert lub.genericsTypes[0].upperBounds[0].superClass == Number_TYPE - assert make(Comparable) in lub.genericsTypes[0].upperBounds[0].interfaces + assert COMPARABLE_TYPE in lub.genericsTypes[0].upperBounds[0].interfaces } void testLUBWithTwoInterfacesAndSingleCommonInterface() { - ClassNode a = extractTypesFromCode("List<Set> type").type - ClassNode b = extractTypesFromCode("List<List> type").type + ClassNode a = extractTypesFromCode('List<Set> type').type + ClassNode b = extractTypesFromCode('List<List> type').type ClassNode lub = lowestUpperBound(a,b) - assert lub == make(List) + assert lub == LIST_TYPE assert lub.genericsTypes.length == 1 assert lub.genericsTypes[0].wildcard assert lub.genericsTypes[0].upperBounds[0] == make(Collection) } void testLUBWithTwoInterfacesAndNestedSingleCommonInterface() { - ClassNode a = extractTypesFromCode("Collection<List<Set>> type").type - ClassNode b = extractTypesFromCode("Collection<List<SortedSet>> type").type + ClassNode a = extractTypesFromCode('Collection<List<Set>> type').type + ClassNode b = extractTypesFromCode('Collection<List<SortedSet>> type').type ClassNode lub = lowestUpperBound(a,b) assert lub == make(Collection) assert lub.genericsTypes.length == 1 def nestedType = lub.genericsTypes[0].type - assert nestedType == make(List) - assert nestedType.genericsTypes.length==1 + assert nestedType == LIST_TYPE + assert nestedType.genericsTypes.length == 1 assert nestedType.genericsTypes[0].wildcard assert nestedType.genericsTypes[0].upperBounds[0] == make(Set) } @@ -276,18 +283,20 @@ final class WideningCategoriesTest extends GenericsTestCase { ClassNode b = extractTypesFromCode('org.codehaus.groovy.ast.tools.WideningCategoriesTest.PTopLong type').type ClassNode lub = lowestUpperBound(a,b) assert lub instanceof LowestUpperBoundClassNode // a virtual class which extends PTop<? extends Number> and implements Serializable + assert lub.interfaces == [make(Serializable)] assert lub.unresolvedSuperClass == make(PTop) assert lub.unresolvedSuperClass.genericsTypes.length == 1 assert lub.unresolvedSuperClass.genericsTypes[0].wildcard // ? extends Number - ClassNode genericType = lub.unresolvedSuperClass.genericsTypes[0].upperBounds[0] - assert genericType == Long_TYPE + ClassNode upperBound = lub.unresolvedSuperClass.genericsTypes[0].upperBounds[0] + assert upperBound.superClass == Number_TYPE + assert upperBound.interfaces.contains(COMPARABLE_TYPE) } void testCommonAssignableType() { def typeA = extractTypesFromCode('LinkedList type').type def typeB = extractTypesFromCode('List type').type def superType = lowestUpperBound(typeA, typeB) - assert superType == make(List) + assert superType == LIST_TYPE } void testCommonAssignableType2() { @@ -336,9 +345,8 @@ final class WideningCategoriesTest extends GenericsTestCase { def type = superType.genericsTypes[0] assert type.wildcard assert type.upperBounds[0] instanceof LowestUpperBoundClassNode - [Comparable, Serializable].each { - assert make(it) in type.upperBounds[0].interfaces - } + assert type.upperBounds[0].interfaces.contains(COMPARABLE_TYPE) + assert type.upperBounds[0].interfaces.contains(make(Serializable)) } void testLUBOfArrayTypes() { @@ -346,8 +354,7 @@ final class WideningCategoriesTest extends GenericsTestCase { def typeB = extractTypesFromCode('Integer[] type').type def superType = lowestUpperBound(typeA, typeB) assert superType.isArray() - def component = superType.getComponentType() - assert component == make(Number) + assert superType.componentType == Number_TYPE } // ---------- Classes and Interfaces used in this unit test ----------------
