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/02/26 16:55:30 UTC

[groovy] branch GROOVY_3_0_X updated: GROOVY-5441: check generics usage for casts, coercions, constructors, array initializers and object initializers

This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch GROOVY_3_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/GROOVY_3_0_X by this push:
     new 49905b5  GROOVY-5441: check generics usage for casts, coercions, constructors, array initializers and object initializers
49905b5 is described below

commit 49905b56ede7cee21010e0d29d6b522a45508caa
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sat Feb 26 10:48:43 2022 -0600

    GROOVY-5441: check generics usage for casts, coercions, constructors,
    array initializers and object initializers
    
    3_0_X backport
---
 .../groovy/ast/expr/VariableExpression.java        |  60 ++++--
 .../codehaus/groovy/control/GenericsVisitor.java   | 209 ++++++++++-----------
 src/test/gls/generics/GenericsUsageTest.groovy     | 152 ++++++++++-----
 src/test/groovy/bugs/Groovy3731Bug.groovy          |  44 -----
 4 files changed, 244 insertions(+), 221 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/expr/VariableExpression.java b/src/main/java/org/codehaus/groovy/ast/expr/VariableExpression.java
index 9043f7a..1dcdd05 100644
--- a/src/main/java/org/codehaus/groovy/ast/expr/VariableExpression.java
+++ b/src/main/java/org/codehaus/groovy/ast/expr/VariableExpression.java
@@ -50,55 +50,63 @@ public class VariableExpression extends Expression implements Variable {
         this.accessedVariable = origin;
     }
 
