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