You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/09/05 14:14:38 UTC

[groovy] branch GROOVY_2_5_X updated: GROOVY-10749: STC: closure parameter(s) as type witness

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 21771766d3 GROOVY-10749: STC: closure parameter(s) as type witness
21771766d3 is described below

commit 21771766d323004afcdf7230a0eb9a12a6b6da43
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sun Sep 4 11:39:21 2022 -0500

    GROOVY-10749: STC: closure parameter(s) as type witness
    
    2_5_X backport
---
 .../java/org/codehaus/groovy/ast/ClassNode.java    | 232 ++++++++++-----------
 .../java/org/codehaus/groovy/ast/GenericsType.java |   8 +-
 .../java/org/codehaus/groovy/ast/Parameter.java    |   2 +-
 .../transform/stc/StaticTypeCheckingVisitor.java   |  45 +---
 src/test/groovy/transform/stc/BugsSTCTest.groovy   |   3 +-
 .../groovy/transform/stc/GenericsSTCTest.groovy    |  66 +++++-
 6 files changed, 186 insertions(+), 170 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/ClassNode.java b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
index 1258bd7f57..eeeb8435c0 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
@@ -63,35 +63,35 @@ import java.util.Set;
  * <ol>
  * <li> Primary ClassNodes:<br>
  * A primary ClassNode is one where we have a source representation
- * which is to be compiled by Groovy and which we have an AST for. 
+ * which is to be compiled by Groovy and which we have an AST for.
  * The groovy compiler will output one class for each such ClassNode
  * that passes through AsmBytecodeGenerator... not more, not less.
  * That means for example Closures become such ClassNodes too at
- * some point. 
+ * some point.
  * <li> ClassNodes create through different sources (typically created
  * from a java.lang.reflect.Class object):<br>
  * The compiler will not output classes from these, the methods
  * usually do not contain bodies. These kind of ClassNodes will be
- * used in different checks, but not checks that work on the method 
+ * used in different checks, but not checks that work on the method
  * bodies. For example if such a ClassNode is a super class to a primary
- * ClassNode, then the abstract method test and others will be done 
- * with data based on these. Theoretically it is also possible to mix both 
+ * ClassNode, then the abstract method test and others will be done
+ * with data based on these. Theoretically it is also possible to mix both
  * (1 and 2) kind of classes in a hierarchy, but this probably works only
  *  in the newest Groovy versions. Such ClassNodes normally have to
- *  isResolved() returning true without having a redirect.In the Groovy 
- *  compiler the only version of this, that exists, is a ClassNode created 
+ *  isResolved() returning true without having a redirect.In the Groovy
+ *  compiler the only version of this, that exists, is a ClassNode created
  *  through a Class instance
  * <li> Labels:<br>
- * ClassNodes created through ClassHelper.makeWithoutCaching. They 
+ * ClassNodes created through ClassHelper.makeWithoutCaching. They
  * are place holders, its redirect points to the real structure, which can
  * be a label too, but following all redirects it should end with a ClassNode
- * from one of the other two categories. If ResolveVisitor finds such a 
- * node, it tries to set the redirects. Any such label created after 
- * ResolveVisitor has done its work needs to have a redirect pointing to 
- * case 1 or 2. If not the compiler may react strange... this can be considered 
- * as a kind of dangling pointer. 
+ * from one of the other two categories. If ResolveVisitor finds such a
+ * node, it tries to set the redirects. Any such label created after
+ * ResolveVisitor has done its work needs to have a redirect pointing to
+ * case 1 or 2. If not the compiler may react strange... this can be considered
+ * as a kind of dangling pointer.
  * </ol>
- * <b>Note:</b> the redirect mechanism is only allowed for classes 
+ * <b>Note:</b> the redirect mechanism is only allowed for classes
  * that are not primary ClassNodes. Typically this is done for classes
  * created by name only.  The redirect itself can be any type of ClassNode.
  * <p>
