You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2016/06/24 21:47:26 UTC

groovy git commit: GROOVY-7865: Better error message in the presence of Generics arity errors (closes #353)

Repository: groovy
Updated Branches:
  refs/heads/master 8bcca9ef2 -> d1308e20b


GROOVY-7865: Better error message in the presence of Generics arity errors (closes #353)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/d1308e20
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/d1308e20
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/d1308e20

Branch: refs/heads/master
Commit: d1308e20b09776b18e67ce99faab4054058b6824
Parents: 8bcca9e
Author: paulk <pa...@asert.com.au>
Authored: Fri Jun 17 18:38:25 2016 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Thu Jun 23 20:38:08 2016 +1000

----------------------------------------------------------------------
 .../groovy/control/CompilationUnit.java         |  15 +-
 .../groovy/control/GenericsVisitor.java         | 138 ++++++++++++-------
 .../stc/StaticTypeCheckingVisitor.java          |  11 +-
 src/test/gls/generics/GenericsTest.groovy       | 123 +++++++++++------
 4 files changed, 184 insertions(+), 103 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/d1308e20/src/main/org/codehaus/groovy/control/CompilationUnit.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/control/CompilationUnit.java b/src/main/org/codehaus/groovy/control/CompilationUnit.java
index de53c5b..0c5726f 100644
--- a/src/main/org/codehaus/groovy/control/CompilationUnit.java
+++ b/src/main/org/codehaus/groovy/control/CompilationUnit.java
@@ -186,6 +186,16 @@ public class CompilationUnit extends ProcessingUnit {
             }
         }, Phases.SEMANTIC_ANALYSIS);
         addPhaseOperation(new PrimaryClassNodeOperation() {
+            @Override
+            public void call(SourceUnit source, GeneratorContext context,
+                             ClassNode classNode) throws CompilationFailedException {
+                if (!classNode.isSynthetic()) {
+                    GenericsVisitor genericsVisitor = new GenericsVisitor(source);
+                    genericsVisitor.visitClass(classNode);
+                }
+            }
+        }, Phases.SEMANTIC_ANALYSIS);
+        addPhaseOperation(new PrimaryClassNodeOperation() {
             public void call(SourceUnit source, GeneratorContext context,
                              ClassNode classNode) throws CompilationFailedException {
                 TraitComposer.doExtendTraits(classNode, source, CompilationUnit.this);
@@ -764,11 +774,6 @@ public class CompilationUnit extends ProcessingUnit {
 
             optimizer.visitClass(classNode, source); // GROOVY-4272: repositioned it here from staticImport
 
-            if(!classNode.isSynthetic()) {
-                GenericsVisitor genericsVisitor = new GenericsVisitor(source);
-                genericsVisitor.visitClass(classNode);
-            }
-
             //
             // Run the Verifier on the outer class
             //

http://git-wip-us.apache.org/repos/asf/groovy/blob/d1308e20/src/main/org/codehaus/groovy/control/GenericsVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/control/GenericsVisitor.java b/src/main/org/codehaus/groovy/control/GenericsVisitor.java
index 7cdf9fd..d95bf24 100644
--- a/src/main/org/codehaus/groovy/control/GenericsVisitor.java
+++ b/src/main/org/codehaus/groovy/control/GenericsVisitor.java
@@ -22,41 +22,56 @@ import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.FieldNode;
 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.ConstructorCallExpression;
 
 /**
- * class used to verify correct usage of generics in 
- * class header (class and superclass declaration) 
+ * class used to verify correct usage of generics in
+ * class header (class and superclass declaration)
+ *
  * @author Jochen Theodorou
  */
 public class GenericsVisitor extends ClassCodeVisitorSupport {
     private SourceUnit source;
-    
+
     public GenericsVisitor(SourceUnit source) {
         this.source = source;
     }
-    
+
     protected SourceUnit getSourceUnit() {
         return source;
     }
-    
+
+    @Override
     public void visitClass(ClassNode node) {
-        boolean error=checkWildcard(node);
+        boolean error = checkWildcard(node);
         if (error) return;
-        checkGenericsUsage(node.getUnresolvedSuperClass(false), node.getSuperClass());
+        boolean isAnon = node instanceof InnerClassNode && ((InnerClassNode)node).isAnonymous();
+        checkGenericsUsage(node.getUnresolvedSuperClass(false), node.getSuperClass(), isAnon ? true : null);
         ClassNode[] interfaces = node.getInterfaces();
-        for (int i = 0; i < interfaces.length; i++) {
-            checkGenericsUsage(interfaces[i], interfaces[i].redirect());
+        for (ClassNode anInterface : interfaces) {
+            checkGenericsUsage(anInterface, anInterface.redirect());
         }
         node.visitContents(this);
     }
-    
+
+    @Override
     public void visitField(FieldNode node) {
         ClassNode type = node.getType();
         checkGenericsUsage(type, type.redirect());
+        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);
+    }
+
+    @Override
     public void visitMethod(MethodNode node) {
         Parameter[] parameters = node.getParameters();
         for (Parameter param : parameters) {
@@ -65,17 +80,18 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
         }
         ClassNode returnType = node.getReturnType();
         checkGenericsUsage(returnType, returnType.redirect());
+        super.visitMethod(node);
     }
-    
+
     private boolean checkWildcard(ClassNode cn) {
         ClassNode sn = cn.getUnresolvedSuperClass(false);
-        if (sn==null) return false;
+        if (sn == null) return false;
         GenericsType[] generics = sn.getGenericsTypes();
-        if (generics==null) return false;
-        boolean error=false;
-        for (int i = 0; i < generics.length; i++) {
-            if(generics[i].isWildcard()) {
-                addError("A supertype may not specify a wildcard type",sn);
+        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;
             }
         }
@@ -83,65 +99,89 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
     }
 
     private void checkGenericsUsage(ClassNode n, ClassNode cn) {
-        if(n.isGenericsPlaceHolder()) return;
+        checkGenericsUsage(n, cn, null);
+    }
+
+    private void checkGenericsUsage(ClassNode n, ClassNode cn, Boolean isAnonInnerClass) {
+        if (n.isGenericsPlaceHolder()) return;
         GenericsType[] nTypes = n.getGenericsTypes();
         GenericsType[] cnTypes = cn.getGenericsTypes();
         // raw type usage is always allowed
-        if (nTypes==null) return;
-        // parameterize a type by using all of the parameters only
-        if (cnTypes==null) {
-            addError( "The class "+n.getName()+" refers to the class "+
-                      cn.getName()+" and uses "+nTypes.length+
-                      " parameters, but the referred class takes no parameters", n);
+        if (nTypes == 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) {
+                message += " (invalid Diamond <> usage?)";
+            }
+            addError(message, n);
             return;
         }
-        if (nTypes.length!=cnTypes.length) {
-            addError( "The class "+n.getName()+" refers to the class "+
-                      cn.getName()+" and uses "+nTypes.length+
-                      " parameters, but the referred class needs "+
-                      cnTypes.length, n);
+        // parameterize a type by using all of the parameters only
+        if (nTypes.length != cnTypes.length) {
+            if (Boolean.FALSE.equals(isAnonInnerClass) && nTypes.length == 0) {
+                return; // allow Diamond for non-AIC cases from CCE
+            }
+            String message;
+            if (Boolean.TRUE.equals(isAnonInnerClass) && nTypes.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 += " (invalid Diamond <> usage?)";
+                }
+            }
+            addError(message, n);
             return;
         }
         // check bounds
-        for (int i=0; i<nTypes.length; i++) {
+        for (int i = 0; i < nTypes.length; i++) {
             ClassNode nType = nTypes[i].getType();
             ClassNode cnType = cnTypes[i].getType();
             if (!nType.isDerivedFrom(cnType)) {
                 if (cnType.isInterface() && nType.implementsInterface(cnType)) continue;
-                addError("The type "+nTypes[i].getName()+
-                         " is not a valid substitute for the bounded parameter <"+
-                         getPrintName(cnTypes[i])+">",n);
+                addError("The type " + nTypes[i].getName() +
+                        " is not a valid substitute for the bounded parameter <" +
+                        getPrintName(cnTypes[i]) + ">", n);
             }
         }
     }
-    
+
+    private String plural(String orig, int count) {
+        return "" + count + " " + (count == 1 ? orig : orig + "s");
+    }
+
     private static String getPrintName(GenericsType gt) {
         String ret = gt.getName();
         ClassNode[] upperBounds = gt.getUpperBounds();
         ClassNode lowerBound = gt.getLowerBound();
-        if (upperBounds!=null) {
-            ret += " extends ";
-            for (int i = 0; i < upperBounds.length; i++) {
-                ret += getPrintName(upperBounds[i]);
-                if (i+1<upperBounds.length) ret += " & ";
+        if (upperBounds != null) {
+            if (upperBounds.length != 1 || !"java.lang.Object".equals(getPrintName(upperBounds[0]))) {
+                ret += " extends ";
+                for (int i = 0; i < upperBounds.length; i++) {
+                    ret += getPrintName(upperBounds[i]);
+                    if (i + 1 < upperBounds.length) ret += " & ";
+                }
             }
-        } else if (lowerBound!=null) {
-            ret += " super "+getPrintName(lowerBound);
+        } else if (lowerBound != null) {
+            ret += " super " + getPrintName(lowerBound);
         }
         return ret;
-
     }
-    
+
     private static String getPrintName(ClassNode cn) {
         String ret = cn.getName();
         GenericsType[] gts = cn.getGenericsTypes();
-        if (gts!=null) {
+        if (gts != null) {
             ret += "<";
             for (int i = 0; i < gts.length; i++) {
-                if (i!=0) ret+=",";
-                ret+=getPrintName(gts[i]);
-            } 
-            ret+=">";
+                if (i != 0) ret += ",";
+                ret += getPrintName(gts[i]);
+            }
+            ret += ">";
         }
         return ret;
     }

http://git-wip-us.apache.org/repos/asf/groovy/blob/d1308e20/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
index e3cc081..bf53f52 100644
--- a/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -771,16 +771,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
     protected void inferDiamondType(final ConstructorCallExpression cce, final ClassNode lType) {
         // check if constructor call expression makes use of the diamond operator
         ClassNode node = cce.getType();
-        if (node.isUsingGenerics() && node instanceof InnerClassNode && ((InnerClassNode) node).isAnonymous()) {
-            ClassNode[] interfaces = node.getInterfaces();
-            node = interfaces != null && interfaces.length == 1 ? interfaces[0] : node.getUnresolvedSuperClass(false);
-            if ((node.getGenericsTypes() == null || node.getGenericsTypes().length == 0) && lType.isUsingGenerics()) {
-                // InterfaceA<Foo> obj = new InterfaceA<>() { ... }
-                // InterfaceA<Foo> obj = new ClassA<>() { ... }
-                // ClassA<Foo> obj = new ClassA<>() { ... }
-                addStaticTypeError("Cannot use diamond <> with anonymous inner classes", cce);
-            }
-        } else if (node.isUsingGenerics() && node.getGenericsTypes() != null && node.getGenericsTypes().length == 0) {
+        if (node.isUsingGenerics() && node.getGenericsTypes() != null && node.getGenericsTypes().length == 0) {
             ArgumentListExpression argumentListExpression = InvocationWriter.makeArgumentList(cce.getArguments());
             if (argumentListExpression.getExpressions().isEmpty()) {
                 GenericsType[] genericsTypes = lType.getGenericsTypes();

http://git-wip-us.apache.org/repos/asf/groovy/blob/d1308e20/src/test/gls/generics/GenericsTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/gls/generics/GenericsTest.groovy b/src/test/gls/generics/GenericsTest.groovy
index b2779c9..02a76ce 100644
--- a/src/test/gls/generics/GenericsTest.groovy
+++ b/src/test/gls/generics/GenericsTest.groovy
@@ -22,7 +22,7 @@ import org.codehaus.groovy.control.MultipleCompilationErrorsException
 
 class GenericsTest extends GenericsTestBase {
 
-    public void testClassWithoutParameterExtendsClassWithFixedParameter() {
+    void testClassWithoutParameterExtendsClassWithFixedParameter() {
         createClassInfo """
             class B extends ArrayList<Long> {}
         """
@@ -31,56 +31,56 @@ class GenericsTest extends GenericsTestBase {
         ]
     }
 
-    public void testMultipleImplementsWithParameter() {
+    void testMultipleImplementsWithParameter() {
         createClassInfo """
             abstract class B<T> implements Runnable,List<T> {}
         """
         assert signatures == ["class": "<T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/lang/Runnable;Ljava/util/List<TT;>;Lgroovy/lang/GroovyObject;"]
     }
 
-    public void testImplementsWithParameter() {
+    void testImplementsWithParameter() {
         createClassInfo """
             abstract class B<T> implements List<T> {}
         """
         assert signatures == ["class": "<T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/List<TT;>;Lgroovy/lang/GroovyObject;"]
     }
 
-    public void testExtendsWithParameter() {
+    void testExtendsWithParameter() {
         createClassInfo """
             class B<T> extends ArrayList<T> {}
         """
         assert signatures == ["class": "<T:Ljava/lang/Object;>Ljava/util/ArrayList<TT;>;Lgroovy/lang/GroovyObject;"]
     }
 
-    public void testNestedExtendsWithParameter() {
+    void testNestedExtendsWithParameter() {
         createClassInfo """
             class B<T> extends HashMap<T,List<T>> {}
         """
         assert signatures == ["class": "<T:Ljava/lang/Object;>Ljava/util/HashMap<TT;Ljava/util/List<TT;>;>;Lgroovy/lang/GroovyObject;"]
     }
 
-    public void testBoundInterface() {
+    void testBoundInterface() {
         createClassInfo """
             class B<T extends List> {}
         """
         assert signatures == ["class": "<T::Ljava/util/List;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;"]
     }
 
-    public void testNestedReuseOfParameter() {
+    void testNestedReuseOfParameter() {
         createClassInfo """
             class B<Y,T extends Map<String,Map<Y,Integer>>> {}
         """
         assert signatures == ["class": "<Y:Ljava/lang/Object;T::Ljava/util/Map<Ljava/lang/String;Ljava/util/Map<TY;Ljava/lang/Integer;>;>;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;"]
     }
 
-    public void testFieldWithParameter() {
+    void testFieldWithParameter() {
         createClassInfo """
             class B { public Collection<Integer> books }
         """
         assert signatures == [books: "Ljava/util/Collection<Ljava/lang/Integer;>;"]
     }
 
-    public void testFieldReusedParameter() {
+    void testFieldReusedParameter() {
         createClassInfo """
             class B<T> { public Collection<T> collection }
         """
@@ -88,7 +88,7 @@ class GenericsTest extends GenericsTestBase {
                 collection: "Ljava/util/Collection<TT;>;"]
     }
 
-    public void testParameterAsReturnType() {
+    void testParameterAsReturnType() {
         createClassInfo """
             class B {
                 static <T> T foo() {return null}
@@ -97,7 +97,7 @@ class GenericsTest extends GenericsTestBase {
         assert signatures == ["foo()Ljava/lang/Object;": "<T:Ljava/lang/Object;>()TT;"]
     }
 
-    public void testParameterAsReturnTypeAndParameter() {
+    void testParameterAsReturnTypeAndParameter() {
         createClassInfo """
             class B {
                 static <T> T foo(T t) {return null}
@@ -106,7 +106,7 @@ class GenericsTest extends GenericsTestBase {
         assert signatures == ["foo(Ljava/lang/Object;)Ljava/lang/Object;": "<T:Ljava/lang/Object;>(TT;)TT;"]
     }
 
-    public void testParameterAsMethodParameter() {
+    void testParameterAsMethodParameter() {
         createClassInfo """
             class B<T> {
                 void foo(T t){}
@@ -116,7 +116,7 @@ class GenericsTest extends GenericsTestBase {
                 "foo(Ljava/lang/Object;)V": "(TT;)V"]
     }
 
-    public void testParameterAsNestedMethodParameter() {
+    void testParameterAsNestedMethodParameter() {
         createClassInfo """
             class B<T> {
                 void foo(List<T> t){}
@@ -126,7 +126,7 @@ class GenericsTest extends GenericsTestBase {
                 "foo(Ljava/util/List;)V": "(Ljava/util/List<TT;>;)V"]
     }
 
-    public void testParameterAsNestedMethodParameterReturningInterface() {
+    void testParameterAsNestedMethodParameterReturningInterface() {
         createClassInfo """
             class B<T> {
                 Cloneable foo(List<T> t){}
@@ -136,7 +136,7 @@ class GenericsTest extends GenericsTestBase {
                 "foo(Ljava/util/List;)Ljava/lang/Cloneable;": "(Ljava/util/List<TT;>;)Ljava/lang/Cloneable;"]
     }
 
-    public void testArray() {
+    void testArray() {
         createClassInfo """
             class B<T> {
                 T[] get(T[] arr) {return null}
@@ -146,7 +146,7 @@ class GenericsTest extends GenericsTestBase {
                 "get([Ljava/lang/Object;)[Ljava/lang/Object;": "([TT;)[TT;"]
     }
 
-    public void testMultipleBounds() {
+    void testMultipleBounds() {
         createClassInfo """
             class Pair<    A extends Comparable<A> & Cloneable , 
                         B extends Cloneable & Comparable<B> > 
@@ -161,7 +161,7 @@ class GenericsTest extends GenericsTestBase {
                         "bar()Ljava/lang/Cloneable;": "()TB;"]
     }
 
-    public void testWildCard() {
+    void testWildCard() {
         createClassInfo """
             class B {
                 private Collection<?> f1 
@@ -192,7 +192,7 @@ class GenericsTest extends GenericsTestBase {
         ]
     }
 
-    public void testParameterAsParameterForReturnTypeAndFieldClass() {
+    void testParameterAsParameterForReturnTypeAndFieldClass() {
         createClassInfo """
                public class B<T> {
                    private T owner;
@@ -207,7 +207,7 @@ class GenericsTest extends GenericsTestBase {
         ]
     }
 
-    public void testInterfaceWithParameter() {
+    void testInterfaceWithParameter() {
         createClassInfo """
             interface B<T> {}
         """
@@ -215,7 +215,7 @@ class GenericsTest extends GenericsTestBase {
     }
 
 
-    public void testTypeParamAsBound() {
+    void testTypeParamAsBound() {
         createClassInfo """
     class Box<A> {
       public <V extends A> void foo(V v) {
@@ -226,7 +226,7 @@ class GenericsTest extends GenericsTestBase {
         assert signatures == ["foo(Ljava/lang/Object;)V": "<V:TA;>(TV;)V", "class": "<A:Ljava/lang/Object;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;"]
     }
 
-    public void testInvalidParameterUsage() {
+    void testInvalidParameterUsage() {
         shouldNotCompile """
             abstract class B<T> implements Map<T>{}
         """
@@ -323,9 +323,9 @@ class GenericsTest extends GenericsTestBase {
     }
 
     void testGenericsDiamondShortcutIllegalPosition() {
-        assertScriptAndVerifyCompilationError """
+        shouldFailCompilationWithMessage '''
             List<> list4 = []
-        """, 'unexpected token: <'
+        ''', 'unexpected token: <'
     }
 
     void testGenericsInAsType() {
@@ -356,55 +356,61 @@ import java.util.concurrent.atomic.AtomicInteger
     }
 
     void testCompilationWithMissingClosingBracketsInGenerics() {
-        assertScriptAndVerifyCompilationError """
+        shouldFailCompilationWithExpectedMessage """
             def list1 = new ArrayList<Integer()
         """
 
-        assertScriptAndVerifyCompilationError """
+        shouldFailCompilationWithExpectedMessage """
             List<Integer list2 = new ArrayList<Integer>()
         """
 
-        assertScriptAndVerifyCompilationError """
+        shouldFailCompilationWithExpectedMessage """
             def c = []
             for (Iterator<String i = c.iterator(); i.hasNext(); ) { }
         """
 
-        assertScriptAndVerifyCompilationError """
+        shouldFailCompilationWithExpectedMessage """
             def m(Class<Integer someParam) {}
         """
 
-        assertScriptAndVerifyCompilationError """
+        shouldFailCompilationWithExpectedMessage """
             abstract class ArrayList1<E extends AbstractList<E> implements List<E> {}
         """
 
-        assertScriptAndVerifyCompilationError """
+        shouldFailCompilationWithExpectedMessage """
             abstract class ArrayList2<E> extends AbstractList<E implements List<E> {}
         """
 
-        assertScriptAndVerifyCompilationError """
+        shouldFailCompilationWithExpectedMessage """
             abstract class ArrayList3<E> extends AbstractList<E> implements List<E {}
         """
 
-        assertScriptAndVerifyCompilationError """
+        shouldFailCompilationWithExpectedMessage """
             def List<List<Integer> history = new ArrayList<List<Integer>>()
         """
 
-        assertScriptAndVerifyCompilationError """
+        shouldFailCompilationWithExpectedMessage """
             def List<List<Integer>> history = new ArrayList<List<Integer>()
         """
     }
 
-    private void assertScriptAndVerifyCompilationError(scriptText) {
-        assertScriptAndVerifyCompilationError(scriptText, "Missing closing bracket '>' for generics types")
+    private void shouldFailCompilationWithExpectedMessage(scriptText) {
+        shouldFailCompilationWithMessage scriptText, "Missing closing bracket '>' for generics types"
     }
 
-    private void assertScriptAndVerifyCompilationError(scriptText, errorMessage) {
+    private void shouldFailCompilationWithMessage(scriptText, String errorMessage) {
+        shouldFailCompilationWithMessages(scriptText, [errorMessage])
+    }
+
+    private void shouldFailCompilationWithMessages(scriptText, List<String> errorMessages) {
         try {
             assertScript scriptText
-            fail("The script compilation should have failed as it contains mis-matching generic brackets")
+            fail("The script compilation should have failed as it contains generics errors, e.g. mis-matching generic brackets")
         } catch (MultipleCompilationErrorsException mcee) {
-            def text = mcee.toString();
-            assert text.contains(errorMessage)
+            def text = mcee.toString()
+            errorMessages.each {
+                assert text.contains(it)
+            }
         }
     }
 
@@ -440,11 +446,50 @@ import java.util.concurrent.atomic.AtomicInteger
         '''
     }
 
-    public void "test method with generic return type defined at class level"() {
+    void "test method with generic return type defined at class level"() {
         // class Bar should compile successfully
 
         // the classes it references should be available as class files to check for ASM resolving
         //  so they're defined in compiled GenericsTestData and not loaded from text in the test
         createClassInfo 'class Bar extends gls.generics.GenericsTestData.Abstract<String> {}'
     }
+
+    void testFriendlyErrorMessageForGenericsArityErrorsGroovy7865() {
+        shouldFailCompilationWithMessages '''
+            class MyList extends ArrayList<String, String> {}
+        ''', ['(supplied with 2 type parameters)', 'which takes 1 parameter']
+        shouldFailCompilationWithMessages '''
+            class MyList extends ArrayList<> {}
+        ''', ['(supplied with 0 type parameters)', 'which takes 1 parameter', 'invalid Diamond <> usage?']
+        shouldFailCompilationWithMessages '''
+            class MyMap extends HashMap<String> {}
+        ''', ['(supplied with 1 type parameter)', 'which takes 2 parameters']
+        shouldFailCompilationWithMessages '''
+            class MyList implements List<String, String> {}
+        ''', ['(supplied with 2 type parameters)', 'which takes 1 parameter']
+        shouldFailCompilationWithMessages '''
+            class MyList implements Map<> {}
+        ''', ['(supplied with 0 type parameters)', 'which takes 2 parameters', 'invalid Diamond <> usage?']
+        shouldFailCompilationWithMessages '''
+            class MyMap implements Map<String> {}
+        ''', ['(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'
+        shouldFailCompilationWithMessages '''
+            List<String> ss = new LinkedList<String, String>(){}
+        ''', ['(supplied with 2 type parameters)', 'which takes 1 parameter']
+        shouldFailCompilationWithMessages '''
+            List<String> ss = new LinkedList<String, String>()
+        ''', ['supplied with 2 type parameters', 'which takes 1 parameter']
+        shouldFailCompilationWithMessages '''
+            def now = new Date<Calendar>()
+        ''', ['supplied with 1 type parameter', 'which takes no parameters']
+        assertScript '''
+            List<String> ss = new LinkedList<>()
+        '''
+    }
 }