-    public VariableExpression(String variable, ClassNode type) {
-        this.variable = variable;
+    public VariableExpression(String name, ClassNode type) {
+        variable = name;
         originType = type;
-        setType(ClassHelper.getWrapper(type));
+        setType(ClassHelper.isPrimitiveType(type) ? ClassHelper.getWrapper(type) : type);
     }
-    
+
     public VariableExpression(String variable) {
         this(variable, ClassHelper.DYNAMIC_TYPE);
     }
-    
+
     public VariableExpression(Variable variable) {
         this(variable.getName(), variable.getOriginType());
         setAccessedVariable(variable);
         setModifiers(variable.getModifiers());
     }
 
+    @Override
     public void visit(GroovyCodeVisitor visitor) {
         visitor.visitVariableExpression(this);
     }
 
+    @Override
     public Expression transformExpression(ExpressionTransformer transformer) {
         return this;
     }
 
+    @Override
     public String getText() {
         return variable;
     }
-    
+
+    @Override
     public String getName() {
         return variable;
     }
 
+    @Override
     public String toString() {
         return super.toString() + "[variable: " + variable + (this.isDynamicTyped() ? "" : " type: " + getType()) + "]";
     }
 
+    @Override
     public Expression getInitialExpression() {
         return null;
     }
 
+    @Override
     public boolean hasInitialExpression() {
         return false;
     }
-    
+
+    @Override
     public boolean isInStaticContext() {
-        if (accessedVariable!=null && accessedVariable!=this) return accessedVariable.isInStaticContext();
+        if (accessedVariable != null && accessedVariable != this) return accessedVariable.isInStaticContext();
         return inStaticContext;
     }
-    
+
     public void setInStaticContext(boolean inStaticContext) {
         this.inStaticContext = inStaticContext;
     }
@@ -108,15 +116,18 @@ public class VariableExpression extends Expression implements Variable {
      * the {@link #getAccessedVariable() accessed variable} is ({@link #isClosureSharedVariable() shared},
      * this operation is unsafe and may lead to a verify error at compile time. Instead, set the type of
      * the {@link #getAccessedVariable() accessed variable}
+     *
      * @param cn the type to be set on this variable
      */
-    public void setType(ClassNode cn){
+    @Override
+    public void setType(ClassNode cn) {
         super.setType(cn);
-        isDynamicTyped |= ClassHelper.DYNAMIC_TYPE==cn;
+        isDynamicTyped |= (cn == ClassHelper.DYNAMIC_TYPE);
     }
-    
+
+    @Override
     public boolean isDynamicTyped() {
-        if (accessedVariable!=null && accessedVariable!=this) return accessedVariable.isDynamicTyped();
+        if (accessedVariable != null && accessedVariable != this) return accessedVariable.isDynamicTyped();
         return isDynamicTyped;
     }
 
@@ -127,10 +138,12 @@ public class VariableExpression extends Expression implements Variable {
      * def cl = { println str }
      * </pre>
      * The "str" variable is closure shared.
+     *
      * @return true if this variable is used in a closure
      */
+    @Override
     public boolean isClosureSharedVariable() {
-        if (accessedVariable!=null && accessedVariable!=this) return accessedVariable.isClosureSharedVariable();
+        if (accessedVariable != null && accessedVariable != this) return accessedVariable.isClosureSharedVariable();
         return closureShare;
     }
 
@@ -141,12 +154,15 @@ public class VariableExpression extends Expression implements Variable {
      * </pre>
      * The "str" variable is closure shared. The variable expression inside the closure references an
      * accessed variable "str" which must have the closure shared flag set.
+     *
      * @param inClosure tells if this variable is later referenced in a closure
      */
+    @Override
     public void setClosureSharedVariable(boolean inClosure) {
-        closureShare = inClosure;        
+        closureShare = inClosure;
     }
 
+    @Override
     public int getModifiers() {
         return modifiers;
     }
@@ -154,12 +170,13 @@ public class VariableExpression extends Expression implements Variable {
     /**
      * For internal use only. This flag is used by compiler internals and should probably
      * be converted to a node metadata in future.
+     *
      * @param useRef
      */
     public void setUseReferenceDirectly(boolean useRef) {
-        this.useRef = useRef;        
+        this.useRef = useRef;
     }
-    
+
     /**
      * For internal use only. This flag is used by compiler internals and should probably
      * be converted to a node metadata in future.
@@ -167,19 +184,22 @@ public class VariableExpression extends Expression implements Variable {
     public boolean isUseReferenceDirectly() {
         return useRef;
     }
-    
+
+    @Override
     public ClassNode getType() {
-        if (accessedVariable!=null && accessedVariable!=this) return accessedVariable.getType();
+        if (accessedVariable != null && accessedVariable != this) return accessedVariable.getType();
         return super.getType();
     }
 
     /**
      * Returns the type which was used when this variable expression was created. For example,
      * {@link #getType()} may return a boxed type while this method would return the primitive type.
+     *
      * @return the type which was used to define this variable expression
      */
+    @Override
     public ClassNode getOriginType() {
-        if (accessedVariable!=null && accessedVariable!=this) return accessedVariable.getOriginType();
+        if (accessedVariable != null && accessedVariable != this) return accessedVariable.getOriginType();
         return originType;
     }
 
diff --git a/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java b/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java
index 95e74cd..07a17a6 100644
--- a/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java
+++ b/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java
@@ -25,10 +25,12 @@ import org.codehaus.groovy.ast.GenericsType;
 import org.codehaus.groovy.ast.InnerClassNode;
 import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.expr.ArrayExpression;
+import org.codehaus.groovy.ast.expr.CastExpression;
 import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
 import org.codehaus.groovy.ast.expr.DeclarationExpression;
 import org.codehaus.groovy.ast.expr.Expression;
-import org.codehaus.groovy.ast.expr.TupleExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
 import org.codehaus.groovy.transform.trait.Traits;
 
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUnboundedWildcard;
@@ -43,142 +45,164 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUnbo
  * </ul>
  */
 public class GenericsVisitor extends ClassCodeVisitorSupport {
-    private final SourceUnit source;
 
-    public GenericsVisitor(SourceUnit source) {
-        this.source = source;
-    }
+    private final SourceUnit source;
 
+    @Override
     protected SourceUnit getSourceUnit() {
         return source;
     }
 
+    public GenericsVisitor(final SourceUnit source) {
+        this.source = source;
+    }
+
+    //--------------------------------------------------------------------------
+
     @Override
-    public void visitClass(ClassNode node) {
-        boolean error = checkWildcard(node);
-        if (error) return;
-        boolean isAnon = node instanceof InnerClassNode && ((InnerClassNode) node).isAnonymous();
-        checkGenericsUsage(node.getUnresolvedSuperClass(false), node.getSuperClass(), isAnon ? true : null);
-        ClassNode[] interfaces = node.getInterfaces();
-        for (ClassNode anInterface : interfaces) {
-            checkGenericsUsage(anInterface, anInterface.redirect());
+    public void visitClass(final ClassNode node) {
+        ClassNode sn = node.getUnresolvedSuperClass(false);
+        if (checkWildcard(sn)) return;
+
+        boolean isAIC = node instanceof InnerClassNode && ((InnerClassNode) node).isAnonymous();
+        checkGenericsUsage(sn, node.getSuperClass(), isAIC ? Boolean.TRUE : null);
+        for (ClassNode face : node.getInterfaces()) {
+            checkGenericsUsage(face);
         }
+
+        visitObjectInitializerStatements(node);
         node.visitContents(this);
     }
 
     @Override
-    public void visitField(FieldNode node) {
-        ClassNode type = node.getType();
-        checkGenericsUsage(type, type.redirect());
+    public void visitField(final FieldNode node) {
+        checkGenericsUsage(node.getType());
+
         super.visitField(node);
     }
 
     @Override
-    public void visitConstructorCallExpression(ConstructorCallExpression call) {
-        ClassNode type = call.getType();
-        boolean isAnon = type instanceof InnerClassNode && ((InnerClassNode) type).isAnonymous();
-        checkGenericsUsage(type, type.redirect(), isAnon);
+    protected void visitConstructorOrMethod(final MethodNode node, final boolean isConstructor) {
+        for (Parameter p : node.getParameters()) {
+            checkGenericsUsage(p.getType());
+        }
+        if (!isConstructor) {
+            checkGenericsUsage(node.getReturnType());
+        }
+
+        super.visitConstructorOrMethod(node, isConstructor);
     }
 
     @Override
-    public void visitMethod(MethodNode node) {
-        Parameter[] parameters = node.getParameters();
-        for (Parameter param : parameters) {
-            ClassNode paramType = param.getType();
-            checkGenericsUsage(paramType, paramType.redirect());
-        }
-        ClassNode returnType = node.getReturnType();
-        checkGenericsUsage(returnType, returnType.redirect());
-        super.visitMethod(node);
+    public void visitConstructorCallExpression(final ConstructorCallExpression expression) {
+        ClassNode type = expression.getType();
+        boolean isAIC = type instanceof InnerClassNode
+                && ((InnerClassNode) type).isAnonymous();
+        checkGenericsUsage(type, type.redirect(), isAIC);
+
+        super.visitConstructorCallExpression(expression);
     }
 
     @Override
-    public void visitDeclarationExpression(DeclarationExpression expression) {
+    public void visitDeclarationExpression(final DeclarationExpression expression) {
         if (expression.isMultipleAssignmentDeclaration()) {
-            TupleExpression tExpr = expression.getTupleExpression();
-            for (Expression nextExpr : tExpr.getExpressions()) {
-                ClassNode declType = nextExpr.getType();
-                checkGenericsUsage(declType, declType.redirect());
+            for (Expression e : expression.getTupleExpression()) {
+                checkGenericsUsage(((VariableExpression) e).getOriginType());
             }
         } else {
-            ClassNode declType = expression.getVariableExpression().getType();
-            checkGenericsUsage(declType, declType.redirect());
+            checkGenericsUsage(expression.getVariableExpression().getOriginType());
         }
+
         super.visitDeclarationExpression(expression);
     }
 
-    private boolean checkWildcard(ClassNode cn) {
-        ClassNode sn = cn.getUnresolvedSuperClass(false);
-        if (sn == null) return false;
-        GenericsType[] generics = sn.getGenericsTypes();
-        if (generics == null) return false;
-        boolean error = false;
-        for (GenericsType generic : generics) {
-            if (generic.isWildcard()) {
-                addError("A supertype may not specify a wildcard type", sn);
-                error = true;
+    @Override
+    public void visitArrayExpression(final ArrayExpression expression) {
+        checkGenericsUsage(expression.getType());
+
+        super.visitArrayExpression(expression);
+    }
+
+    @Override
+    public void visitCastExpression(final CastExpression expression) {
+        checkGenericsUsage(expression.getType());
+
+        super.visitCastExpression(expression);
+    }
+
+    //--------------------------------------------------------------------------
+
+    private boolean checkWildcard(final ClassNode sn) {
+        boolean wildcard = false;
+        if (sn.getGenericsTypes() != null) {
+            for (GenericsType gt : sn.getGenericsTypes()) {
+                if (gt.isWildcard()) {
+                    addError("A supertype may not specify a wildcard type", sn);
+                    wildcard = true;
+                }
             }
         }
-        return error;
+        return wildcard;
     }
 
-    private void checkGenericsUsage(ClassNode n, ClassNode cn) {
-        checkGenericsUsage(n, cn, null);
+    private void checkGenericsUsage(ClassNode cn) {
+        while (cn.isArray())
+            cn = cn.getComponentType();
+        checkGenericsUsage(cn, cn.redirect(), null);
     }
 
-    private void checkGenericsUsage(ClassNode n, ClassNode cn, Boolean isAnonInnerClass) {
-        if (n.isGenericsPlaceHolder()) return;
-        GenericsType[] nTypes = n.getGenericsTypes();
+    private void checkGenericsUsage(final ClassNode cn, final ClassNode rn, final Boolean isAIC) {
+        if (cn.isGenericsPlaceHolder()) return;
         GenericsType[] cnTypes = cn.getGenericsTypes();
+        GenericsType[] rnTypes = rn.getGenericsTypes();
         // raw type usage is always allowed
-        if (nTypes == null) return;
+        if (cnTypes == null) return;
         // you can't parameterize a non-generified type
-        if (cnTypes == null) {
-            String message = "The class " + getPrintName(n) + " (supplied with " + plural("type parameter", nTypes.length) +
-                    ") refers to the class " + getPrintName(cn) + " which takes no parameters";
-            if (nTypes.length == 0) {
+        if (rnTypes == null) {
+            String message = "The class " + cn.toString(false) + " (supplied with " + plural("type parameter", cnTypes.length) +
+                    ") refers to the class " + rn.toString(false) + " which takes no parameters";
+            if (cnTypes.length == 0) {
                 message += " (invalid Diamond <> usage?)";
             }
-            addError(message, n);
+            addError(message, cn);
             return;
         }
         // parameterize a type by using all of the parameters only
-        if (nTypes.length != cnTypes.length) {
-            if (Boolean.FALSE.equals(isAnonInnerClass) && nTypes.length == 0) {
+        if (cnTypes.length != rnTypes.length) {
+            if (Boolean.FALSE.equals(isAIC) && cnTypes.length == 0) {
                 return; // allow Diamond for non-AIC cases from CCE
             }
             String message;
-            if (Boolean.TRUE.equals(isAnonInnerClass) && nTypes.length == 0) {
+            if (Boolean.TRUE.equals(isAIC) && cnTypes.length == 0) {
                 message = "Cannot use diamond <> with anonymous inner classes";
             } else {
-                message = "The class " + getPrintName(n) + " (supplied with " + plural("type parameter", nTypes.length) +
-                        ") refers to the class " + getPrintName(cn) +
-                        " which takes " + plural("parameter", cnTypes.length);
-                if (nTypes.length == 0) {
+                message = "The class " + cn.toString(false) + " (supplied with " + plural("type parameter", cnTypes.length) +
+                        ") refers to the class " + rn.toString(false) + " which takes " + plural("parameter", rnTypes.length);
+                if (cnTypes.length == 0) {
                     message += " (invalid Diamond <> usage?)";
                 }
             }
-            addError(message, n);
+            addError(message, cn);
             return;
         }
-        for (int i = 0; i < nTypes.length; i++) {
-            ClassNode nType = nTypes[i].getType();
+        for (int i = 0; i < cnTypes.length; i++) {
             ClassNode cnType = cnTypes[i].getType();
+            ClassNode rnType = rnTypes[i].getType();
             // check nested type parameters
-            checkGenericsUsage(nType, nType.redirect());
+            checkGenericsUsage(cnType);
             // check bounds: unbounded wildcard (aka "?") is universal substitute
-            if (!isUnboundedWildcard(nTypes[i])) {
+            if (!isUnboundedWildcard(cnTypes[i])) {
                 // check upper bound(s)
-                ClassNode[] bounds = cnTypes[i].getUpperBounds();
+                ClassNode[] bounds = rnTypes[i].getUpperBounds();
 
                 // first can be class or interface
-                boolean valid = nType.isDerivedFrom(cnType) || ((cnType.isInterface() || Traits.isTrait(cnType)) && nType.implementsInterface(cnType));
+                boolean valid = cnType.isDerivedFrom(rnType) || ((rnType.isInterface() || Traits.isTrait(rnType)) && cnType.implementsInterface(rnType));
 
                 // subsequent bounds if present can be interfaces
                 if (valid && bounds != null && bounds.length > 1) {
                     for (int j = 1; j < bounds.length; j++) {
                         ClassNode bound = bounds[j];
-                        if (!nType.implementsInterface(bound)) {
+                        if (!cnType.implementsInterface(bound)) {
                             valid = false;
                             break;
                         }
@@ -186,46 +210,13 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
                 }
 
                 if (!valid) {
-                    addError("The type " + nTypes[i].getName() + " is not a valid substitute for the bounded parameter <" +
-                            getPrintName(cnTypes[i]) + ">", nTypes[i]);
+                    addError("The type " + cnTypes[i].getName() + " is not a valid substitute for the bounded parameter <" + rnTypes[i] + ">", cnTypes[i]);
                 }
             }
         }
     }
 
-    private String plural(String orig, int count) {
-        return "" + count + " " + (count == 1 ? orig : orig + "s");
-    }
-
-    private static String getPrintName(GenericsType gt) {
-        StringBuilder ret = new StringBuilder(gt.getName());
-        ClassNode[] upperBounds = gt.getUpperBounds();
-        ClassNode lowerBound = gt.getLowerBound();
-        if (upperBounds != null) {
-            if (upperBounds.length != 1 || !"java.lang.Object".equals(getPrintName(upperBounds[0]))) {
-                ret.append(" extends ");
-                for (int i = 0; i < upperBounds.length; i++) {
-                    ret.append(getPrintName(upperBounds[i]));
-                    if (i + 1 < upperBounds.length) ret.append(" & ");
-                }
-            }
-        } else if (lowerBound != null) {
-            ret.append(" super ").append(getPrintName(lowerBound));
-        }
-        return ret.toString();
-    }
-
-    private static String getPrintName(ClassNode cn) {
-        StringBuilder ret = new StringBuilder(cn.getName());
-        GenericsType[] gts = cn.getGenericsTypes();
-        if (gts != null) {
-            ret.append("<");
-            for (int i = 0; i < gts.length; i++) {
-                if (i != 0) ret.append(",");
-                ret.append(getPrintName(gts[i]));
-            }
-            ret.append(">");
-        }
-        return ret.toString();
+    private static String plural(final String string, final int count) {
+        return "" + count + " " + (count == 1 ? string : string + "s");
     }
 }
diff --git a/src/test/gls/generics/GenericsUsageTest.groovy b/src/test/gls/generics/GenericsUsageTest.groovy
index 214ab5d..dfef88a 100644
--- a/src/test/gls/generics/GenericsUsageTest.groovy
+++ b/src/test/gls/generics/GenericsUsageTest.groovy
@@ -251,11 +251,12 @@ final class GenericsUsageTest extends CompilableTestSupport {
     // GROOVY-3975
     void testGenericsForClosureParameters() {
         def cl = { List<String> s -> }
-        def type = cl.getClass().getMethod("call", List).genericParameterTypes[0]
-        assert type.toString().contains("java.util.List<java.lang.String>")
 
-        type = cl.getClass().getMethod("doCall", List).genericParameterTypes[0]
-        assert type.toString().contains("java.util.List<java.lang.String>")
+        String type = cl.getClass().getMethod('call', List).genericParameterTypes[0]
+        assert type.contains('java.util.List<java.lang.String>')
+
+        type = cl.getClass().getMethod('doCall', List).genericParameterTypes[0]
+        assert type.contains('java.util.List<java.lang.String>')
     }
 
     // GROOVY-4974
@@ -282,79 +283,134 @@ final class GenericsUsageTest extends CompilableTestSupport {
         '''
     }
 
-    // GROOVY-7865
-    void testFriendlyErrorMessageForGenericsArityErrors() {
+    // GROOVY-3731, GROOVY-7865, GROOVY-10033
+    void testFriendlyErrorMessageForGenericsErrors() {
+        // superclass and interfaces
+        shouldFailCompilationWithMessages '''
+            class C extends ArrayList<> { }
+        ''', ["Unexpected input: '<'"]
+        shouldFailCompilationWithMessages '''
+            class C extends ArrayList<? extends Number> { }
+        ''', ['A supertype may not specify a wildcard type']
+        shouldFailCompilationWithMessages '''
+            class C extends ArrayList<String, String> { }
+        ''', ['(supplied with 2 type parameters)', 'which takes 1 parameter']
+        shouldFailCompilationWithMessages '''
+            class C extends HashMap<String> { }
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
         shouldFailCompilationWithMessages '''
-            class MyList extends ArrayList<String, String> {}
+            class C implements Map<> { }
+        ''', ["Unexpected input: '<'"]
+        shouldFailCompilationWithMessages '''
+            class MyMap implements Map<String> { }
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
+        shouldFailCompilationWithMessages '''
+            class C implements List<String, String> { }
         ''', ['(supplied with 2 type parameters)', 'which takes 1 parameter']
 
-        if (CompilerConfiguration.DEFAULT.pluginFactory instanceof AntlrParserPluginFactory) {
-            shouldFailCompilationWithMessages '''
-                class MyList extends ArrayList<> {}
-            ''', ['(supplied with 0 type parameters)', 'which takes 1 parameter', 'invalid Diamond <> usage?']
-        } else {
-            shouldFailCompilationWithMessages '''
-                class MyList extends ArrayList<> {}
-            ''', ['Unexpected input: \'<\'']
-        }
+        // constructor call
+        assertScript '''
+            List<String> list = new LinkedList<>()
+        '''
+        shouldFailCompilationWithMessage '''
+            new LinkedList<>() { }
+        ''', 'Cannot use diamond <> with anonymous inner classes'
+        shouldFailCompilationWithMessages '''
+            new LinkedList<Integer, String>()
+        ''', ['(supplied with 2 type parameters)', 'which takes 1 parameter']
+        shouldFailCompilationWithMessages '''
+            new LinkedList<String, String>()
+        ''', ['(supplied with 2 type parameters)', 'which takes 1 parameter']
+        shouldFailCompilationWithMessages '''
+            new LinkedList<String, String>() { }
+        ''', ['(supplied with 2 type parameters)', 'which takes 1 parameter']
+        shouldFailCompilationWithMessages '''
+            new Date<Calendar>()
+        ''', ['(supplied with 1 type parameter)', 'which takes no parameters']
 
+        // constructor declaration
         shouldFailCompilationWithMessages '''
-            class MyMap extends HashMap<String> {}
+            class C { C(Map<String> m) { } }
         ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
         shouldFailCompilationWithMessages '''
-            class MyList implements List<String, String> {}
+            class C { C(Closure<String,Number> c) { } }
         ''', ['(supplied with 2 type parameters)', 'which takes 1 parameter']
 
-        if (CompilerConfiguration.DEFAULT.pluginFactory instanceof AntlrParserPluginFactory) {
-            shouldFailCompilationWithMessages '''
-                class MyList implements Map<> {}
-            ''', ['(supplied with 0 type parameters)', 'which takes 2 parameters', 'invalid Diamond <> usage?']
-        } else {
-            shouldFailCompilationWithMessages '''
-                class MyList implements Map<> {}
-            ''', ['Unexpected input: \'<\'']
-        }
+        // method declaration
+        shouldFailCompilationWithMessages '''
+            def method(Map<String> map) { }
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
+        shouldFailCompilationWithMessages '''
+            Map<String> method() { }
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
+        shouldFailCompilationWithMessages '''
+            def method(Map<String, Map<String>> map) { }
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
+        shouldFailCompilationWithMessages '''
+            def method(Map<String, Map<String>> map) { }
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
 
+        // field declaration
+        shouldFailCompilationWithMessages '''
+            class C { Map<String> map }
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
+        shouldFailCompilationWithMessages '''
+            class C { Map<String, Map<String>> map }
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
 
+        // variable declaration
         shouldFailCompilationWithMessages '''
-            class MyMap implements Map<String> {}
+            def method() { Map<String> map }
         ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
         shouldFailCompilationWithMessages '''
-            List<String> ss = new LinkedList<Integer, String>()
-        ''', ['(supplied with 2 type parameters)', 'which takes 1 parameter']
-        shouldFailCompilationWithMessage '''
-            List<String> ss = new LinkedList<>(){}
-        ''', 'Cannot use diamond <> with anonymous inner classes'
+            def method() { Map<String, Map<String>> map }
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
         shouldFailCompilationWithMessages '''
-            List<String> ss = new LinkedList<String, String>(){}
-        ''', ['(supplied with 2 type parameters)', 'which takes 1 parameter']
+            def (Map<String,String> one, Map<String> two) = [ [:], [:] ]
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
         shouldFailCompilationWithMessages '''
-            List<String> ss = new LinkedList<String, String>()
-        ''', ['supplied with 2 type parameters', 'which takes 1 parameter']
+            Map<String>[][] array = new Map[0][0]
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
         shouldFailCompilationWithMessages '''
-            def now = new Date<Calendar>()
-        ''', ['supplied with 1 type parameter', 'which takes no parameters']
+            Map<String,String>[][] array = new Map<String>[0][]
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
         shouldFailCompilationWithMessages '''
-            def method(Map<String> map) { map.toString() }
+            class C { { Map<String> m = null } }
         ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
         shouldFailCompilationWithMessages '''
-            def method(Map<String, Map<String>> map) { map.toString() }
+            class C { static { Map<String> m = null } }
         ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
         shouldFailCompilationWithMessages '''
-            class MyClass { Map<String> map }
+            @groovy.transform.ASTTest(value={
+                Map<String> m = null
+            })
+            class C { }
         ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
+
+        // casting and coercion
         shouldFailCompilationWithMessages '''
-            class MyClass { Map<String, Map<String>> map }
+            def map = (Map<String>) null
         ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
         shouldFailCompilationWithMessages '''
-             def method() { Map<String> map }
+            def map = null as Map<String>
         ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
+    }
+
+    // GROOVY-5441
+    void testCompilationErrorForMismatchedGenericsWithQualifiedTypes() {
         shouldFailCompilationWithMessages '''
-             def method() { Map<String, Map<String>> map }
+            groovy.lang.Tuple2<Object> tuple
         ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
-        assertScript '''
-            List<String> ss = new LinkedList<>()
-        '''
+        shouldFailCompilationWithMessages '''
+            java.util.List<Object,Object> list
+        ''', ['(supplied with 2 type parameters)', 'which takes 1 parameter']
+        shouldFailCompilationWithMessages '''
+            java.util.Map<Object,Object,Object> map
+        ''', ['(supplied with 3 type parameters)', 'which takes 2 parameters']
+        shouldFailCompilationWithMessages '''
+            def (java.util.Map<Object> x, java.util.List<Object,Object> y) = [null,null]
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters',
+              '(supplied with 2 type parameters)', 'which takes 1 parameter']
     }
 
     // GROOVY-8990
diff --git a/src/test/groovy/bugs/Groovy3731Bug.groovy b/src/test/groovy/bugs/Groovy3731Bug.groovy
deleted file mode 100644
index 3ba1957..0000000
--- a/src/test/groovy/bugs/Groovy3731Bug.groovy
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package groovy.bugs
-
-import gls.CompilableTestSupport
-
-class Groovy3731Bug extends CompilableTestSupport {
-    
-    void testWrongGenericsUseInFieldsAndMethods() {
-        shouldNotCompile """
-            public class G3731A {
-                Map<Object> m = [:]
-            }
-        """
-
-        shouldNotCompile """
-            public class G3731B {
-                void m1(x, Map<Object> y) {}
-            }
-        """
-
-        shouldNotCompile """
-            public class G3731C {
-                Map<Object> m1(x, y) {null}
-            }
-        """
-    }
-}
\ No newline at end of file