@@ -104,20 +104,21 @@ import java.util.Set;
  * @see org.codehaus.groovy.ast.ClassHelper
  */
 public class ClassNode extends AnnotatedNode implements Opcodes {
+
     private static class MapOfLists {
-        private Map<Object, List<MethodNode>> map;
+        Map<Object, List<MethodNode>> map;
 
-        public List<MethodNode> get(Object key) {
+        List<MethodNode> get(Object key) {
             return map == null ? null : map.get(key);
         }
 
-        public List<MethodNode> getNotNull(Object key) {
-            List<MethodNode> ret = get(key);
-            if (ret==null) ret = Collections.emptyList();
-            return ret;
+        List<MethodNode> getNotNull(Object key) {
+            List<MethodNode> list = get(key);
+            if (list == null) list = Collections.emptyList();
+            return list;
         }
 
-        public void put(Object key, MethodNode value) {
+        void put(Object key, MethodNode value) {
             if (map == null) {
                  map = new LinkedHashMap<Object, List<MethodNode>>();
             }
@@ -130,7 +131,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
             }
         }
 
-        public void remove(Object key, MethodNode value) {
+        void remove(Object key, MethodNode value) {
             get(key).remove(value);
         }
     }
@@ -153,15 +154,15 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     private Map<String, FieldNode> fieldIndex;
     private ModuleNode module;
     private CompileUnit compileUnit;
-    private boolean staticClass = false;
-    private boolean scriptBody = false;
+    private boolean staticClass;
+    private boolean scriptBody;
     private boolean script;
     private ClassNode superClass;
     protected boolean isPrimaryNode;
     protected List<InnerClassNode> innerClasses;
 
     /**
-     * The ASTTransformations to be applied to the Class
+     * The AST Transformations to be applied during compilation.
      */
     private Map<CompilePhase, Map<Class<? extends ASTTransformation>, Set<ASTNode>>> transformInstances;
 
@@ -189,16 +190,16 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     private boolean placeholder;
 
     /**
-     * Returns the ClassNode this ClassNode is redirecting to.
+     * Returns the {@code ClassNode} this node is a proxy for or the node itself.
      */
     public ClassNode redirect() {
-        if (redirect == null) return this;
-        return redirect.redirect();
+        return (redirect == null ? this : redirect.redirect());
     }
 
     /**
-     * Sets this instance as proxy for the given ClassNode.
-     * @param cn the class to redirect to. If set to null the redirect will be removed
+     * Sets this instance as proxy for the given {@code ClassNode}.
+     *
+     * @param node the class to redirect to; if {@code null} the redirect is removed
      */
     public void setRedirect(ClassNode cn) {
         if (isPrimaryNode) throw new GroovyBugError("tried to set a redirect for a primary ClassNode ("+getName()+"->"+cn.getName()+").");
@@ -208,8 +209,8 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * Returns a ClassNode representing an array of the class
-     * represented by this ClassNode
+     * Returns a {@code ClassNode} representing an array of the type represented
+     * by this.
      */
     public ClassNode makeArray() {
         if (redirect != null) {
@@ -229,14 +230,14 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * @return true if this instance is a primary ClassNode
+     * @return {@code true} if this instance is a primary {@code ClassNode}
      */
     public boolean isPrimaryClassNode() {
         return redirect().isPrimaryNode || (componentType != null && componentType.isPrimaryClassNode());
     }
 
-    /*
-     * Constructor used by makeArray() if no real class is available
+    /**
+     * Constructor used by {@code makeArray()} if no real class is available.
      */
     private ClassNode(ClassNode componentType) {
         this(componentType.getName() + "[]", ACC_PUBLIC, ClassHelper.OBJECT_TYPE);
@@ -244,8 +245,8 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
         isPrimaryNode = false;
     }
 
-    /*
-     * Constructor used by makeArray() if a real class is available
+    /**
+     * Constructor used by {@code makeArray()} if a real class is available.
      */
     private ClassNode(Class c, ClassNode componentType) {
         this(c);
@@ -254,8 +255,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * Creates a ClassNode from a real class. The resulting
-     * ClassNode will not be a primary ClassNode.
+     * Creates a non-primary {@code ClassNode} from a real class.
      */
     public ClassNode(Class c) {
         this(c.getName(), c.getModifiers(), null, null, MixinNode.EMPTY_ARRAY);
@@ -265,24 +265,26 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * The complete class structure will be initialized only when really
-     * needed to avoid having too many objects during compilation
+     * The complete class structure will be initialized only when really needed
+     * to avoid having too many objects during compilation.
      */
     private void lazyClassInit() {
         if (lazyInitDone) return;
         synchronized (lazyInitLock) {
             if (redirect != null) {
-                throw new GroovyBugError("lazyClassInit called on a proxy ClassNode, that must not happen."+
+                throw new GroovyBugError("lazyClassInit called on a proxy ClassNode, that must not happen. " +
                                          "A redirect() call is missing somewhere!");
-            }   
+            }
             if (lazyInitDone) return;
             VMPluginFactory.getPlugin().configureClassNode(compileUnit, this);
             lazyInitDone = true;
         }
     }
 
-    // added to track the enclosing method for local inner classes
-    private MethodNode enclosingMethod = null;
+    /**
+     * Tracks the enclosing method for local inner classes.
+     */
+    private MethodNode enclosingMethod;
 
     public MethodNode getEnclosingMethod() {
         return redirect().enclosingMethod;
@@ -293,13 +295,12 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * Indicates that this class has been "promoted" to public by
-     * Groovy when in fact there was no public modifier explicitly
-     * in the source code. I.e. it remembers that it has applied
-     * Groovy's "public classes by default" rule.This property is
-     * typically only of interest to AST transform writers.
+     * Indicates that this class has been "promoted" to public by Groovy when in
+     * fact there was no public modifier explicitly in the source code. That is,
+     * it remembers that it has applied Groovy's "public classes by default" rule.
+     * This property is typically only of interest to AST transform writers.
      *
-     * @return true if this class is public but had no explicit public modifier
+     * @return {@code true} if node is public but had no explicit public modifier
      */
     public boolean isSyntheticPublic() {
         return syntheticPublic;
@@ -310,21 +311,18 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * @param name       is the full name of the class
-     * @param modifiers  the modifiers,
-     * @param superClass the base class name - use "java.lang.Object" if no direct
-     *                   base class
-     * @see org.objectweb.asm.Opcodes
+     * @param name       the fully-qualified name of the class
+     * @param modifiers  the modifiers; see {@link org.objectweb.asm.Opcodes}
+     * @param superClass the base class; use "java.lang.Object" if no direct base class
      */
     public ClassNode(String name, int modifiers, ClassNode superClass) {
         this(name, modifiers, superClass, EMPTY_ARRAY, MixinNode.EMPTY_ARRAY);
     }
 
     /**
-     * @param name       is the full name of the class
-     * @param modifiers  the modifiers,
-     * @param superClass the base class name - use "java.lang.Object" if no direct
-     *                   base class
+     * @param name       the fully-qualified name of the class
+     * @param modifiers  the modifiers; see {@link org.objectweb.asm.Opcodes}
+     * @param superClass the base class; use "java.lang.Object" if no direct base class
      * @param interfaces the interfaces for this class
      * @param mixins     the mixins for this class
      * @see org.objectweb.asm.Opcodes
@@ -350,14 +348,14 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * Sets the superclass of this ClassNode
+     * Sets the superclass of this {@code ClassNode}.
      */
     public void setSuperClass(ClassNode superClass) {
         redirect().superClass = superClass;
     }
 
     /**
-     * @return the list of FieldNode's associated with this ClassNode
+     * @return the fields associated with this {@code ClassNode}
      */
     public List<FieldNode> getFields() {
         if (redirect != null)
@@ -387,14 +385,14 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * @return the array of mixins associated with this ClassNode
+     * @return the mixins associated with this {@code ClassNode}
      */
     public MixinNode[] getMixins() {
         return redirect().mixins;
     }
 
     /**
-     * @return the list of methods associated with this ClassNode
+     * @return the methods associated with this {@code ClassNode}
      */
     public List<MethodNode> getMethods() {
         if (redirect != null)
@@ -404,8 +402,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * @return the list of abstract methods associated with this
-     * ClassNode or null if there are no such methods
+     * @return the abstract methods associated with this {@code ClassNode}
      */
     public List<MethodNode> getAbstractMethods() {
         List<MethodNode> result = new ArrayList<MethodNode>(3);
@@ -461,7 +458,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     public String setName(String name) {
-        return redirect().name=name;
+        return redirect().name = name;
     }
 
     public int getModifiers() {
@@ -489,9 +486,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * Finds a constructor matching the given parameters in this class.
-     *
-     * @return the constructor matching the given parameters or null
+     * @return the constructor matching the given parameters or {@code null}
      */
     public ConstructorNode getDeclaredConstructor(Parameter[] parameters) {
         for (ConstructorNode method : getDeclaredConstructors()) {
@@ -503,7 +498,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     public void removeConstructor(ConstructorNode node) {
-        redirect().constructors.remove(node);
+        getDeclaredConstructors().remove(node);
     }
 
     public ModuleNode getModule() {
@@ -638,7 +633,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
                                 ClassNode[] exceptions,
                                 Statement code) {
         MethodNode other = getDeclaredMethod(name, parameters);
-        // let's not add duplicate methods
+        // don't add duplicate methods
         if (other != null) {
             return other;
         }
@@ -664,7 +659,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * Adds a synthetic method as part of the compilation process
+     * Adds a synthetic method as part of the compilation process.
      */
     public MethodNode addSyntheticMethod(String name,
                                          int modifiers,
@@ -760,7 +755,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * @return outer class field or {@code null} if not found or this is not an inner class
+     * @return the field on the outer class or {@code null} if this is not an inner class
      */
     public FieldNode getOuterField(final String name) {
         if (redirect != null) {
@@ -946,8 +941,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * @return true if this class is derived from a groovy object
-     *         i.e. it implements GroovyObject
+     * @return {@code true} if this type implements {@code GroovyObject}
      */
     public boolean isDerivedFromGroovyObject() {
         return implementsInterface(ClassHelper.GROOVY_OBJECT_TYPE);
@@ -955,7 +949,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
 
     /**
      * @param classNode the class node for the interface
-     * @return true if this class or any base class implements the given interface
+     * @return {@code true} if this type implements the given interface
      */
     public boolean implementsInterface(ClassNode classNode) {
         for (ClassNode node = redirect(); node != null; node = node.getSuperClass()) {
@@ -968,13 +962,12 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
 
     /**
      * @param classNode the class node for the interface
-     * @return true if this class declares that it implements the given interface
-     * or if one of its interfaces extends directly or indirectly the interface
+     * @return {@code true} if this class declares that it implements the given
+     * interface or if one of its interfaces extends directly/indirectly the interface
      *
      * NOTE: Doesn't consider an interface to implement itself.
      * I think this is intended to be called on ClassNodes representing
      * classes, not interfaces.
-     * 
      */
     public boolean declaresInterface(ClassNode classNode) {
         ClassNode[] interfaces = getInterfaces();
@@ -988,7 +981,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * @return the ClassNode of the super class of this type
+     * @return the {@code ClassNode} of the super class of this type
      */
     public ClassNode getSuperClass() {
         if (!lazyInitDone && !isResolved()) {
@@ -1039,7 +1032,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * @return true if the two arrays are of the same size and have the same contents
+     * @return {@code true} if the two arrays are of the same size and have the same contents
      * @deprecated
      */
     @Deprecated
@@ -1047,9 +1040,6 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
         return ParameterUtils.parametersEqual(a, b);
     }
 
-    /**
-     * @return the package name of this class
-     */
     public String getPackageName() {
         int idx = getName().lastIndexOf('.');
         if (idx > 0) {
@@ -1143,7 +1133,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * @return Returns true if this inner class or closure was declared inside a script body
+     * @return {@code true} if this inner class or closure was declared inside a script body
      */
     public boolean isScriptBody() {
         return redirect().scriptBody;
@@ -1167,53 +1157,53 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
 
     public String toString(boolean showRedirect) {
         if (isArray()) {
-            return componentType.toString(showRedirect)+"[]";
+            return getComponentType().toString(showRedirect) + "[]";
         }
-        StringBuilder ret = new StringBuilder(getName());
-        if (placeholder) ret = new StringBuilder(getUnresolvedName());
-        if (!placeholder && genericsTypes != null) {
+        boolean placeholder = isGenericsPlaceHolder();
+        StringBuilder ret = new StringBuilder(!placeholder ? getName() : getUnresolvedName());
+        GenericsType[] genericsTypes = getGenericsTypes();
+        if (!placeholder && genericsTypes != null && genericsTypes.length > 0) {
             ret.append(" <");
-            for (int i = 0; i < genericsTypes.length; i++) {
+            for (int i = 0, n = genericsTypes.length; i < n; i += 1) {
                 if (i != 0) ret.append(", ");
-                GenericsType genericsType = genericsTypes[i];
-                ret.append(genericTypeAsString(genericsType));
+                ret.append(genericTypeAsString(genericsTypes[i]));
             }
             ret.append(">");
         }
-        if (redirect != null && showRedirect) {
-            ret.append(" -> ").append(redirect().toString());
+        if (showRedirect && redirect != null) {
+            ret.append(" -> ").append(redirect);
         }
         return ret.toString();
     }
 
     /**
-     * This exists to avoid a recursive definition of toString. The default toString
-     * in GenericsType calls ClassNode.toString(), which calls GenericsType.toString(), etc. 
-     * @param genericsType
-     * @return the string representing the generic type
+     * Avoids a recursive definition of toString. The default {@code toString}
+     * in {@link GenericsType} calls {@code ClassNode.toString()}, which would
+     * call {@code GenericsType.toString()} without this method.
      */
     private String genericTypeAsString(GenericsType genericsType) {
-        StringBuilder ret = new StringBuilder(genericsType.getName());
+        String name = genericsType.getName();
         if (genericsType.getUpperBounds() != null) {
-            ret.append(" extends ");
-            for (int i = 0; i < genericsType.getUpperBounds().length; i++) {
-                ClassNode classNode = genericsType.getUpperBounds()[i];
-                if (classNode.equals(this)) {
-                    ret.append(classNode.getName());
-                } else {
-                    ret.append(classNode.toString(false));
-                }
-                if (i + 1 < genericsType.getUpperBounds().length) ret.append(" & ");
-            }
-        } else if (genericsType.getLowerBound() !=null) {
-            ClassNode classNode = genericsType.getLowerBound();
-            if (classNode.equals(this)) {
-                ret.append(" super ").append(classNode.getName());
-            } else {
-                ret.append(" super ").append(classNode.toString(false));
+            StringBuilder bounds = new StringBuilder();
+            ClassNode[] upperBounds = genericsType.getUpperBounds();
+            for (int i = 0, n = upperBounds.length; i < n; i += 1) {
+                if (i != 0) bounds.append(" & ");
+                bounds.append(toStringTerminal(upperBounds[i]));
             }
+            return name + " extends " + bounds;
+        } else if (genericsType.getLowerBound() != null) {
+            return name + " super " + toStringTerminal(genericsType.getLowerBound());
+        } else {
+            return name;
+        }
+    }
+
+    private String toStringTerminal(ClassNode classNode) {
+        if (classNode.equals(this)) {
+            return classNode.getName();
+        } else {
+            return classNode.toString(false);
         }
-        return ret.toString();
     }
 
     /**
@@ -1320,11 +1310,12 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * Returns true if the given method has a possibly matching static method with the given name and arguments.
+     * Checks if the given method has a possibly matching static method with the
+     * given name and arguments.
      *
      * @param name      the name of the method of interest
      * @param arguments the arguments to match against
-     * @return true if a matching method was found
+     * @return {@code true} if a matching method was found
      */
     public boolean hasPossibleStaticMethod(String name, Expression arguments) {
         return ClassNodeUtils.hasPossibleStaticMethod(this, name, arguments, false);
@@ -1375,8 +1366,7 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     }
 
     /**
-     * Marks if the current class uses annotations or not
-     * @param flag
+     * Marks if the current class uses annotations or not.
      */
     public void setAnnotated(boolean flag) {
         this.annotated = flag;
diff --git a/src/main/java/org/codehaus/groovy/ast/GenericsType.java b/src/main/java/org/codehaus/groovy/ast/GenericsType.java
index e07a63b225..cd123a1a44 100644
--- a/src/main/java/org/codehaus/groovy/ast/GenericsType.java
+++ b/src/main/java/org/codehaus/groovy/ast/GenericsType.java
@@ -76,7 +76,7 @@ public class GenericsType extends ASTNode {
 
         if (placeholder) visited.add(name);
 
-        StringBuilder ret = new StringBuilder(wildcard ? "?" : ((type == null || placeholder) ? name : genericsBounds(type, visited)));
+        StringBuilder ret = new StringBuilder(wildcard || placeholder ? name : genericsBounds(type, visited));
         if (lowerBound != null) {
             ret.append(" super ").append(genericsBounds(lowerBound, visited));
         } else if (upperBounds != null
@@ -114,6 +114,8 @@ public class GenericsType extends ASTNode {
     private static StringBuilder appendName(final ClassNode theType, final StringBuilder sb) {
         if (theType.isArray()) {
             appendName(theType.getComponentType(), sb).append("[]");
+        } else if (theType.isGenericsPlaceHolder()) {
+            sb.append(theType.getUnresolvedName());
         } else if (theType.getOuterClass() != null) {
             String parentClassNodeName = theType.getOuterClass().getName();
             if (Modifier.isStatic(theType.getModifiers()) || theType.isInterface()) {
@@ -124,13 +126,13 @@ public class GenericsType extends ASTNode {
             sb.append('.');
             sb.append(theType.getName(), parentClassNodeName.length() + 1, theType.getName().length());
         } else {
-            sb.append(theType.isGenericsPlaceHolder() ? theType.getUnresolvedName() : theType.getName());
+            sb.append(theType.getName());
         }
         return sb;
     }
 
     public String getName() {
-        return name;
+        return (isWildcard() ? "?" : name);
     }
 
     public void setName(final String name) {
diff --git a/src/main/java/org/codehaus/groovy/ast/Parameter.java b/src/main/java/org/codehaus/groovy/ast/Parameter.java
index 65bebe1a78..d2f2211c3d 100644
--- a/src/main/java/org/codehaus/groovy/ast/Parameter.java
+++ b/src/main/java/org/codehaus/groovy/ast/Parameter.java
@@ -52,7 +52,7 @@ public class Parameter extends AnnotatedNode implements Variable {
     }
 
     public String toString() {
-        return super.toString() + "[name:" + name + ((type == null) ? "" : " type: " + type.getName()) + ", hasDefaultValue: " + this.hasInitialExpression() + "]";
+        return super.toString() + "[name: " + name + (type == null ? "" : ", type: " + type.toString(false)) + ", hasDefaultValue: " + this.hasInitialExpression() + "]";
     }
 
     public String getName() {
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 67bb6291b4..1325eee92a 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -5413,7 +5413,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
      */
     private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType, final Map<GenericsTypeName, GenericsType> placeholders) {
         // use the generics information from Closure to further specify the type
-        if (closureType.isUsingGenerics()) {
+        if (isClosureWithType(closureType)) {
             ClassNode closureReturnType = closureType.getGenericsTypes()[0].getType();
 
             // the SAM's return type exactly corresponds to the inferred closure return type
@@ -5422,44 +5422,11 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             Parameter[] parameters = sam.getParameters();
             // repeat the same for each parameter given in the ClosureExpression
             if (parameters.length > 0 && expression instanceof ClosureExpression) {
-                List<ClassNode[]> genericsToConnect = new ArrayList<ClassNode[]>();
-                ClassNode[] closureParamTypes = expression.getNodeMetaData(StaticTypesMarker.CLOSURE_ARGUMENTS);
-                if (closureParamTypes == null)
-                    closureParamTypes = extractTypesFromParameters(((ClosureExpression) expression).getParameters());
-
-                for (int i = 0, n = parameters.length; i < n; i += 1) {
-                    Parameter parameter = parameters[i];
-                    if (parameter.getOriginType().isUsingGenerics() && closureParamTypes.length > i) {
-                        genericsToConnect.add(new ClassNode[]{closureParamTypes[i], parameter.getOriginType()});
-                    }
-                }
-                for (ClassNode[] classNodes : genericsToConnect) {
-                    ClassNode found = classNodes[0];
-                    ClassNode expected = classNodes[1];
-                    if (!isAssignableTo(found, expected)) {
-                        // probably facing a type mismatch
-                        continue;
-                    }
-                    ClassNode generifiedType = GenericsUtils.parameterizeType(found, expected);
-                    while (expected.isArray()) {
-                        expected = expected.getComponentType();
-                        generifiedType = generifiedType.getComponentType();
-                    }
-                    if (expected.isGenericsPlaceHolder()) {
-                        placeholders.put(new GenericsTypeName(expected.getGenericsTypes()[0].getName()), new GenericsType(generifiedType));
-                    } else {
-                        GenericsType[] expectedGenericsTypes = expected.getGenericsTypes();
-                        GenericsType[] foundGenericsTypes = generifiedType.getGenericsTypes();
-
-                        for (int i = 0, n = expectedGenericsTypes.length; i < n; i += 1) {
-                            GenericsType type = expectedGenericsTypes[i];
-                            if (type.isPlaceholder()) {
-                                String name = type.getName();
-                                placeholders.put(new GenericsTypeName(name), foundGenericsTypes[i]);
-                            }
-                        }
-                    }
-                }
+                ClassNode[] paramTypes = applyGenericsContext(placeholders, extractTypesFromParameters(parameters));
+                int i = 0;
+                // GROOVY-10054, GROOVY-10699, GROOVY-10749, et al.
+                for (Parameter p : getParametersSafe((ClosureExpression) expression))
+                    if (!p.isDynamicTyped()) extractGenericsConnections(placeholders, p.getType(), paramTypes[i++]);
             }
         }
 
diff --git a/src/test/groovy/transform/stc/BugsSTCTest.groovy b/src/test/groovy/transform/stc/BugsSTCTest.groovy
index 395894d97d..9ec2413d9b 100644
--- a/src/test/groovy/transform/stc/BugsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/BugsSTCTest.groovy
@@ -238,7 +238,8 @@ class BugsSTCTest extends StaticTypeCheckingTestCase {
             L<String> items = ['foo', 'bar'] as L<String>
             items.removeIf({a, b -> 1} as Comparator<?>)
             assert items
-        ''', 'Cannot call L <String>#removeIf(java.util.Comparator <java.lang.Object super java.lang.String>) with arguments [java.util.Comparator <?>]'
+        ''',
+        'Cannot call L <String>#removeIf(java.util.Comparator <? super java.lang.String>) with arguments [java.util.Comparator <?>]'
     }
 
     void testGroovy5482ListsAndFlowTyping() {
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 62d6415254..91d80ea251 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -284,6 +284,60 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    @NotYetImplemented // GROOVY-10646
+    void testReturnTypeInferenceWithMethodGenerics28() {
+        String types = '''
+            class Model {
+            }
+            interface Output<T> {
+                T getT()
+            }
+            abstract class WhereDSL<Type> {
+                abstract Type where()
+            }
+            abstract class Input<T> extends WhereDSL<ReferencesOuterClassTP> {
+                class ReferencesOuterClassTP implements Output<T> {
+                    @Override T getT() { return null }
+                }
+            }
+        '''
+        assertScript types + '''
+            void m(Input<Model> input) {
+                def output = input.where()
+                @ASTTest(phase=INSTRUCTION_SELECTION, value={
+                    assert node.getNodeMetaData(INFERRED_TYPE).toString(false) == 'Model'
+                })
+                def result = output.getT()
+            }
+        '''
+        assertScript types + '''
+            @FunctionalInterface
+            interface Xform extends java.util.function.Function<Input<Model>, Output<Model>> {
+            }
+
+            void select(Xform xform) {
+            }
+
+            select { input ->
+                def result = input.where()
+                return result // Cannot return value of type Input$ReferencesOuterClassTP for closure expecting Output<Model>
+            }
+        '''
+    }
+
+    // GROOVY-10749
+    void testReturnTypeInferenceWithMethodGenerics29() {
+        String named = 'class Named { String name }'
+
+        assertScript named + '''
+            @ASTTest(phase=INSTRUCTION_SELECTION, value={
+                def type = node.getNodeMetaData(INFERRED_TYPE)
+                assert type.toString(false) == 'java.util.stream.Collector <Named, ?, java.util.Map>'
+            })
+            def collector = java.util.stream.Collectors.groupingBy { Named named -> named.getName() }
+        '''
+    }
+
     void testDiamondInferrenceFromConstructor1() {
         assertScript '''
             class Foo<U> {
@@ -1048,7 +1102,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             List<String> list = new LinkedList<String>([1,2,3])
         ''',
-        'Cannot call java.util.LinkedList <String>#<init>(java.util.Collection <java.lang.Object extends java.lang.String>) with arguments [java.util.List <java.lang.Integer>]'
+        'Cannot call java.util.LinkedList <String>#<init>(java.util.Collection <? extends java.lang.String>) with arguments [java.util.List <java.lang.Integer>]'
     }
 
     void testCompatibleGenericAssignmentWithInference() {
@@ -1824,13 +1878,15 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             List<String> list = ['a','b','c']
             Collection<Integer> e = (Collection<Integer>) [1,2,3]
             boolean r = list.addAll(e)
-        ''', 'Cannot call java.util.List <java.lang.String>#addAll(java.util.Collection <java.lang.Object extends java.lang.String>) with arguments [java.util.Collection <Integer>]'
+        ''',
+        'Cannot call java.util.List <java.lang.String>#addAll(java.util.Collection <? extends java.lang.String>) with arguments [java.util.Collection <Integer>]'
     }
 
     // GROOVY-5528
     void testAssignmentToInterfaceFromUserClassWithGenerics() {
-        assertScript '''class UserList<T> extends LinkedList<T> {}
-        List<String> list = new UserList<String>()
+        assertScript '''
+            class UserList<T> extends LinkedList<T> {}
+            List<String> list = new UserList<String>()
         '''
     }
 
@@ -3023,7 +3079,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             List<Object> l = new ArrayList<>()
             assert foo(l) == 1
         ''',
-        '#foo(java.util.List <A extends A>) with arguments [java.util.ArrayList <java.lang.Object>]'
+        '#foo(java.util.List <? extends A>) with arguments [java.util.ArrayList <java.lang.Object>]'
     }
 
     // GROOVY-5891