diff --git a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java index 330d02d35b1..1d195349734 100644 --- a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java +++ b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java @@ -50,7 +50,6 @@ import org.codehaus.groovy.classgen.asm.util.TypeUtil; import org.codehaus.groovy.runtime.GeneratedClosure; import org.codehaus.groovy.runtime.GeneratedLambda; -import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport; import org.codehaus.groovy.transform.trait.Traits; import org.codehaus.groovy.util.ManagedConcurrentMap; import org.codehaus.groovy.util.ReferenceBundle; @@ -70,6 +69,8 @@ import java.util.Map; import java.util.regex.Pattern; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf; + /** * Helper for {@link ClassNode} and classes handling them. Contains a set of * pre-defined instances for the most used types and some code for cached node @@ -209,7 +210,7 @@ public static ClassNode[] make(Class[] classes) { * A new ClassNode object is only created if the class * is not one of the predefined ones * - * @param c class used to created the ClassNode + * @param c class used to create the ClassNode * @return ClassNode instance created from the given class */ public static ClassNode make(Class c) { @@ -487,39 +488,30 @@ private static boolean hasUsableImplementation(ClassNode c, MethodNode m) { } /** - * Returns a super class or interface for a given class depending on a given target. - * If the target is no super class or interface, then null will be returned. - * For a non-primitive array type, returns an array of the componentType's super class - * or interface if the target is also an array. - * - * @param clazz the start class - * @param goalClazz the goal class - * @return the next super class or interface + * Returns a super class or interface for a given class depending on supplied + * target. If the target is not a super class or interface, then null will be + * returned. For a non-primitive array type -- if the target is also an array + * -- returns an array of the component type's super class or interface. */ - public static ClassNode getNextSuperClass(ClassNode clazz, ClassNode goalClazz) { - if (clazz.isArray()) { - if (!goalClazz.isArray()) return null; - ClassNode cn = getNextSuperClass(clazz.getComponentType(), goalClazz.getComponentType()); + public static ClassNode getNextSuperClass(final ClassNode source, final ClassNode target) { + if (source.isArray()) { + if (!target.isArray()) return null; + + ClassNode cn = getNextSuperClass(source.getComponentType(), target.getComponentType()); if (cn != null) cn = cn.makeArray(); return cn; } - if (!goalClazz.isInterface()) { - if (clazz.isInterface()) { - if (OBJECT_TYPE.equals(clazz)) return null; - return OBJECT_TYPE; - } else { - return clazz.getUnresolvedSuperClass(); + if (target.isInterface()) { + for (ClassNode face : source.getUnresolvedInterfaces()) { + if (implementsInterfaceOrIsSubclassOf(face,target)) { + return face; + } } + } else if (source.isInterface()) { + return OBJECT_TYPE; } - ClassNode[] interfaces = clazz.getUnresolvedInterfaces(); - for (ClassNode anInterface : interfaces) { - if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(anInterface, goalClazz)) { - return anInterface; - } - } - //none of the interfaces here match, so continue with super class - return clazz.getUnresolvedSuperClass(); + return source.getUnresolvedSuperClass(); } } diff --git a/src/main/java/org/codehaus/groovy/ast/decompiled/DecompiledClassNode.java b/src/main/java/org/codehaus/groovy/ast/decompiled/DecompiledClassNode.java index 232e70f1edc..d7ed3186863 100644 --- a/src/main/java/org/codehaus/groovy/ast/decompiled/DecompiledClassNode.java +++ b/src/main/java/org/codehaus/groovy/ast/decompiled/DecompiledClassNode.java @@ -39,13 +39,12 @@ * @see AsmDecompiler */ public class DecompiledClassNode extends ClassNode { + private final ClassStub classData; private final AsmReferenceResolver resolver; - private volatile boolean supersInitialized; - private volatile boolean membersInitialized; public DecompiledClassNode(final ClassStub classData, final AsmReferenceResolver resolver) { - super(classData.className, getFullModifiers(classData), null, null, MixinNode.EMPTY_ARRAY); + super(classData.className, getModifiers(classData), null, null, MixinNode.EMPTY_ARRAY); this.classData = classData; this.resolver = resolver; isPrimaryNode = false; @@ -54,13 +53,14 @@ public DecompiledClassNode(final ClassStub classData, final AsmReferenceResolver /** * Handle the case of inner classes returning the correct modifiers from * the INNERCLASS reference since the top-level modifiers for inner classes - * wont include static or private/protected. + * won't include static or private/protected. */ - private static int getFullModifiers(ClassStub data) { - return (data.innerClassModifiers == -1) - ? data.accessModifiers : data.innerClassModifiers; + private static int getModifiers(ClassStub classData) { + return (classData.innerClassModifiers != -1 ? classData.innerClassModifiers : classData.accessModifiers); } + // + public long getCompilationTimeStamp() { if (classData.fields != null) { for (FieldStub field : classData.fields) { @@ -76,64 +76,40 @@ public long getCompilationTimeStamp() { } @Override - public GenericsType[] getGenericsTypes() { - lazyInitSupers(); - return super.getGenericsTypes(); - } - - @Override - public boolean isUsingGenerics() { - lazyInitSupers(); - return super.isUsingGenerics(); - } - - @Override - public List getFields() { - lazyInitMembers(); - return super.getFields(); + public Class getTypeClass() { + return resolver.resolveJvmClass(getName()); } - @Override - public ClassNode[] getInterfaces() { - lazyInitSupers(); - return super.getInterfaces(); + public boolean isParameterized() { + return (classData.signature != null && classData.signature.charAt(0) == '<'); } @Override - public List getMethods() { - lazyInitMembers(); - return super.getMethods(); + public boolean isResolved() { + return true; } @Override - public List getDeclaredConstructors() { - lazyInitMembers(); - return super.getDeclaredConstructors(); + public String setName(String name) { + throw new UnsupportedOperationException(); } @Override - public FieldNode getDeclaredField(String name) { - lazyInitMembers(); - return super.getDeclaredField(name); + public void setRedirect(ClassNode cn) { + throw new UnsupportedOperationException(); } @Override - public List getDeclaredMethods(String name) { - lazyInitMembers(); - return super.getDeclaredMethods(name); + public void setUsingGenerics(boolean b) { + throw new UnsupportedOperationException(); } @Override - public ClassNode getUnresolvedSuperClass(boolean useRedirect) { - lazyInitSupers(); - return super.getUnresolvedSuperClass(useRedirect); + public void setGenericsPlaceHolder(boolean b) { + throw new UnsupportedOperationException(); } - @Override - public ClassNode[] getUnresolvedInterfaces(boolean useRedirect) { - lazyInitSupers(); - return super.getUnresolvedInterfaces(useRedirect); - } + //-------------------------------------------------------------------------- @Override public List getAnnotations() { @@ -148,38 +124,36 @@ public List getAnnotations(ClassNode type) { } @Override - public void setRedirect(ClassNode cn) { - throw new UnsupportedOperationException(); + public GenericsType[] getGenericsTypes() { + lazyInitSupers(); + return super.getGenericsTypes(); } @Override - public void setGenericsPlaceHolder(boolean b) { - throw new UnsupportedOperationException(); + public ClassNode[] getInterfaces() { + lazyInitSupers(); + return super.getInterfaces(); } @Override - public void setUsingGenerics(boolean b) { - throw new UnsupportedOperationException(); + public ClassNode[] getUnresolvedInterfaces(boolean useRedirect) { + lazyInitSupers(); + return super.getUnresolvedInterfaces(useRedirect); } @Override - public String setName(String name) { - throw new UnsupportedOperationException(); - } - - public boolean isParameterized() { - return (classData.signature != null && classData.signature.charAt(0) == '<'); + public ClassNode getUnresolvedSuperClass(boolean useRedirect) { + lazyInitSupers(); + return super.getUnresolvedSuperClass(useRedirect); } @Override - public boolean isResolved() { - return true; + public boolean isUsingGenerics() { + lazyInitSupers(); + return super.isUsingGenerics(); } - @Override - public Class getTypeClass() { - return resolver.resolveJvmClass(getName()); - } + private volatile boolean supersInitialized; private void lazyInitSupers() { if (supersInitialized) return; @@ -193,6 +167,40 @@ private void lazyInitSupers() { } } + //-------------------------------------------------------------------------- + + @Override + public List getDeclaredConstructors() { + lazyInitMembers(); + return super.getDeclaredConstructors(); + } + + @Override + public FieldNode getDeclaredField(final String name) { + lazyInitMembers(); + return super.getDeclaredField(name); + } + + @Override + public List getDeclaredMethods(final String name) { + lazyInitMembers(); + return super.getDeclaredMethods(name); + } + + @Override + public List getFields() { + lazyInitMembers(); + return super.getFields(); + } + + @Override + public List getMethods() { + lazyInitMembers(); + return super.getMethods(); + } + + private volatile boolean membersInitialized; + private void lazyInitMembers() { if (membersInitialized) return; @@ -200,7 +208,7 @@ private void lazyInitMembers() { if (!membersInitialized) { if (classData.methods != null) { for (MethodStub method : classData.methods) { - if (isConstructor(method)) { + if ("".equals(method.methodName)) { addConstructor(createConstructor(method)); } else { addMethod(createMethodNode(method)); @@ -249,10 +257,6 @@ private ConstructorNode createConstructor(final MethodStub method) { return constructorNodeSupplier.get(); } - private boolean isConstructor(MethodStub method) { - return "".equals(method.methodName); - } - private T addAnnotations(MemberStub stub, T node) { List annotations = stub.annotations; if (annotations != null) { diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java index 7de64804752..d0b17f0161c 100644 --- a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java +++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java @@ -54,7 +54,6 @@ import java.util.function.Function; import java.util.function.Predicate; -import static groovy.lang.Tuple.tuple; import static org.apache.groovy.util.SystemUtil.getSystemPropertySafe; import static org.codehaus.groovy.runtime.DefaultGroovyMethods.plus; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf; @@ -130,15 +129,11 @@ public static GenericsType[] alignGenericTypes(final GenericsType[] redirectGene } /** - * Generates a wildcard generic type in order to be used for checks against class nodes. - * See {@link GenericsType#isCompatibleWith(org.codehaus.groovy.ast.ClassNode)}. - * - * @param types the type to be used as the wildcard upper bound - * @return a wildcard generics type + * Generates a wildcard generic type in order to be used for checks against + * class nodes. See {@link GenericsType#isCompatibleWith(ClassNode)}. */ - public static GenericsType buildWildcardType(final ClassNode... types) { - ClassNode base = ClassHelper.makeWithoutCaching("?"); - GenericsType gt = new GenericsType(base, types, null); + public static GenericsType buildWildcardType(final ClassNode... upperBounds) { + GenericsType gt = new GenericsType(ClassHelper.makeWithoutCaching("?"), upperBounds, null); gt.setWildcard(true); return gt; } @@ -170,31 +165,31 @@ public static void extractPlaceholders(final ClassNode type, final Map typeArguments = new ArrayList<>(n); for (int i = 0; i < n; i += 1) { GenericsType rgt = redirectGenericsTypes[i]; - if (rgt.isPlaceholder()) { - GenericsType typeArgument = parameterized[i]; - placeholders.computeIfAbsent(new GenericsType.GenericsTypeName(rgt.getName()), name -> { + if (rgt.isPlaceholder()) { // type parameter + GenericsType typeArgument = genericsTypes[i]; + placeholders.computeIfAbsent(new GenericsType.GenericsTypeName(rgt.getName()), x -> { typeArguments.add(typeArgument); return typeArgument; }); @@ -256,31 +251,47 @@ public static ClassNode parameterizeInterfaceGenerics(final ClassNode hint, fina * arguments. This method allows returning a parameterized interface given the parameterized class * node which implements this interface. * - * @param hint the class node where generics types are parameterized + * @param hint the ClassNode where generics types are parameterized * @param target the interface we want to parameterize generics types - * @return a parameterized interface class node + * @return a parameterized interface ClassNode */ - public static ClassNode parameterizeType(final ClassNode hint, final ClassNode target) { + public static ClassNode parameterizeType(ClassNode hint, final ClassNode target) { if (hint.isArray()) { if (target.isArray()) { return parameterizeType(hint.getComponentType(), target.getComponentType()).makeArray(); } return target; } - if (!target.equals(hint) && implementsInterfaceOrIsSubclassOf(target, hint)) { - ClassNode nextSuperClass = ClassHelper.getNextSuperClass(target, hint); - if (!hint.equals(nextSuperClass)) { - Map genericsSpec = createGenericsSpec(hint); - extractSuperClassGenerics(hint, nextSuperClass, genericsSpec); - ClassNode result = correctToGenericsSpecRecurse(genericsSpec, nextSuperClass); - return parameterizeType(result, target); - } + if (hint.isGenericsPlaceHolder()) { + ClassNode bound = hint.redirect(); + return parameterizeType(bound, target); + } + if (target.redirect().getGenericsTypes() == null) { + return target; + } + + ClassNode cn = target; + Map gt; + + // relationship may be reversed for cases like "Iterable x = []" + if (!cn.equals(hint) && implementsInterfaceOrIsSubclassOf(target, hint)) { + do { // walk target type hierarchy towards hint + cn = ClassHelper.getNextSuperClass(cn, hint); + if (hasUnresolvedGenerics(cn)) { + gt = createGenericsSpec(hint); + extractSuperClassGenerics(hint, cn, gt); + cn = correctToGenericsSpecRecurse(gt, cn); + } + } while (!cn.equals(hint)); + + hint = cn; } - Map genericsSpec = createGenericsSpec(hint); - ClassNode targetRedirect = target.redirect(); - genericsSpec = createGenericsSpec(targetRedirect, genericsSpec); - extractSuperClassGenerics(hint, targetRedirect, genericsSpec); - return correctToGenericsSpecRecurse(genericsSpec, targetRedirect); + + cn = target.redirect(); + gt = createGenericsSpec(hint); + gt = createGenericsSpec(cn, gt); + extractSuperClassGenerics(hint, cn, gt); + return correctToGenericsSpecRecurse(gt, cn); } public static ClassNode nonGeneric(final ClassNode type) { @@ -512,9 +523,9 @@ public static Map addMethodGenerics(MethodNode current, Map addMethodGenerics(MethodNode current, Map spec) { - // TODO: this method is very similar to StaticTypesCheckingSupport#extractGenericsConnections, - // but operates on ClassNodes instead of GenericsType - if (target == null || type == target) return; - if (type.isArray() && target.isArray()) { + // TODO: this is very similar to StaticTypesCheckingSupport#extractGenericsConnections, using ClassNode instead of GenericsType + public static void extractSuperClassGenerics(final ClassNode type, final ClassNode target, final Map spec) { + if (target == null || target == type) return; + if (target.isGenericsPlaceHolder()) { + spec.put(target.getUnresolvedName(), type); + } else if (type.isArray() && target.isArray()) { extractSuperClassGenerics(type.getComponentType(), target.getComponentType(), spec); - } else if (type.isArray() && JAVA_LANG_OBJECT.equals(target.getName())) { - // Object is superclass of arrays but no generics involved - } else if (target.isGenericsPlaceHolder() || type.equals(target) || !implementsInterfaceOrIsSubclassOf(type, target)) { - // structural match route - if (target.isGenericsPlaceHolder()) { - spec.put(target.getGenericsTypes()[0].getName(), type); - } else { - extractSuperClassGenerics(type.getGenericsTypes(), target.getGenericsTypes(), spec); - } + } else if (type.isArray() && target.getName().equals(JAVA_LANG_OBJECT)) { + // Object is the superclass of an array, but no generics are involved + } else if (type.equals(target) || !implementsInterfaceOrIsSubclassOf(type, target)) { + extractSuperClassGenerics(type.getGenericsTypes(), target.getGenericsTypes(), spec); } else { - // find matching super class or interface ClassNode superClass = getSuperClass(type, target); if (superClass != null) { extractSuperClassGenerics(correctToGenericsSpecRecurse(createGenericsSpec(type), superClass), target, spec); @@ -624,15 +630,15 @@ public static ClassNode[] parseClassNodesFromString(final String option, final S // the returned node is DummyNode makeDeclaringAndActualGenericsType * but the other will not try even if the parameterized type has placeholders * * @param declaringClass the generics class node declaring the generics types - * @param actualReceiver the sub-class class node + * @param actualReceiver the subclass class node * @return the placeholder-to-actualtype mapping * * @since 3.0.0 diff --git a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java index effd78626d8..fd1f1dfefe2 100644 --- a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java +++ b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java @@ -1588,45 +1588,37 @@ private void resolveGenericsHeader(final GenericsType[] types, final GenericsTyp continue; } - ClassNode classNode = type.getType(); String name = type.getName(); + ClassNode typeType = type.getType(); GenericsTypeName gtn = new GenericsTypeName(name); - ClassNode[] bounds = type.getUpperBounds(); - boolean isWild = QUESTION_MARK.equals(name); - boolean toDealWithGenerics = 0 == level || (level > 0 && null != genericParameterNames.get(gtn)); + boolean isWildcardGT = QUESTION_MARK.equals(name); + boolean dealWithGenerics = (level == 0 || (level > 0 && genericParameterNames.get(gtn) != null)); - if (bounds != null) { + if (type.getUpperBounds() != null) { boolean nameAdded = false; - for (ClassNode upperBound : bounds) { - if (!isWild) { - if (!nameAdded && upperBound != null || !resolve(classNode)) { - if (toDealWithGenerics) { - genericParameterNames.put(gtn, type); + for (ClassNode upperBound : type.getUpperBounds()) { + if (upperBound == null) continue; + if (!isWildcardGT) { + if (!nameAdded || !resolve(typeType)) { + if (dealWithGenerics) { type.setPlaceholder(true); - classNode.setRedirect(upperBound); + typeType.setRedirect(upperBound); + genericParameterNames.put(gtn, type); nameAdded = true; } } - - upperBoundsToResolve.add(tuple(upperBound, classNode)); + upperBoundsToResolve.add(tuple(upperBound, typeType)); } - - if (upperBound != null && upperBound.isUsingGenerics()) { + if (upperBound.isUsingGenerics()) { upperBoundsWithGenerics.add(tuple(upperBound, type)); } } } else { - if (!isWild) { - if (toDealWithGenerics) { - GenericsType originalGt = genericParameterNames.get(gtn); - genericParameterNames.put(gtn, type); + if (!isWildcardGT) { + if (dealWithGenerics) { type.setPlaceholder(true); - - if (null == originalGt) { - classNode.setRedirect(ClassHelper.OBJECT_TYPE); - } else { - classNode.setRedirect(originalGt.getType()); - } + GenericsType last = genericParameterNames.put(gtn, type); + typeType.setRedirect(last != null ? last.getType().redirect() : ClassHelper.OBJECT_TYPE); } } } diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java index 2235ec63ded..3672629b3b7 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java @@ -1018,9 +1018,6 @@ public static List chooseBestMethod(final ClassNode receiver, final if (!asBoolean(methods)) { return Collections.emptyList(); } - if (isUsingUncheckedGenerics(receiver)) { - return chooseBestMethod(makeRawType(receiver), methods, argumentTypes); - } int bestDist = Integer.MAX_VALUE; List bestChoices = new LinkedList<>(); @@ -1295,7 +1292,7 @@ public static boolean isUsingGenericsOrIsArrayUsingGenerics(final ClassNode cn) if (cn.isArray()) { return isUsingGenericsOrIsArrayUsingGenerics(cn.getComponentType()); } - return (cn.isUsingGenerics() && cn.getGenericsTypes() != null); + return (cn.isUsingGenerics() && (cn.getGenericsTypes() != null || cn.isGenericsPlaceHolder())); } /** @@ -1633,10 +1630,19 @@ static void applyGenericsConnections(final Map c checkForMorePlaceholders = checkForMorePlaceholders || !equalIncludingGenerics(oldValue, newValue); } else if (!newValue.isPlaceholder() || newValue != resolvedPlaceholders.get(name)) { // GROOVY-6787: Don't override the original if the replacement doesn't respect the bounds otherwise - // the original bounds are lost, which can result in accepting an incompatible type as an argument. + // the original bounds are lost, which can result in accepting an incompatible type as an argument! ClassNode replacementType = extractType(newValue); - if (oldValue.isCompatibleWith(replacementType)) { - entry.setValue(newValue); + ClassNode suitabilityType = !replacementType.isGenericsPlaceHolder() + ? replacementType : Optional.ofNullable(replacementType.getGenericsTypes()) + .map(gts -> extractType(gts[0])).orElse(replacementType.redirect()); + + if (oldValue.isCompatibleWith(suitabilityType)) { + if (newValue.isWildcard() && newValue.getLowerBound() == null && newValue.getUpperBounds() == null) { + // GROOVY-9998: apply upper/lower bound for unknown + entry.setValue(replacementType.asGenericsType()); + } else { + entry.setValue(newValue); + } if (!checkForMorePlaceholders && newValue.isPlaceholder()) { checkForMorePlaceholders = !equalIncludingGenerics(oldValue, newValue); } @@ -1654,7 +1660,7 @@ static void applyGenericsConnections(final Map c private static ClassNode extractType(GenericsType gt) { ClassNode cn; if (!gt.isPlaceholder()) { - cn = gt.getType(); + cn = getCombinedBoundType(gt); } else { // discard the placeholder cn = gt.getType().redirect(); @@ -1727,7 +1733,7 @@ private static boolean equalIncludingGenerics(final ClassNode orig, final ClassN * Should the target not have any generics this method does nothing. */ static void extractGenericsConnections(final Map connections, final ClassNode type, final ClassNode target) { - if (target == null || target == type || !isUsingGenericsOrIsArrayUsingGenerics(target)) return; + if (target == null || target == type || (!target.isGenericsPlaceHolder() && !isUsingGenericsOrIsArrayUsingGenerics(target))) return; if (type == null || type == UNKNOWN_PARAMETER_TYPE) return; if (target.isGenericsPlaceHolder()) { @@ -1741,18 +1747,21 @@ static void extractGenericsConnections(final Map ClassNode returnType = StaticTypeCheckingVisitor.wrapTypeIfNecessary(GenericsUtils.parameterizeSAM(target).getV2()); extractGenericsConnections(connections, type.getGenericsTypes(), new GenericsType[] {new GenericsType(returnType)}); - } else if (type.equals(target) || !implementsInterfaceOrIsSubclassOf(type, target)) { + } else if (type.equals(target)) { extractGenericsConnections(connections, type.getGenericsTypes(), target.getGenericsTypes()); - } else { // find matching super class or interface + } else if (implementsInterfaceOrIsSubclassOf(type, target)) { ClassNode superClass = GenericsUtils.getSuperClass(type, target); if (superClass != null) { if (GenericsUtils.hasUnresolvedGenerics(superClass)) { - // propagate type arguments to the super class or interface - Map spec = new HashMap<>(); - extractGenericsConnections(spec, type.getGenericsTypes(), - type.redirect().getGenericsTypes()); - superClass = applyGenericsContext(spec, superClass); + GenericsType[] tp = type.redirect().getGenericsTypes(); + if (tp != null) { + GenericsType[] ta = type.getGenericsTypes(); + // propagate type arguments to the super class or interface + Map spec = new HashMap<>(); + extractGenericsConnections(spec, ta, tp); + superClass = applyGenericsContext(spec, superClass); + } } extractGenericsConnections(connections, superClass, target); } else { @@ -1787,15 +1796,6 @@ private static void extractGenericsConnections(final Map" and ui like "List" - extractGenericsConnections(connections, ui.getType(), boundType); - }*/ extractGenericsConnections(connections, ui.getType(), boundType); } } else { 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 807b128f1c9..cc30f5e3c8c 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -2527,9 +2527,10 @@ && getType(nameExpr).equals(STRING_TYPE)) { List> receivers = new ArrayList<>(); addReceivers(receivers, makeOwnerList(expression.getExpression()), false); + ClassNode receiverType = null; List candidates = EMPTY_METHODNODE_LIST; for (Receiver currentReceiver : receivers) { - ClassNode receiverType = wrapTypeIfNecessary(currentReceiver.getType()); + receiverType = wrapTypeIfNecessary(currentReceiver.getType()); candidates = findMethodsWithGenerated(receiverType, nameText); if (isBeingCompiled(receiverType) && !receiverType.isInterface()) { @@ -2558,10 +2559,12 @@ && getType(nameExpr).equals(STRING_TYPE)) { } if (!candidates.isEmpty()) { - candidates.stream().map(MethodNode::getReturnType) - .reduce(WideningCategories::lowestUpperBound) - .filter(returnType -> !returnType.equals(OBJECT_TYPE)) - .ifPresent(returnType -> storeType(expression, wrapClosureType(returnType))); + Map gts = GenericsUtils.extractPlaceholders(receiverType); + candidates.stream().map(candidate -> applyGenericsContext(gts, candidate.getReturnType())) + .reduce(WideningCategories::lowestUpperBound).ifPresent(returnType -> { + ClassNode closureType = wrapClosureType(returnType); + storeType(expression, closureType); + }); expression.putNodeMetaData(MethodNode.class, candidates); } else if (!(expression instanceof MethodReferenceExpression)) { ClassNode type = wrapTypeIfNecessary(getType(expression.getExpression())); @@ -5602,15 +5605,17 @@ private static MethodNode chooseMethod(final MethodPointerExpression source, fin ClassNode[] paramTypes = samSignature.get(); return options.stream().filter((MethodNode option) -> { ClassNode[] types = collateMethodReferenceParameterTypes(source, option); - if (types.length == paramTypes.length) { - for (int i = 0, n = types.length; i < n; i += 1) { - if (!types[i].isGenericsPlaceHolder() && !isAssignableTo(types[i], paramTypes[i])) { - return false; - } + final int n = types.length; + if (n != paramTypes.length) { + return false; + } + for (int i = 0; i < n; i += 1) { + // param type represents incoming argument type + if (!isAssignableTo(paramTypes[i], types[i]) && !paramTypes[i].isGenericsPlaceHolder()) { + return false; } - return true; } - return false; + return true; }).findFirst().orElse(null); // TODO: order matches by param distance } @@ -6055,19 +6060,23 @@ private void reportPropertyType(ClassNode type, final ClassNode declaringClass) /** * Wrapper for a Parameter so it can be treated like a VariableExpression - * and tracked in the ifElseForWhileAssignmentTracker. - *

- * This class purposely does not adhere to the normal equals and hashCode - * contract on the Object class and delegates those calls to the wrapped - * variable. + * and tracked in the {@code ifElseForWhileAssignmentTracker}. */ - private static class ParameterVariableExpression extends VariableExpression { + private class ParameterVariableExpression extends VariableExpression { private final Parameter parameter; ParameterVariableExpression(final Parameter parameter) { super(parameter); this.parameter = parameter; - this.parameter.getNodeMetaData(INFERRED_TYPE, x -> parameter.getType()); + + ClassNode inferredType = getNodeMetaData(INFERRED_TYPE); + if (inferredType == null) { + inferredType = typeCheckingContext.controlStructureVariables.get(parameter); // for/catch/closure + if (inferredType == null) { + inferredType = getTypeFromClosureArguments(parameter); // @ClosureParams or SAM-type coercion + } + setNodeMetaData(INFERRED_TYPE, inferredType != null ? inferredType : parameter.getType()); // to parameter + } } @Override