You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2026/08/30 13:12:01 UTC

(groovy) 02/03: GROOVY-12319: Java compatibility: remaining generic type syntax

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

asf-gitbox-commits pushed a commit to branch generics-tck
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 034fbc3cc667db3406e7c1c5f63f2a7c582226f4
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sat Aug 29 14:14:13 2026 +0000

    GROOVY-12319: Java compatibility: remaining generic type syntax
---
 src/antlr/GroovyParser.g4                          |   17 +-
 .../groovy/grape/GrabAnnotationTransformation.java |    2 +-
 .../apache/groovy/parser/antlr4/AstBuilder.java    |  116 +-
 .../java/org/codehaus/groovy/ast/ClassNode.java    |   63 +-
 .../java/org/codehaus/groovy/ast/GenericsType.java |    2 +-
 .../java/org/codehaus/groovy/ast/MethodNode.java   |   13 +
 .../groovy/ast/expr/ConstructorCallExpression.java |   66 +-
 .../codehaus/groovy/ast/tools/GenericsUtils.java   |  147 +-
 .../org/codehaus/groovy/classgen/Verifier.java     |   29 +-
 .../groovy/classgen/asm/BytecodeHelper.java        |   57 +-
 .../codehaus/groovy/control/GenericsVisitor.java   |  277 +++-
 .../codehaus/groovy/control/ResolveVisitor.java    |   13 +
 .../groovy/control/StaticImportVisitor.java        |    4 +-
 .../transform/stc/StaticTypeCheckingSupport.java   |   27 +-
 .../transform/stc/StaticTypeCheckingVisitor.java   |  216 ++-
 .../groovy/transform/stc/TypeCheckingContext.java  |    7 +-
 src/spec/doc/core-operators.adoc                   |    7 +
 src/spec/test/OperatorsTest.groovy                 |   15 +
 src/test-resources/core/BreakingChange_01x.groovy  |    4 +-
 src/test-resources/core/BreakingChange_04x.groovy  |    4 +-
 src/test-resources/core/Expression_19.groovy       |    8 +-
 src/test/groovy/bugs/Groovy12319.groovy            | 1227 ++++++++++++++++
 src/test/groovy/bugs/Groovy5318.groovy             |    5 +-
 .../generics/GenericsJavaCompatibilityTest.groovy  | 1495 +++++++++++++++++++-
 .../groovy/gls/generics/GenericsUsageTest.groovy   |    5 +-
 .../groovy/gls/innerClass/InnerClassTest.groovy    |    4 +-
 .../stc/ArraysAndCollectionsSTCTest.groovy         |    8 +-
 .../stc/DefaultGroovyMethodsSTCTest.groovy         |    2 +-
 .../groovy/transform/stc/GenericsSTCTest.groovy    |    7 +-
 .../org/codehaus/groovy/ast/ClassNodeTest.java     |   15 +
 .../org/codehaus/groovy/ast/MethodNodeTest.groovy  |   10 +
 .../groovy/ast/tools/GenericsUtilsTest.groovy      |  144 ++
 .../groovy/classgen/BytecodeHelperTest.groovy      |   50 +
 .../console/ui/AstNodeToScriptAdapterTest.groovy   |   10 +-
 34 files changed, 3823 insertions(+), 253 deletions(-)

diff --git a/src/antlr/GroovyParser.g4 b/src/antlr/GroovyParser.g4
index 2e132403c1..af30fc1631 100644
--- a/src/antlr/GroovyParser.g4
+++ b/src/antlr/GroovyParser.g4
@@ -349,8 +349,9 @@ primitiveType
     :   BuiltInPrimitiveType
     ;
 
+// GROOVY-12319: JLS 4.5 "rare" types (Outer<T>.Inner after the outer type arguments)
 referenceType
-    :   qualifiedClassName typeArguments?
+    :   qualifiedClassName (typeArguments (DOT identifier typeArguments?)*)?
     ;
 
 matchingType // see: instanceof / !instanceof type patterns (JEP 394)
@@ -376,7 +377,7 @@ options { baseContext = type; }
 
 standardClassOrInterfaceType
 options { baseContext = referenceType; }
-    :   qualifiedStandardClassName typeArguments?
+    :   qualifiedStandardClassName (typeArguments (DOT className typeArguments?)*)?
     ;
 
 typeArguments
@@ -992,7 +993,7 @@ pathElement returns [int t]
     :   nls
         (
             DOT nls
-            (   NEW creator[1]
+            (   NEW nonWildcardTypeArguments? creator[1]  // GROOVY-12319: outer.new <T>Inner(...)
                 { $t = 6; }
             |
                 // AT: foo.@bar selects the field (or attribute), not property
@@ -1088,9 +1089,10 @@ primary
         identifier typeArguments?                                                           #identifierPrmrAlt
     |   literal                                                                             #literalPrmrAlt
     |   gstring                                                                             #gstringPrmrAlt
-    |   NEW nls creator[0]                                                                  #newPrmrAlt
-    |   THIS                                                                                #thisPrmrAlt
-    |   SUPER                                                                               #superPrmrAlt
+    // GROOVY-12319: JLS 15.9 / 8.8.7.1 constructor type arguments (new <T>C(), <T>this(), <T>super())
+    |   NEW nls nonWildcardTypeArguments? creator[0]                                        #newPrmrAlt
+    |   nonWildcardTypeArguments? THIS                                                      #thisPrmrAlt
+    |   nonWildcardTypeArguments? SUPER                                                     #superPrmrAlt
     |   parExpression                                                                       #parenPrmrAlt
     |   closureOrLambdaExpression                                                           #closureOrLambdaExpressionPrmrAlt
     |   list                                                                                #listPrmrAlt
@@ -1215,7 +1217,8 @@ anonymousInnerClassDeclaration[int t]
 createdName
     :   annotationsOpt
         (   primitiveType
-        |   qualifiedClassName typeArgumentsOrDiamond?
+        |   // GROOVY-12319: JLS 15.9 ClassOrInterfaceTypeToInstantiate (new Outer<T>.Inner<U>(...))
+            qualifiedClassName (typeArgumentsOrDiamond (DOT identifier typeArgumentsOrDiamond?)*)?
         )
     ;
 
diff --git a/src/main/java/groovy/grape/GrabAnnotationTransformation.java b/src/main/java/groovy/grape/GrabAnnotationTransformation.java
index e3398ecc6f..b628721b5a 100644
--- a/src/main/java/groovy/grape/GrabAnnotationTransformation.java
+++ b/src/main/java/groovy/grape/GrabAnnotationTransformation.java
@@ -473,7 +473,7 @@ public class GrabAnnotationTransformation extends ClassCodeVisitorSupport implem
         // call stack — the stack-walk depth is tuned for the compile-time path, not the
         // generated static-initializer path, and would overshoot into java.lang.reflect frames.
         basicArgs.addMapEntryExpression(constX("classLoader"),
-                callX(new ClassExpression(classNode), "getClassLoader"));
+                callX(new ClassExpression(classNode.getPlainNodeReference()), "getClassLoader"));
         if (autoDownload != null)  {
             basicArgs.addMapEntryExpression(constX(AUTO_DOWNLOAD_SETTING), constX(autoDownload));
         }
diff --git a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
index 70fef04b72..97fcb45fe3 100644
--- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
+++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -2878,6 +2878,9 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         } else if (asBoolean(ctx.creator())) {
             CreatorContext creatorContext = ctx.creator();
             creatorContext.putNodeMetaData(ENCLOSING_INSTANCE_EXPRESSION, baseExpr);
+            if (asBoolean(ctx.nonWildcardTypeArguments())) {
+                creatorContext.putNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES, this.visitNonWildcardTypeArguments(ctx.nonWildcardTypeArguments()));
+            }
             return configureAST(this.visitCreator(creatorContext), ctx);
         } else if (asBoolean(ctx.indexPropertyArgs())) { // e.g. list[1, 3, 5]
             Tuple2<Token, Expression> tuple = this.visitIndexPropertyArgs(ctx.indexPropertyArgs());
@@ -2969,14 +2972,14 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
                                 ctx);
                     }
 
-                    return configureAST(
-                            new ConstructorCallExpression(
-                                    SUPER_STR.equals(baseExprText)
-                                            ? ClassNode.SUPER
-                                            : ClassNode.THIS,
-                                    argumentsExpr
-                            ),
-                            ctx);
+                    ConstructorCallExpression constructorCallExpression = new ConstructorCallExpression(
+                            SUPER_STR.equals(baseExprText)
+                                    ? ClassNode.SUPER
+                                    : ClassNode.THIS,
+                            argumentsExpr
+                    );
+                    constructorCallExpression.setGenericsTypes(baseExpr.getNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES));
+                    return configureAST(constructorCallExpression, ctx);
                 }
 
                 MethodCallExpression methodCallExpression = this.createMethodCallExpression(baseExpr, argumentsExpr);
@@ -3861,17 +3864,28 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
 
     @Override
     public Expression visitNewPrmrAlt(final NewPrmrAltContext ctx) {
+        if (asBoolean(ctx.nonWildcardTypeArguments())) {
+            ctx.creator().putNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES, this.visitNonWildcardTypeArguments(ctx.nonWildcardTypeArguments()));
+        }
         return configureAST(this.visitCreator(ctx.creator()), ctx);
     }
 
     @Override
     public VariableExpression visitThisPrmrAlt(final ThisPrmrAltContext ctx) {
-        return configureAST(new VariableExpression(ctx.THIS().getText()), ctx);
+        VariableExpression varExpr = configureAST(new VariableExpression(ctx.THIS().getText()), ctx);
+        if (asBoolean(ctx.nonWildcardTypeArguments())) {
+            varExpr.putNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES, this.visitNonWildcardTypeArguments(ctx.nonWildcardTypeArguments()));
+        }
+        return varExpr;
     }
 
     @Override
     public VariableExpression visitSuperPrmrAlt(final SuperPrmrAltContext ctx) {
-        return configureAST(new VariableExpression(ctx.SUPER().getText()), ctx);
+        VariableExpression varExpr = configureAST(new VariableExpression(ctx.SUPER().getText()), ctx);
+        if (asBoolean(ctx.nonWildcardTypeArguments())) {
+            varExpr.putNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES, this.visitNonWildcardTypeArguments(ctx.nonWildcardTypeArguments()));
+        }
+        return varExpr;
     }
 
     // } primary ---------------------------------------------------------------
@@ -3883,6 +3897,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         if (asBoolean(ctx.arguments())) { // create instance of class
             Expression arguments = this.visitArguments(ctx.arguments());
             Expression enclosingInstanceExpression = ctx.getNodeMetaData(ENCLOSING_INSTANCE_EXPRESSION);
+            GenericsType[] constructorGenerics = ctx.getNodeMetaData(PATH_EXPRESSION_BASE_EXPR_GENERICS_TYPES);
 
             if (enclosingInstanceExpression != null) {
                 if (arguments instanceof ArgumentListExpression) {
@@ -3910,10 +3925,16 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
 
                 ConstructorCallExpression constructorCallExpression = new ConstructorCallExpression(anonymousInnerClassNode, arguments);
                 constructorCallExpression.setUsingAnonymousInnerClass(true);
+                if (constructorGenerics != null) {
+                    constructorCallExpression.setGenericsTypes(constructorGenerics);
+                }
                 return configureAST(constructorCallExpression, ctx);
             }
 
             ConstructorCallExpression constructorCallExpression = new ConstructorCallExpression(classNode, arguments);
+            if (constructorGenerics != null) {
+                constructorCallExpression.setGenericsTypes(constructorGenerics);
+            }
             return configureAST(constructorCallExpression, ctx);
         }
 
@@ -3963,16 +3984,14 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
 
     @Override
     public ClassNode visitCreatedName(final CreatedNameContext ctx) {
-        ClassNode classNode = null;
-        if (asBoolean(ctx.qualifiedClassName())) {
-            classNode = this.visitQualifiedClassName(ctx.qualifiedClassName());
-            if (asBoolean(ctx.typeArgumentsOrDiamond())) {
-                classNode.setGenericsTypes(
-                        this.visitTypeArgumentsOrDiamond(ctx.typeArgumentsOrDiamond()));
+        ClassNode classNode;
+        if (asBoolean(ctx.primitiveType())) {
+            classNode = configureAST(this.visitPrimitiveType(ctx.primitiveType()), ctx);
+        } else {
+            classNode = this.visitParameterizedClassType(ctx);
+            if (classNode != null) {
                 configureAST(classNode, ctx);
             }
-        } else if (asBoolean(ctx.primitiveType())) {
-            classNode = configureAST(this.visitPrimitiveType(ctx.primitiveType()), ctx);
         }
         if (classNode == null) {
             throw createParsingFailedException("Unsupported created name: " + ctx.getText(), ctx);
@@ -4647,29 +4666,52 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
 
     @Override
     public ClassNode visitReferenceType(final ReferenceTypeContext ctx) {
-        ClassNode classNode;
-        if (asBoolean(ctx.qualifiedClassName())) {
-            if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) {
-                ctx.qualifiedClassName().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, Boolean.TRUE);
-            }
-            classNode = this.visitQualifiedClassName(ctx.qualifiedClassName());
-        } else {
-            if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) {
-                ctx.qualifiedStandardClassName().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, Boolean.TRUE);
-            }
-            classNode = this.visitQualifiedStandardClassName(ctx.qualifiedStandardClassName());
-        }
+        return configureAST(this.visitParameterizedClassType(ctx), ctx);
+    }
 
-        if (asBoolean(ctx.typeArguments())) {
-            GenericsType[] generics = this.visitTypeArguments(ctx.typeArguments());
-            classNode.setGenericsTypes(generics);
-            if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR) && Arrays.stream(generics).anyMatch(gt ->
-                    !gt.isWildcard() || asBoolean(gt.getLowerBound()) || ArrayGroovyMethods.asBoolean(gt.getUpperBounds()))) { // GROOVY-11585
-                throw this.createParsingFailedException("Cannot perform instanceof check against parameterized type " + classNode, ctx);
+    /**
+     * Builds a class type that may include JLS 4.5 rare types ({@code Outer<T>.Inner<U>}).
+     * Shared by {@code referenceType} (type arguments only) and {@code createdName}
+     * (type arguments or diamond).
+     */
+    private ClassNode visitParameterizedClassType(final GroovyParserRuleContext ctx) {
+        ClassNode classNode = null;
+        for (int i = 0, n = ctx.getChildCount(); i < n; i += 1) {
+            ParseTree child = ctx.getChild(i);
+            if (child instanceof QualifiedClassNameContext qcn) {
+                if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) {
+                    qcn.putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, Boolean.TRUE);
+                }
+                classNode = this.visitQualifiedClassName(qcn);
+            } else if (child instanceof QualifiedStandardClassNameContext qscn) {
+                if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) {
+                    qscn.putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, Boolean.TRUE);
+                }
+                classNode = this.visitQualifiedStandardClassName(qscn);
+            } else if (child instanceof TypeArgumentsContext tac) {
+                GenericsType[] generics = this.visitTypeArguments(tac);
+                classNode.setGenericsTypes(generics);
+                rejectParameterizedInstanceof(ctx, classNode, generics);
+            } else if (child instanceof TypeArgumentsOrDiamondContext tad) {
+                classNode.setGenericsTypes(this.visitTypeArgumentsOrDiamond(tad));
+            } else if (child instanceof IdentifierContext || child instanceof ClassNameContext) {
+                ClassNode outer = classNode;
+                ClassNode inner = ClassHelper.makeWithoutCaching(outer.getName() + '.' + child.getText());
+                if (!isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) {
+                    inner = this.proxyClassNode(inner);
+                }
+                inner.setOuterClassType(outer); // GROOVY-10646, GROOVY-12319
+                classNode = configureAST(inner, (GroovyParserRuleContext) child);
             }
         }
+        return classNode;
+    }
 
-        return configureAST(classNode, ctx);
+    private void rejectParameterizedInstanceof(final GroovyParserRuleContext ctx, final ClassNode classNode, final GenericsType[] generics) {
+        if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR) && Arrays.stream(generics).anyMatch(gt ->
+                !gt.isWildcard() || asBoolean(gt.getLowerBound()) || ArrayGroovyMethods.asBoolean(gt.getUpperBounds()))) { // GROOVY-11585
+            throw this.createParsingFailedException("Cannot perform instanceof check against parameterized type " + classNode, ctx);
+        }
     }
 
     @Override
diff --git a/src/main/java/org/codehaus/groovy/ast/ClassNode.java b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
index ec0a56f7a1..7c7544190c 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
@@ -257,17 +257,15 @@ public class ClassNode extends AnnotatedNode {
     }
 
     /**
-     * Constructs a non-primary array {@code ClassNode} when no real array class is available.
-     * This internal constructor is used by {@code makeArray()} to create a synthetic array ClassNode.
-     * The resulting node represents an array type with appropriate interfaces and modifiers.
-     *
-     * @param componentType the {@link ClassNode} representing the element type of the array
+     * Synthetic array {@code ClassNode} used by {@link #makeArray()} when no real
+     * array class is available. {@code componentType} is the element type.
      */
-    private ClassNode(final ClassNode componentType) {
-        this(componentType.getName() + "[]", ACC_ABSTRACT | ACC_FINAL | ACC_PUBLIC, ClassHelper.OBJECT_TYPE,
+    private static ClassNode syntheticArray(final ClassNode componentType) {
+        ClassNode node = new ClassNode(componentType.getName() + "[]", ACC_ABSTRACT | ACC_FINAL | ACC_PUBLIC, ClassHelper.OBJECT_TYPE,
           new ClassNode[]{ClassHelper.CLONEABLE_TYPE, ClassHelper.SERIALIZABLE_TYPE}, MixinNode.EMPTY_ARRAY);
-        this.componentType = componentType.redirect();
-        this.isPrimaryNode = false;
+        node.componentType = componentType.redirect();
+        node.isPrimaryNode = false;
+        return node;
     }
 
     //--------------------------------------------------------------------------
@@ -339,6 +337,10 @@ public class ClassNode extends AnnotatedNode {
         if (isArray()) {
             n.componentType = redirect().getComponentType();
         }
+        ClassNode outerType = getOuterClassType();
+        if (outerType != null) {
+            n.setOuterClassType(outerType);
+        }
         return n;
     }
 
@@ -503,6 +505,7 @@ public class ClassNode extends AnnotatedNode {
      * @return the modifier flags as an integer bitmask
      * @see #setModifiers(int)
      * @see #isAbstract()
+     * @see #isStatic()
      * @see #isInterface()
      * @see #isEnum()
      */
@@ -1861,6 +1864,22 @@ faces:  if (method == null && asBoolean(getInterfaces())) { // GROOVY-11323
         return (getModifiers() & ACC_ABSTRACT) != 0;
     }
 
+    /**
+     * Returns whether this {@link ClassNode} represents a static class.
+     * A class is static if the {@code ACC_STATIC} modifier flag is set in its modifiers.
+     * Static nested classes have no enclosing instance; they can be used without an instance of the enclosing class.
+     * This method checks the actual modifier bits, independent of the source code syntax.
+     *
+     * @return {@code true} if this class has the static modifier set; {@code false} otherwise
+     * @see #getModifiers()
+     * @see #setModifiers(int)
+     *
+     * @since 6.0.0
+     */
+    public boolean isStatic() {
+        return (getModifiers() & ACC_STATIC) != 0;
+    }
+
     /**
      * Returns whether this {@link ClassNode} represents an interface type.
      * A class is an interface if the {@code ACC_INTERFACE} modifier flag is set in its modifiers.
@@ -1980,7 +1999,7 @@ faces:  if (method == null && asBoolean(getInterfaces())) { // GROOVY-11323
             // don't use the ClassHelper here!
             node = new ClassNode(type, this);
         } else {
-            node = new ClassNode(this);
+            node = syntheticArray(this);
         }
         return node;
     }
@@ -2034,6 +2053,28 @@ faces:  if (method == null && asBoolean(getInterfaces())) { // GROOVY-11323
         return result;
     }
 
+    /**
+     * For a JLS 4.5 rare type {@code Outer<T>.Inner}, the parameterized enclosing
+     * type. Distinct from {@link #getOuterClass()}, which is the enclosing class
+     * of a nested class <em>declaration</em>.
+     *
+     * @return the parameterized enclosing type, or {@code null} if this is not a rare type
+     * @since 6.0.0
+     */
+    public ClassNode getOuterClassType() {
+        return getNodeMetaData("outer.class");
+    }
+
+    /**
+     * Records the parameterized enclosing type of a JLS 4.5 rare type.
+     *
+     * @param outer the parameterized {@code Outer<T>} node; {@code null} clears it
+     * @since 6.0.0
+     */
+    public void setOuterClassType(final ClassNode outer) {
+        putNodeMetaData("outer.class", outer);
+    }
+
     /**
      * @return the field on the outer class or {@code null} if this is not an inner class
      */
@@ -2247,6 +2288,7 @@ faces:  if (method == null && asBoolean(getInterfaces())) { // GROOVY-11323
      *
      * @return {@code true} if this class is declared in a static method; {@code false} otherwise
      * @see #setStaticClass(boolean)
+     * @see #isStatic()
      * @see #isScriptBody()
      */
     public boolean isStaticClass() {
@@ -2261,6 +2303,7 @@ faces:  if (method == null && asBoolean(getInterfaces())) { // GROOVY-11323
      *
      * @param staticClass {@code true} to mark as defined in a static context; {@code false} otherwise
      * @see #isStaticClass()
+     * @see #isStatic()
      * @see #setScriptBody(boolean)
      */
     public void setStaticClass(boolean staticClass) {
diff --git a/src/main/java/org/codehaus/groovy/ast/GenericsType.java b/src/main/java/org/codehaus/groovy/ast/GenericsType.java
index 3f3b019a75..ef573f5dc8 100644
--- a/src/main/java/org/codehaus/groovy/ast/GenericsType.java
+++ b/src/main/java/org/codehaus/groovy/ast/GenericsType.java
@@ -160,7 +160,7 @@ public class GenericsType extends ASTNode {
             if (Modifier.isStatic(theType.getModifiers()) || theType.isInterface()) {
                 sb.append(parentClassNodeName);
             } else {
-                ClassNode outerClass = theType.getNodeMetaData("outer.class");
+                ClassNode outerClass = theType.getOuterClassType();
                 if (outerClass == null) outerClass = theType.getOuterClass();
                 sb.append(genericsBounds(outerClass, new HashSet<>()));
             }
diff --git a/src/main/java/org/codehaus/groovy/ast/MethodNode.java b/src/main/java/org/codehaus/groovy/ast/MethodNode.java
index 51c6e068de..1a932116e8 100644
--- a/src/main/java/org/codehaus/groovy/ast/MethodNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/MethodNode.java
@@ -28,6 +28,7 @@ import static org.apache.groovy.ast.tools.ClassNodeUtils.formatTypeName;
 import static org.apache.groovy.ast.tools.MethodNodeUtils.methodDescriptor;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.toGenericTypesString;
 import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
+import static org.objectweb.asm.Opcodes.ACC_BRIDGE;
 import static org.objectweb.asm.Opcodes.ACC_FINAL;
 import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
 import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
@@ -342,6 +343,18 @@ public class MethodNode extends AnnotatedNode {
         return (modifiers & (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED)) == 0;
     }
 
+    /**
+     * Checks whether this method is a compiler-generated bridge.
+     * Bridge methods are adapters inserted for covariant overrides and
+     * generic type erasure.
+     *
+     * @return true if this method has the bridge modifier
+     * @since 6.0.0
+     */
+    public boolean isBridge() {
+        return (modifiers & ACC_BRIDGE) != 0;
+    }
+
     /**
      * Returns the list of checked exceptions declared in the method's throws clause.
      *
diff --git a/src/main/java/org/codehaus/groovy/ast/expr/ConstructorCallExpression.java b/src/main/java/org/codehaus/groovy/ast/expr/ConstructorCallExpression.java
index 86bcbcb5af..f1b10ee43a 100644
--- a/src/main/java/org/codehaus/groovy/ast/expr/ConstructorCallExpression.java
+++ b/src/main/java/org/codehaus/groovy/ast/expr/ConstructorCallExpression.java
@@ -20,6 +20,7 @@ package org.codehaus.groovy.ast.expr;
 
 import org.codehaus.groovy.ast.ASTNode;
 import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.GenericsType;
 import org.codehaus.groovy.ast.GroovyCodeVisitor;
 
 /**
@@ -28,6 +29,8 @@ import org.codehaus.groovy.ast.GroovyCodeVisitor;
  * as well as special constructor calls like {@code this()} and {@code super()} calls within constructors.
  * The constructor is identified by the type being constructed, and the arguments are wrapped in a {@link TupleExpression}.
  * May optionally use an anonymous inner class.
+ * Explicit constructor type arguments ({@code new <T>Box(t)}, {@code <T>this(t)}, {@code <T>super(t)})
+ * are stored separately from the constructed type's own type arguments.
  * 
  * @see {@link MethodCall} for the method interface this class implements
  * @see {@link TupleExpression} for argument representation
@@ -44,6 +47,10 @@ public class ConstructorCallExpression extends Expression implements MethodCall
      * Whether this constructor call uses an anonymous inner class declaration.
      */
     private boolean usesAnonymousInnerClass;
+    /**
+     * Explicit constructor type arguments, e.g. {@code new <String>Box("x")} or {@code <T>super(t)}.
+     */
+    private GenericsType[] genericsTypes;
 
     /**
      * Creates a constructor call expression.
@@ -71,6 +78,7 @@ public class ConstructorCallExpression extends Expression implements MethodCall
     public Expression transformExpression(ExpressionTransformer transformer) {
         ConstructorCallExpression answer = new ConstructorCallExpression(getType(), transformer.transform(arguments));
         answer.setUsingAnonymousInnerClass(isUsingAnonymousInnerClass());
+        answer.setGenericsTypes(genericsTypes);
         answer.setSourcePosition(this);
         answer.copyNodeMetaData(this);
         return answer;
@@ -98,15 +106,63 @@ public class ConstructorCallExpression extends Expression implements MethodCall
 
     @Override
     public String getText() {
-        String text;
+        StringBuilder builder = new StringBuilder(64);
         if (isSuperCall()) {
-            text = "super ";
+            appendTypeArguments(builder);
+            builder.append("super ");
         } else if (isThisCall()) {
-            text = "this ";
+            appendTypeArguments(builder);
+            builder.append("this ");
         } else {
-            text = "new " + getType().toString(false);
+            builder.append("new ");
+            appendTypeArguments(builder);
+            builder.append(getType().toString(false));
+        }
+        return builder.append(getArguments().getText()).toString();
+    }
+
+    private void appendTypeArguments(final StringBuilder builder) {
+        if (!isUsingGenerics()) return;
+        builder.append('<');
+        boolean first = true;
+        for (GenericsType t : genericsTypes) {
+            if (!first) builder.append(", ");
+            else first = false;
+            builder.append(t);
         }
-        return text + getArguments().getText();
+        builder.append('>');
+    }
+
+    /**
+     * Returns the explicit constructor type arguments, if specified.
+     * Used for generic constructor invocations such as {@code new <String>Box("x")},
+     * {@code <T>this(t)} and {@code <T>super(t)}.
+     *
+     * @return an array of {@link GenericsType} parameters, or {@code null} if none are specified
+     * @since 6.0.0
+     */
+    public GenericsType[] getGenericsTypes() {
+        return genericsTypes;
+    }
+
+    /**
+     * Sets the explicit constructor type arguments.
+     *
+     * @param genericsTypes an array of {@link GenericsType} parameters; may be {@code null}
+     * @since 6.0.0
+     */
+    public void setGenericsTypes(final GenericsType[] genericsTypes) {
+        this.genericsTypes = genericsTypes;
+    }
+
+    /**
+     * Indicates whether explicit constructor type arguments are specified.
+     *
+     * @return {@code true} if type arguments are present; {@code false} otherwise
+     * @since 6.0.0
+     */
+    public boolean isUsingGenerics() {
+        return genericsTypes != null && genericsTypes.length > 0;
     }
 
     /**
diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
index c4909f9608..4385234072 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
@@ -20,6 +20,7 @@ package org.codehaus.groovy.ast.tools;
 
 import groovy.lang.Tuple2;
 import groovy.transform.stc.IncorrectTypeHintException;
+import groovy.transform.Internal;
 import org.codehaus.groovy.GroovyBugError;
 import org.codehaus.groovy.ast.ASTNode;
 import org.codehaus.groovy.ast.ClassHelper;
@@ -573,7 +574,7 @@ public class GenericsUtils {
         // class C<T extends Number> extends A<T,Object,String> { }
         // the type "A<T,Object,String> -> A<X,Y,Z>" will produce [X:Number,Y:Object,Z:String]
 
-        ClassNode oc = type.getNodeMetaData("outer.class"); // GROOVY-10646: outer class type parameters
+        ClassNode oc = type.getOuterClassType(); // GROOVY-10646: outer class type parameters
         Map<String, ClassNode> newSpec = oc != null ? createGenericsSpec(oc, oldSpec) : new HashMap<>();
         GenericsType[] gt = type.getGenericsTypes(), rgt = type.redirect().getGenericsTypes();
         if (gt != null && rgt != null) {
@@ -584,6 +585,150 @@ public class GenericsUtils {
         return newSpec;
     }
 
+    /**
+     * Writes {@code genericsSpec} onto {@code type} as an explicit type-argument
+     * list, filling any missing parameter from its bound (or {@code Object}).
+     * Mutates {@code type} in place; callers must pass a per-use copy such as
+     * an anonymous class's diamond super type, not a shared redirect.
+     *
+     * @since 6.0.0
+     */
+    @Internal
+    public static void applyGenericsSpec(final ClassNode type, final Map<String, ClassNode> genericsSpec) {
+        GenericsType[] rgt = type.redirect().getGenericsTypes();
+        if (rgt == null || rgt.length == 0) return;
+        GenericsType[] newGt = new GenericsType[rgt.length];
+        for (int i = 0; i < rgt.length; i += 1) {
+            ClassNode resolved = genericsSpec.get(rgt[i].getName());
+            if (resolved == null) {
+                resolved = rgt[i].getUpperBounds() != null && rgt[i].getUpperBounds().length > 0
+                        ? rgt[i].getUpperBounds()[0] : ClassHelper.OBJECT_TYPE;
+            }
+            newGt[i] = new GenericsType(resolved);
+        }
+        type.setGenericsTypes(newGt);
+    }
+
+    /**
+     * Recovers a {@link #createGenericsSpec(ClassNode, Map) generics specification}
+     * for {@code superType} from {@code impl}'s instance methods. Used for diamond
+     * anonymous classes (GROOVY-12319 / JEP 213): type arguments of {@code Super<>}
+     * are not written in source, so they are read back from overrides such as
+     * {@code String process(String)} for {@code T process(T)}.
+     * <p>
+     * The result uses the same {@code Map<String, ClassNode>} protocol as
+     * {@link #createGenericsSpec(ClassNode, Map)} and {@link #applyGenericsSpec(ClassNode, Map)}.
+     * Assignment / target typing remains the primary JLS 15.9.3 witness; this is
+     * the fallback used when the diamond is still empty.
+     *
+     * @since 6.0.0
+     */
+    @Internal
+    public static Map<String, ClassNode> inferGenericsSpecFromOverrides(final ClassNode impl, final ClassNode superType, final Map<String, ClassNode> oldSpec) {
+        Map<String, ClassNode> spec = new HashMap<>(oldSpec);
+        GenericsType[] rgt = superType.redirect().getGenericsTypes();
+        if (rgt == null || rgt.length == 0) return spec;
+
+        Set<String> typeParameterNames = new HashSet<>();
+        for (GenericsType gt : rgt) {
+            typeParameterNames.add(gt.getName());
+        }
+
+        Map<String, List<MethodNode>> declaredByName = new HashMap<>();
+        for (MethodNode declaredMethod : impl.getMethods()) {
+            if (!isPossibleOverride(declaredMethod)) continue;
+            declaredByName.computeIfAbsent(declaredMethod.getName(), k -> new ArrayList<>()).add(declaredMethod);
+        }
+        for (MethodNode superMethod : superType.getMethods()) {
+            if (!isPossibleOverride(superMethod)) continue;
+            List<MethodNode> candidates = declaredByName.get(superMethod.getName());
+            if (candidates == null) continue;
+            for (MethodNode declaredMethod : candidates) {
+                if (declaredMethod.getParameters().length != superMethod.getParameters().length) continue;
+                Map<String, ClassNode> extracted = new HashMap<>();
+                Parameter[] sp = superMethod.getParameters();
+                Parameter[] dp = declaredMethod.getParameters();
+                boolean plausibleOverride = true;
+                for (int i = 0; i < sp.length; i += 1) {
+                    if (!extractOverrideConnection(dp[i].getType(), sp[i].getType(), extracted)) {
+                        plausibleOverride = false;
+                        break;
+                    }
+                }
+                if (!plausibleOverride) continue;
+                extractOverrideConnection(declaredMethod.getReturnType(), superMethod.getReturnType(), extracted);
+                for (Map.Entry<String, ClassNode> entry : extracted.entrySet()) {
+                    if (typeParameterNames.contains(entry.getKey())) {
+                        spec.putIfAbsent(entry.getKey(), entry.getValue());
+                    }
+                }
+            }
+        }
+        return spec;
+    }
+
+    /**
+     * Connects an override's actual type to a super-type parameter that may
+     * contain placeholders. Primitive types are boxed so they can appear as
+     * type arguments; {@link ClassHelper#getWrapper(ClassNode)} is not used
+     * because it redirects and would drop type arguments such as
+     * {@code List<String>}.
+     * <p>
+     * Only declared types that still mention a placeholder are considered, and
+     * only when {@code actual} is related to the erasure of {@code declared},
+     * so coincidental name-and-arity matches are skipped without catching
+     * {@link GroovyBugError}.
+     *
+     * @return {@code false} when the pair cannot be an override because the
+     *         erasures are unrelated; {@code true} otherwise
+     */
+    private static boolean extractOverrideConnection(final ClassNode actual, final ClassNode declared, final Map<String, ClassNode> extracted) {
+        if (actual == null || declared == null) return true;
+        if (!declared.isGenericsPlaceHolder() && !hasUnresolvedGenerics(declared)) return true;
+        ClassNode boxed = ClassHelper.isPrimitiveType(actual) ? ClassHelper.getWrapper(actual) : actual;
+        ClassNode erasure = declared.redirect();
+        if (!declared.isGenericsPlaceHolder()
+                && !boxed.equals(erasure)
+                && !implementsInterfaceOrIsSubclassOf(boxed, erasure)
+                && !implementsInterfaceOrIsSubclassOf(erasure, boxed)) {
+            return false;
+        }
+        extractSuperClassGenerics(boxed, declared, extracted);
+        return true;
+    }
+
+    private static boolean isPossibleOverride(final MethodNode method) {
+        return !(method.isStatic() || method.isPrivate() || method.isBridge());
+    }
+
+    /**
+     * Diamond {@code <>} of an anonymous class is stored on its super type,
+     * or on an interface once {@code InnerClassVisitor} has moved an interface
+     * super type. Returns that node, or {@code null} if none still carries a
+     * diamond.
+     *
+     * @since 6.0.0
+     */
+    @Internal
+    public static ClassNode diamondTargetOfAnonymousClass(final ClassNode anonType) {
+        if (anonType == null) return null;
+        ClassNode sc = anonType.getUnresolvedSuperClass(false);
+        if (isEmptyDiamond(sc)) return sc;
+        ClassNode[] ifaces = anonType.getInterfaces();
+        if (ifaces != null) {
+            for (ClassNode iface : ifaces) {
+                if (isEmptyDiamond(iface)) return iface;
+            }
+        }
+        return null;
+    }
+
+    private static boolean isEmptyDiamond(final ClassNode type) {
+        if (type == null) return false;
+        GenericsType[] gt = type.getGenericsTypes();
+        return gt != null && gt.length == 0;
+    }
+
     /**
      * @since 2.4.1
      */
diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
index 0d73045355..d948be8d36 100644
--- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
@@ -122,8 +122,10 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.addMethodGenerics;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.applyGenericsSpec;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpec;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.createGenericsSpec;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.inferGenericsSpecFromOverrides;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.parameterizeType;
 import static org.codehaus.groovy.ast.tools.PropertyNodeUtils.adjustPropertyModifiersForMethod;
 import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.STATIC_COMPILE_NODE;
@@ -1783,7 +1785,7 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
     private void addCovariantMethods(final ClassNode classNode, final List<MethodNode> declaredMethods, final Map<String, MethodNode> interfaceMethods, final Map<String, MethodNode> methodsToAdd, final Map<String, ClassNode> oldGenericsSpec) {
         ClassNode sc = classNode.getUnresolvedSuperClass(false);
         if (sc != null) {
-            Map<String, ClassNode> genericsSpec = createGenericsSpec(sc, oldGenericsSpec);
+            Map<String, ClassNode> genericsSpec = genericsSpecForSuperType(classNode, sc, oldGenericsSpec);
             List<MethodNode> scMethods = sc.getMethods();
             // add bridge methods in original class for overridden methods with changed signature
             storeMissingCovariantMethods(declaredMethods, methodsToAdd, genericsSpec, scMethods);
@@ -1807,14 +1809,35 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
 
         for (ClassNode anInterface : classNode.getInterfaces()) {
             List<MethodNode> ifaceMethods = anInterface.getMethods();
-            Map<String, ClassNode> genericsSpec = createGenericsSpec(anInterface, oldGenericsSpec);
+            Map<String, ClassNode> genericsSpec = genericsSpecForSuperType(classNode, anInterface, oldGenericsSpec);
             storeMissingCovariantMethods(declaredMethods, methodsToAdd, genericsSpec, ifaceMethods);
             addCovariantMethods(anInterface, declaredMethods, interfaceMethods, methodsToAdd, genericsSpec);
         }
     }
 
+    /**
+     * Diamond AIC types may still carry {@code Super<>} at classgen when no
+     * assignment target filled them. Recover the spec from overrides (GROOVY-12319)
+     * and write it onto {@code superType} so covariant bridges see the same
+     * parameterization as the anonymous body.
+     */
+    private static Map<String, ClassNode> genericsSpecForSuperType(final ClassNode classNode, final ClassNode superType, final Map<String, ClassNode> oldGenericsSpec) {
+        Map<String, ClassNode> genericsSpec = createGenericsSpec(superType, oldGenericsSpec);
+        GenericsType[] usage = superType.getGenericsTypes();
+        // Only an actual diamond (<>) is inferred here. A raw AIC of a generic
+        // super type (gt == null) must not pay override reconstruction.
+        if (classNode instanceof InnerClassNode && ((InnerClassNode) classNode).isAnonymous()
+                && usage != null && usage.length == 0) {
+            genericsSpec = inferGenericsSpecFromOverrides(classNode, superType, oldGenericsSpec);
+            if (!genericsSpec.isEmpty()) {
+                applyGenericsSpec(superType, genericsSpec);
+            }
+        }
+        return genericsSpec;
+    }
+
     private static boolean isPossibleOverride(final MethodNode methodNode) {
-        return (methodNode.getModifiers() & (ACC_BRIDGE | ACC_PRIVATE | ACC_STATIC)) == 0;
+        return !(methodNode.isBridge() || methodNode.isPrivate() || methodNode.isStatic());
     }
 
     private void storeMissingCovariantMethods(final List<MethodNode> declaredMethods, final Map<String, MethodNode> methodsToAdd, final Map<String, ClassNode> genericsSpec, final List<MethodNode> superClassMethods) {
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java b/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java
index 1187065a13..0b12674e16 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java
@@ -337,7 +337,10 @@ public class BytecodeHelper {
     }
 
     private static boolean hasGenerics(ClassNode type) {
-        return type.isArray() ? hasGenerics(type.getComponentType()) : type.getGenericsTypes() != null;
+        if (type.isArray()) return hasGenerics(type.getComponentType());
+        if (type.getGenericsTypes() != null && type.getGenericsTypes().length > 0) return true;
+        ClassNode outer = type.getOuterClassType();
+        return outer != null && hasGenerics(outer);
     }
 
     /**
@@ -393,18 +396,13 @@ public class BytecodeHelper {
     public static String getTypeGenericsSignature(ClassNode node) {
         if (!usesGenericsInTypeSignature(node)) return null;
         StringBuilder ret = new StringBuilder(100);
-        ret.append(getTypeDescription(node.getPlainNodeReference(), false));
-        addSubTypes(ret, node.getGenericsTypes(), "<", ">");
+        writeParameterizedClass(ret, node);
         ret.append(";");
-
         return ret.toString();
     }
 
     private static boolean usesGenericsInTypeSignature(ClassNode node) {
-        if (!node.isUsingGenerics()) return false;
-        if (hasGenerics(node)) return true;
-
-        return false;
+        return hasGenerics(node);
     }
 
     /**
@@ -429,7 +427,7 @@ public class BytecodeHelper {
     }
 
     private static void getGenericsTypeSpec(StringBuilder ret, GenericsType[] genericsTypes) {
-        if (genericsTypes == null) return;
+        if (genericsTypes == null || genericsTypes.length == 0) return;
         ret.append('<');
         for (GenericsType genericsType : genericsTypes) {
             String name = genericsType.getName();
@@ -447,8 +445,11 @@ public class BytecodeHelper {
      * @return the bounds signature, or {@code null} if none is needed
      */
     public static String getGenericsBounds(ClassNode type) {
-        GenericsType[] genericsTypes = type.getGenericsTypes();
-        if (genericsTypes == null) return null;
+        if (type.isArray()) {
+            String component = getGenericsBounds(type.getComponentType());
+            return component == null ? null : "[" + component;
+        }
+        if (!hasGenerics(type)) return null;
         StringBuilder ret = new StringBuilder(100);
         if (type.isGenericsPlaceHolder()) {
             addSubTypes(ret, type.getGenericsTypes(), "", "");
@@ -466,12 +467,41 @@ public class BytecodeHelper {
             ret.append(printType.getGenericsTypes()[0].getName());
             ret.append(";");
         } else {
-            ret.append(getTypeDescription(printType, false));
-            addSubTypes(ret, printType.getGenericsTypes(), "<", ">");
+            writeParameterizedClass(ret, printType);
             if (!isPrimitiveType(printType)) ret.append(";");
         }
     }
 
+    /**
+     * Writes a class type and its type arguments, using the JLS 4.5 nested form
+     * {@code LOuter&lt;...&gt;.Inner&lt;...&gt;} when an enclosing rare type is present.
+     */
+    private static void writeParameterizedClass(StringBuilder ret, ClassNode printType) {
+        ClassNode owner = printType.getOuterClassType();
+        if (owner != null) {
+            writeParameterizedClass(ret, owner);
+            ret.append('.');
+            ret.append(innerClassSimpleName(printType, owner));
+            addSubTypes(ret, printType.getGenericsTypes(), "<", ">");
+            return;
+        }
+        ret.append(getTypeDescription(printType, false));
+        addSubTypes(ret, printType.getGenericsTypes(), "<", ">");
+    }
+
+    private static String innerClassSimpleName(final ClassNode inner, final ClassNode owner) {
+        String innerName = inner.getName();
+        String ownerName = owner.getName();
+        if (innerName.startsWith(ownerName) && innerName.length() > ownerName.length()) {
+            char sep = innerName.charAt(ownerName.length());
+            if (sep == '.' || sep == '$') {
+                return innerName.substring(ownerName.length() + 1).replace('$', '.');
+            }
+        }
+        int dot = Math.max(innerName.lastIndexOf('.'), innerName.lastIndexOf('$'));
+        return dot < 0 ? innerName : innerName.substring(dot + 1);
+    }
+
     private static void writeGenericsBounds(StringBuilder ret, GenericsType type, boolean writeInterfaceMarker) {
         if (type.getUpperBounds() != null) {
             ClassNode[] bounds = type.getUpperBounds();
@@ -487,6 +517,7 @@ public class BytecodeHelper {
 
     private static void addSubTypes(StringBuilder ret, GenericsType[] types, String start, String end) {
         if (types == null) return;
+        if (types.length == 0 && "<".equals(start)) return;
         ret.append(start);
         for (GenericsType type : types) {
             if (type.getType().isArray()) {
diff --git a/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java b/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java
index 2c133d69ce..991cfdc548 100644
--- a/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java
+++ b/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java
@@ -19,6 +19,7 @@
 package org.codehaus.groovy.control;
 
 import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.FieldNode;
 import org.codehaus.groovy.ast.GenericsType;
@@ -26,13 +27,21 @@ 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.BinaryExpression;
 import org.codehaus.groovy.ast.expr.CastExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
 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.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
 import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.syntax.Types;
 import org.codehaus.groovy.transform.trait.Traits;
 
+import static org.codehaus.groovy.ast.ClassHelper.isObjectType;
+import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.diamondTargetOfAnonymousClass;
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUnboundedWildcard;
 
 /**
@@ -42,6 +51,7 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUnbo
  * <li>class header (class and superclass declaration)</li>
  * <li>arity of type parameters for fields, parameters, local variables</li>
  * <li>invalid diamond {@code <>} usage</li>
+ * <li>JLS well-formedness of type arguments, bounds, array creation, and type parameters</li>
  * </ul>
  */
 public class GenericsVisitor extends ClassCodeVisitorSupport {
@@ -76,13 +86,20 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
      */
     @Override
     public void visitClass(final ClassNode node) {
+        checkTypeParameterBounds(node.getGenericsTypes());
+        if (node.getGenericsTypes() != null && node.getGenericsTypes().length > 0
+                && node.isDerivedFrom(ClassHelper.THROWABLE_TYPE)) {
+            addError("A generic class may not extend java.lang.Throwable", node);
+        }
+
         ClassNode sc = node.getUnresolvedSuperClass(false);
         if (checkWildcard(sc)) return;
 
         boolean isAIC = node instanceof InnerClassNode && ((InnerClassNode) node).isAnonymous();
         checkGenericsUsage(sc, sc.redirect(), isAIC ? Boolean.TRUE : null);
         for (ClassNode face : node.getInterfaces()) {
-            checkGenericsUsage(face);
+            checkWildcard(face); // JLS 8.1.5: superinterfaces may not specify a wildcard
+            checkGenericsUsage(face, face.redirect(), isAIC ? Boolean.TRUE : null);
         }
 
         visitObjectInitializerStatements(node);
@@ -109,6 +126,7 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
      */
     @Override
     protected void visitConstructorOrMethod(final MethodNode node, final boolean isConstructor) {
+        checkTypeParameterBounds(node.getGenericsTypes());
         for (Parameter p : node.getParameters()) {
             checkGenericsUsage(p.getType());
         }
@@ -127,9 +145,21 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
     @Override
     public void visitConstructorCallExpression(final ConstructorCallExpression expression) {
         ClassNode type = expression.getType();
+        if (type.isGenericsPlaceHolder()) {
+            addError("Cannot instantiate the type parameter " + type.getUnresolvedName(), expression);
+        }
+        if (hasWildcardTypeArgument(type)) {
+            addError("A constructor call may not specify a wildcard type", expression);
+        }
         boolean isAIC = type instanceof InnerClassNode
                 && ((InnerClassNode) type).isAnonymous();
         checkGenericsUsage(type, type.redirect(), isAIC);
+        if (expression.isUsingGenerics()) {
+            ClassNode created = isAIC ? diamondTargetOfAnonymousClass(type) : type;
+            if (created != null && created.getGenericsTypes() != null && created.getGenericsTypes().length == 0) {
+                addError("Cannot use diamond <> together with constructor type arguments", expression);
+            }
+        }
 
         super.visitConstructorCallExpression(expression);
     }
@@ -159,11 +189,61 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
      */
     @Override
     public void visitArrayExpression(final ArrayExpression expression) {
+        ClassNode elementType = expression.getElementType();
+        if (!isReifiable(elementType)) {
+            addError("generic array creation", expression);
+        }
         checkGenericsUsage(expression.getType());
 
         super.visitArrayExpression(expression);
     }
 
+    /**
+     * JLS 15.8.2: a class literal may not name a type variable.
+     */
+    @Override
+    public void visitClassExpression(final ClassExpression expression) {
+        ClassNode type = expression.getType();
+        if (type.isGenericsPlaceHolder()) {
+            addError("Cannot select from a type parameter " + type.getUnresolvedName(), expression);
+        }
+        super.visitClassExpression(expression);
+    }
+
+    /**
+     * Groovy represents the type operand of {@code instanceof} as a
+     * {@link ClassExpression}, but it is not a class literal. Skip it so
+     * {@link InstanceOfVerifier} can diagnose JLS 15.20.2.
+     */
+    @Override
+    public void visitBinaryExpression(final BinaryExpression expression) {
+        if (expression.getOperation().isA(Types.INSTANCEOF_OPERATOR)
+                && expression.getRightExpression() instanceof ClassExpression) {
+            expression.getLeftExpression().visit(this);
+            return;
+        }
+        super.visitBinaryExpression(expression);
+    }
+
+    /**
+     * JLS 4.5.2: a static member of a generic type must be referred to by the
+     * generic type name, not a parameterization such as {@code Cell<String>.id()}.
+     */
+    @Override
+    public void visitMethodCallExpression(final MethodCallExpression call) {
+        checkStaticMemberViaParameterizedType(call.getObjectExpression(), call);
+        super.visitMethodCallExpression(call);
+    }
+
+    /**
+     * JLS 4.5.2 also applies to static fields accessed as {@code Cell<String>.ID}.
+     */
+    @Override
+    public void visitPropertyExpression(final PropertyExpression expression) {
+        checkStaticMemberViaParameterizedType(expression.getObjectExpression(), expression);
+        super.visitPropertyExpression(expression);
+    }
+
     /**
      * Validates generic usage for cast target types.
      *
@@ -178,6 +258,12 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
 
     //--------------------------------------------------------------------------
 
+    private void checkStaticMemberViaParameterizedType(final Expression object, final Expression location) {
+        if (object instanceof ClassExpression && isParameterizedTypeUsage(object.getType())) {
+            addError("Cannot refer to a static member of a generic type through a parameterization", location);
+        }
+    }
+
     private boolean checkWildcard(final ClassNode sn) {
         boolean wildcard = false;
         if (sn.getGenericsTypes() != null) {
@@ -199,6 +285,19 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
 
     private void checkGenericsUsage(final ClassNode cn, final ClassNode rn, final Boolean isAIC) {
         if (cn.isGenericsPlaceHolder()) return;
+        ClassNode oc = cn.getOuterClassType(); // GROOVY-12319: Outer<T>.Inner
+        if (oc != null) {
+            checkGenericsUsage(oc);
+            // JLS 4.5 / 6.5.5: a parameterized enclosing type may only select a
+            // non-static member type, and a generic member must be parameterized.
+            if (isParameterizedEnclosingType(oc)) {
+                if (isStaticMemberType(cn)) {
+                    addError("Cannot refer to a static member of a generic type through a parameterization", cn);
+                } else if (cn.getGenericsTypes() == null && rn.getGenericsTypes() != null) {
+                    addError("A raw member type may not be used with a parameterized enclosing type", cn);
+                }
+            }
+        }
         GenericsType[] cnTypes = cn.getGenericsTypes();
         // raw type usage is always allowed
         if (cnTypes == null) return;
@@ -215,53 +314,167 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
         }
         // parameterize a type by using all the parameters only
         if (cnTypes.length != rnTypes.length) {
-            if (Boolean.FALSE.equals(isAIC) && cnTypes.length == 0) {
-                return; // allow Diamond for non-AIC cases from CCE
+            if (isAIC != null && cnTypes.length == 0) {
+                return; // diamond on constructor calls, including anonymous classes (GROOVY-12319 / JEP 213)
             }
-            String message;
-            if (Boolean.TRUE.equals(isAIC) && cnTypes.length == 0) {
-                message = "Cannot use diamond <> with anonymous inner classes";
-            } else {
-                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?)";
-                }
+            String 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, cn);
             return;
         }
         for (int i = 0; i < cnTypes.length; i++) {
             ClassNode cnType = cnTypes[i].getType();
-            ClassNode rnType = rnTypes[i].getType();
+            if (isPrimitiveType(cnType)) {
+                addError("The type argument " + cnType.getName() + " is of primitive type; a reference type is required", cnTypes[i]);
+                continue;
+            }
             // check nested type parameters
             checkGenericsUsage(cnType);
-            // check bounds: unbounded wildcard (aka "?") is universal substitute
-            if (!isUnboundedWildcard(cnTypes[i])) {
-                // check upper bound(s)
-                ClassNode[] bounds = rnTypes[i].getUpperBounds();
-
-                // first can be class or interface
-                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 (!cnType.implementsInterface(bound)) {
-                            valid = false;
-                            break;
-                        }
-                    }
+            if (!isTypeArgumentWithinBounds(cnTypes[i], rnTypes[i])) {
+                addError("The type " + cnTypes[i].getName() + " is not a valid substitute for the bounded parameter <" + rnTypes[i] + ">", cnTypes[i]);
+            }
+        }
+    }
+
+    /**
+     * JLS 4.5: a type argument is within bounds of the corresponding type
+     * parameter. Unbounded {@code ?} is always within bounds. For
+     * {@code ? extends U} against {@code T extends B}, javac accepts the
+     * argument when {@code glb(U, B)} is well-formed (not an empty
+     * intersection of two classes). For {@code ? super L}, {@code L} must
+     * be a subtype of {@code B}.
+     */
+    private boolean isTypeArgumentWithinBounds(final GenericsType arg, final GenericsType param) {
+        if (isUnboundedWildcard(arg)) return true;
+        ClassNode[] paramBounds = param.getUpperBounds();
+        ClassNode firstBound = (paramBounds != null && paramBounds.length > 0)
+                ? paramBounds[0] : param.getType().redirect();
+
+        if (arg.isWildcard()) {
+            ClassNode lower = arg.getLowerBound();
+            if (lower != null) {
+                return isSubtypeOfBound(lower, firstBound);
+            }
+            ClassNode[] uppers = arg.getUpperBounds();
+            if (uppers != null && uppers.length > 0) {
+                return !areDisjointClassTypes(uppers[0], firstBound);
+            }
+            return true;
+        }
+
+        ClassNode cnType = arg.getType();
+        boolean valid = isSubtypeOfBound(cnType, firstBound);
+        if (valid && paramBounds != null && paramBounds.length > 1) {
+            for (int j = 1; j < paramBounds.length; j++) {
+                ClassNode bound = paramBounds[j];
+                if (!cnType.implementsInterface(bound)) {
+                    return false;
                 }
+            }
+        }
+        return valid;
+    }
+
+    private static boolean isSubtypeOfBound(final ClassNode type, final ClassNode bound) {
+        if (bound == null || isObjectType(bound)) return true;
+        return type.isDerivedFrom(bound)
+                || ((bound.isInterface() || Traits.isTrait(bound)) && type.implementsInterface(bound));
+    }
 
-                if (!valid) {
-                    addError("The type " + cnTypes[i].getName() + " is not a valid substitute for the bounded parameter <" + rnTypes[i] + ">", cnTypes[i]);
+    /**
+     * Two classes (not interfaces) whose glb would be empty: neither is a
+     * subtype of the other. {@code Object} is a universal upper bound.
+     */
+    private static boolean areDisjointClassTypes(final ClassNode a, final ClassNode b) {
+        if (a == null || b == null || isObjectType(a) || isObjectType(b)) return false;
+        if (a.isInterface() || b.isInterface() || Traits.isTrait(a) || Traits.isTrait(b)) {
+            return false;
+        }
+        return !a.isDerivedFrom(b) && !b.isDerivedFrom(a);
+    }
+
+    /**
+     * JLS 4.4: a class type or type variable may appear only as the first type
+     * of a bound; additional bounds must be interfaces.
+     */
+    private void checkTypeParameterBounds(final GenericsType[] typeParameters) {
+        if (typeParameters == null) return;
+        for (GenericsType tp : typeParameters) {
+            ClassNode[] bounds = tp.getUpperBounds();
+            if (bounds == null) continue;
+            for (int i = 1; i < bounds.length; i++) {
+                ClassNode bound = bounds[i];
+                if (bound != null && !bound.isInterface() && !Traits.isTrait(bound)) {
+                    addError("Additional bounds of a type parameter must be interfaces", tp);
                 }
             }
         }
     }
 
+    /**
+     * True when {@code type} is a parameterized usage ({@code Cell<String>}), as
+     * opposed to the generic type name ({@code Cell}) or the class declaration
+     * node (whose {@link ClassNode#getGenericsTypes()} are the formals).
+     */
+    private static boolean isParameterizedTypeUsage(final ClassNode type) {
+        GenericsType[] gt = type.getGenericsTypes();
+        return gt != null && gt.length > 0 && type.isRedirectNode() && !type.isGenericsPlaceHolder();
+    }
+
+    /**
+     * True when {@code type} carries type arguments, including wildcards
+     * ({@code Outer<?>}). Raw names ({@code Outer}) are not parameterized.
+     */
+    private static boolean isParameterizedEnclosingType(final ClassNode type) {
+        GenericsType[] gt = type.getGenericsTypes();
+        return gt != null && gt.length > 0 && !type.isGenericsPlaceHolder();
+    }
+
+    /**
+     * Nested interfaces and enums are implicitly static; otherwise the
+     * {@code ACC_STATIC} bit on the declaration is authoritative.
+     */
+    private static boolean isStaticMemberType(final ClassNode type) {
+        ClassNode declared = type.redirect();
+        return declared.isStatic()
+                || declared.isInterface()
+                || declared.isEnum();
+    }
+
+    private static boolean hasWildcardTypeArgument(final ClassNode type) {
+        GenericsType[] gt = type.getGenericsTypes();
+        if (gt == null) return false;
+        for (GenericsType t : gt) {
+            if (t.isWildcard()) return true;
+        }
+        ClassNode oc = type.getOuterClassType();
+        return oc != null && hasWildcardTypeArgument(oc);
+    }
+
+    /**
+     * JLS 4.7: types available in full at run time. Used for array creation
+     * (JLS 15.10.1), which requires a reifiable component type.
+     */
+    private static boolean isReifiable(ClassNode type) {
+        while (type.isArray()) {
+            type = type.getComponentType();
+        }
+        if (type.isGenericsPlaceHolder()) return false;
+        if (isPrimitiveType(type)) return true;
+        GenericsType[] gt = type.getGenericsTypes();
+        if (gt != null) {
+            if (gt.length == 0) return false;
+            for (GenericsType t : gt) {
+                if (!isUnboundedWildcard(t)) return false;
+            }
+        }
+        ClassNode oc = type.getOuterClassType();
+        return oc == null || isReifiable(oc);
+    }
+
     private static String plural(final String string, final int count) {
         return count + " " + (count == 1 ? string : string + "s");
     }
diff --git a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
index 78a4a88db0..0bcb6e0d69 100644
--- a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
+++ b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
@@ -420,6 +420,11 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer {
         if (type.isRedirectNode() || !type.isPrimaryClassNode()) {
             visitTypeAnnotations(type); // JSR 308 support
         }
+        ClassNode oc = type.getOuterClassType(); // GROOVY-10646, GROOVY-12319: Outer<T>.Inner
+        if (oc != null) {
+            resolveOrFail(oc, msg, node, preferImports);
+            resolveGenericsTypes(oc.getGenericsTypes());
+        }
         if (preferImports && !type.isResolved() && !type.isPrimaryClassNode()) {
             resolveGenericsTypes(type.getGenericsTypes());
             if (resolveAliasFromModule(type)) return;
@@ -483,6 +488,13 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer {
     protected boolean resolve(final ClassNode type, final boolean testModuleImports, final boolean testDefaultImports, final boolean testStaticInnerClasses) {
         GenericsType[] genericsTypes = type.getGenericsTypes();
         resolveGenericsTypes(genericsTypes);
+        ClassNode oc = type.getOuterClassType();
+        if (oc != null) {
+            if (!resolve(oc, testModuleImports, testDefaultImports, testStaticInnerClasses)) {
+                return false;
+            }
+            resolveGenericsTypes(oc.getGenericsTypes());
+        }
 
         if (type.isPrimaryClassNode()) return true;
         if (type.isArray()) {
@@ -1347,6 +1359,7 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer {
                 addError("You cannot create an instance from the abstract " + getDescription(cceType) + ".", cce);
             }
         }
+        resolveGenericsTypes(cce.getGenericsTypes());
 
         return cce.transformExpression(this);
     }
diff --git a/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java b/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java
index 8786567651..808c70219b 100644
--- a/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java
+++ b/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java
@@ -309,7 +309,7 @@ public class StaticImportVisitor extends ClassCodeExpressionTransformer {
                 }
             }
         } else if (staticWrtCurrent && isSuperExpression(object)) {
-            Expression result = new MethodCallExpression(new ClassExpression(currentClass.getSuperClass()), method, args);
+            Expression result = new MethodCallExpression(new ClassExpression(currentClass.getSuperClass().getPlainNodeReference()), method, args);
             result.setSourcePosition(mce);
             return result;
         }
@@ -416,7 +416,7 @@ public class StaticImportVisitor extends ClassCodeExpressionTransformer {
         if (currentMethod != null && currentMethod.isStatic()
                 && isSuperExpression(pe.getObjectExpression())) {
             PropertyExpression pexp = new PropertyExpression(
-                    new ClassExpression(currentClass.getUnresolvedSuperClass()),
+                    new ClassExpression(currentClass.getUnresolvedSuperClass().getPlainNodeReference()),
                     transform(pe.getProperty())
             );
             pexp.setSourcePosition(pe);
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
index bf6d37f088..1be2df4e32 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -1856,7 +1856,7 @@ public abstract class StaticTypeCheckingSupport {
 
         } else if (type.equals(target)) {
             extractGenericsConnections(connections, type.getGenericsTypes(), target.getGenericsTypes());
-            extractGenericsConnections(connections, type.getNodeMetaData("outer.class"), target.getOuterClass()); //GROOVY-10646
+            extractGenericsConnections(connections, type.getOuterClassType(), target.getOuterClass()); //GROOVY-10646
 
         } else if (implementsInterfaceOrIsSubclassOf(type, target)) {
             ClassNode superClass = getNextSuperClass(type, target);
@@ -2021,7 +2021,7 @@ public abstract class StaticTypeCheckingSupport {
                 Optional.ofNullable(type.getOuterClass())
                     .filter(oc -> oc.getGenericsTypes()!=null)
                     .map(oc -> applyGenericsContext(ctx, spec, oc))
-                    .ifPresent(oc -> newType.putNodeMetaData("outer.class", oc));
+                    .ifPresent(newType::setOuterClassType);
             }
         }
         return new GenericsType(newType);
@@ -2078,6 +2078,9 @@ public abstract class StaticTypeCheckingSupport {
         }
 
         GenericsType[] gt = type.getGenericsTypes();
+        if (gt == null && type.isGenericsPlaceHolder()) {
+            gt = new GenericsType[]{type.asGenericsType()};
+        }
         if (asBoolean(spec)) {
             gt = applyGenericsContext(ctx, spec, gt);
         }
@@ -2087,16 +2090,18 @@ public abstract class StaticTypeCheckingSupport {
             return cn;
         }
 
-        if (!gt[0].isPlaceholder()) { // convert T to Type or Type<...>
-            return getCombinedBoundType(gt[0]);
-        }
+        if (gt != null && gt.length > 0) {
+            if (!gt[0].isPlaceholder()) { // convert T to Type or Type<...>
+                return getCombinedBoundType(gt[0]);
+            }
 
-        if (type.getGenericsTypes()[0] != gt[0]) { // convert T to X
-            ClassNode cn = make(gt[0].getName()) , erasure = getCombinedBoundType(gt[0]).redirect();
-            cn.setGenericsPlaceHolder(true);
-            cn.setGenericsTypes(gt);
-            cn.setRedirect(erasure);
-            return cn;
+            if (type.getGenericsTypes() == null || type.getGenericsTypes()[0] != gt[0]) { // convert T to X
+                ClassNode cn = make(gt[0].getName()), erasure = getCombinedBoundType(gt[0]).redirect();
+                cn.setGenericsPlaceHolder(true);
+                cn.setGenericsTypes(gt);
+                cn.setRedirect(erasure);
+                return cn;
+            }
         }
 
         return type; // nothing to do
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 f74ee46f30..e31fb71edc 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -567,14 +567,20 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             typeCheckingContext.pushEnclosingClassNode(node);
             Set<MethodNode> oldSet = typeCheckingContext.alreadyVisitedMethods;
             typeCheckingContext.alreadyVisitedMethods = new LinkedHashSet<>();
-
-            doWithTypeCheckingExtensions(node, super::visitClass);
-            node.getInnerClasses().forEachRemaining(this::visitClass);
-
-            typeCheckingContext.alreadyVisitedMethods = oldSet;
-            typeCheckingContext.popEnclosingClassNode();
-            if (type != null) {
-                typeCheckingContext.popErrorCollector();
+            boolean oldStatic = typeCheckingContext.isInStaticContext;
+            if (node instanceof InnerClassNode && ((InnerClassNode) node).isAnonymous()) {
+                typeCheckingContext.isInStaticContext = false; // AIC body is an instance context
+            }
+            try {
+                doWithTypeCheckingExtensions(node, super::visitClass);
+                node.getInnerClasses().forEachRemaining(this::visitClass);
+            } finally {
+                typeCheckingContext.isInStaticContext = oldStatic;
+                typeCheckingContext.alreadyVisitedMethods = oldSet;
+                typeCheckingContext.popEnclosingClassNode();
+                if (type != null) {
+                    typeCheckingContext.popErrorCollector();
+                }
             }
 
             node.putNodeMetaData(INFERRED_TYPE, node);
@@ -1418,10 +1424,18 @@ out:    if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0
      */
     protected void inferDiamondType(final ConstructorCallExpression cce, final ClassNode lType) {
         ClassNode cceType = cce.getType(), inferredType = lType;
+        boolean isAIC = cce.isUsingAnonymousInnerClass();
+        if (isAIC) {
+            ClassNode diamond = GenericsUtils.diamondTargetOfAnonymousClass(cce.getType());
+            if (diamond != null) cceType = diamond;
+        }
         // check if constructor call expression makes use of the diamond operator
         if (cceType.getGenericsTypes() != null && cceType.getGenericsTypes().length == 0) {
             ArgumentListExpression argumentList = InvocationWriter.makeArgumentList(cce.getArguments());
             ConstructorNode constructor = cce.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
+            if (isAIC && constructor != null && constructor.getDeclaringClass() != cceType.redirect()) {
+                constructor = matchingConstructor(cceType.redirect(), argumentList.getExpressions().size());
+            }
             if (!argumentList.getExpressions().isEmpty() && constructor != null) {
                 ClassNode type = GenericsUtils.parameterizeType(cceType, cceType);
                 type = inferReturnTypeGenerics(type, constructor, argumentList);
@@ -1448,10 +1462,18 @@ out:    if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0
                 inferredType = getCombinedBoundType(inferredType.getGenericsTypes()[0]);
             }
             adjustGenerics(inferredType, cceType);
-            storeType(cce, cceType);
+            storeType(cce, isAIC ? cce.getType() : cceType);
         }
     }
 
+    private static ConstructorNode matchingConstructor(final ClassNode type, final int argumentCount) {
+        if (type == null || type.isInterface()) return null;
+        for (ConstructorNode ctor : type.getDeclaredConstructors()) {
+            if (ctor.getParameters().length == argumentCount) return ctor;
+        }
+        return null;
+    }
+
     private void adjustGenerics(final ClassNode source, final ClassNode target) {
         GenericsType[] genericsTypes = source.getGenericsTypes();
         if (genericsTypes == null) { // Map foo = new HashMap<>()
@@ -1963,6 +1985,7 @@ out:    if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0
         if (leftExpressionType.isUsingGenerics()
                 && !missesGenericsTypes(rightExpressionType)
                 && !(rightExpression instanceof ClosureExpression) // GROOVY-10277
+                && !(rightExpression instanceof ConstructorCallExpression && ((ConstructorCallExpression) rightExpression).isUsingAnonymousInnerClass()) // GROOVY-12319: AIC type is the generated class, not the diamond super type
                 && !isNullConstant(rightExpression) && !UNKNOWN_PARAMETER_TYPE.equals(rightExpressionType)
                 && !GenericsUtils.buildWildcardType(leftExpressionType).isCompatibleWith(wrapTypeIfNecessary(rightExpressionType)))
             addStaticTypeError("Incompatible generic argument types. Cannot assign " + prettyPrintType(rightExpressionType) + " to: " + prettyPrintType(leftExpressionType), rightExpression);
@@ -3089,65 +3112,95 @@ out:    if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0
             typeCheckingContext.getEnclosingClosure().addReturnType(returnType);
     }
 
-    /** {@inheritDoc} */
     @Override
     public void visitConstructorCallExpression(final ConstructorCallExpression call) {
-        if (!extension.beforeMethodCall(call)) {
-            ClassNode receiver;
-            if (call.isThisCall()) {
-                receiver = makeThis();
-            } else if (call.isSuperCall()) {
-                receiver = makeSuper();
-            } else {
-                receiver = call.getType();
-            }
-            Expression arguments = call.getArguments();
-            ArgumentListExpression argumentList = InvocationWriter.makeArgumentList(arguments);
+        typeCheckingContext.pushEnclosingMethodCall(call);
+        try {
+            if (!extension.beforeMethodCall(call)) {
+                ClassNode receiver;
+                if (call.isThisCall()) {
+                    receiver = makeThis();
+                } else if (call.isSuperCall()) {
+                    receiver = makeSuper();
+                } else {
+                    receiver = call.getType();
+                }
+                Expression arguments = call.getArguments();
+                ArgumentListExpression argumentList = InvocationWriter.makeArgumentList(arguments);
 
-            // visit functional arguments *after* method has been chosen
-            visitMethodCallArguments(receiver, argumentList, false, null);
-            final ClassNode[] argumentTypes = getArgumentTypes(argumentList);
+                // visit functional arguments *after* method has been chosen
+                visitMethodCallArguments(receiver, argumentList, false, null);
+                final ClassNode[] argumentTypes = getArgumentTypes(argumentList);
 
-            MethodNode ctor;
-            if (looksLikeNamedArgConstructor(receiver, argumentTypes)
-                    && findMethod(receiver, "<init>", argumentTypes).isEmpty()
-                    && findMethod(receiver, "<init>", init(argumentTypes)).size() == 1) {
-                ctor = typeCheckMapConstructor(call, receiver, arguments);
-            } else {
-                ctor = findMethodOrFail(call, receiver, "<init>", argumentTypes);
-                visitMethodCallArguments(receiver, argumentList, true, ctor);
+                MethodNode ctor;
+                if (looksLikeNamedArgConstructor(receiver, argumentTypes)
+                        && findMethod(receiver, "<init>", argumentTypes).isEmpty()
+                        && findMethod(receiver, "<init>", init(argumentTypes)).size() == 1) {
+                    ctor = typeCheckMapConstructor(call, receiver, arguments);
+                } else {
+                    ctor = findMethodOrFail(call, receiver, "<init>", argumentTypes);
+                    visitMethodCallArguments(receiver, argumentList, true, ctor);
+                    if (ctor != null) {
+                        Parameter[] parameters = ctor.getParameters();
+                        GenericsType[] typeParameters = ctor.getDeclaringClass().getGenericsTypes();
+                        if (typeParameters != null) { // GROOVY-10283, GROOVY-10316, GROOVY-10482, GROOVY-10624, GROOVY-10698
+                            Map<GenericsTypeName, GenericsType> context = extractGenericsConnectionsFromArguments(typeParameters, parameters, argumentList, receiver.getGenericsTypes());
+                            if (!context.isEmpty()) parameters = Arrays.stream(parameters).map(p -> new Parameter(applyGenericsContext(context, p.getType()), p.getName())).toArray(Parameter[]::new);
+                        }
+                        resolvePlaceholdersFromImplicitTypeHints(argumentTypes, argumentList, parameters);
+                        typeCheckMethodsWithGenericsOrFail(receiver, argumentTypes, ctor, call);
+                        checkForbiddenSpreadArgument(argumentList, parameters);
+                    } else {
+                        checkForbiddenSpreadArgument(argumentList);
+                    }
+                }
                 if (ctor != null) {
-                    Parameter[] parameters = ctor.getParameters();
-                    GenericsType[] typeParameters = ctor.getDeclaringClass().getGenericsTypes();
-                    if (typeParameters != null) { // GROOVY-10283, GROOVY-10316, GROOVY-10482, GROOVY-10624, GROOVY-10698
-                        Map<GenericsTypeName, GenericsType> context = extractGenericsConnectionsFromArguments(typeParameters, parameters, argumentList, receiver.getGenericsTypes());
-                        if (!context.isEmpty()) parameters = Arrays.stream(parameters).map(p -> new Parameter(applyGenericsContext(context, p.getType()), p.getName())).toArray(Parameter[]::new);
+                    storeTargetMethod(call, ctor);
+                    if (call.isUsingGenerics()) {
+                        GenericsType[] typeParameters = ctor.getGenericsTypes();
+                        if (typeParameters != null && typeParameters.length != call.getGenericsTypes().length) {
+                            addStaticTypeError("Incorrect number of constructor type arguments; required " + typeParameters.length, call);
+                        }
                     }
-                    resolvePlaceholdersFromImplicitTypeHints(argumentTypes, argumentList, parameters);
-                    typeCheckMethodsWithGenericsOrFail(receiver, argumentTypes, ctor, call);
-                    checkForbiddenSpreadArgument(argumentList, parameters);
-                } else {
-                    checkForbiddenSpreadArgument(argumentList);
                 }
             }
-            if (ctor != null) storeTargetMethod(call, ctor);
-        }
-
-        // GROOVY-9327: check for AIC in STC method with non-STC enclosing class
-        if (call.isUsingAnonymousInnerClass()) {
-            Set<MethodNode> methods = typeCheckingContext.methodsToBeVisited;
-            if (!methods.isEmpty()) { // indicates specific methods have STC
-                typeCheckingContext.methodsToBeVisited = Collections.emptySet();
 
+            // GROOVY-9327: check for AIC in STC method with non-STC enclosing class
+            if (call.isUsingAnonymousInnerClass()) {
                 ClassNode anonType = call.getType();
-                visitClass(anonType); // visit anon. inner class inline with method
-                anonType.putNodeMetaData(StaticTypeCheckingVisitor.class, Boolean.TRUE);
+                ClassNode diamondTarget = GenericsUtils.diamondTargetOfAnonymousClass(anonType);
+                if (diamondTarget != null && diamondTarget.getGenericsTypes() != null && diamondTarget.getGenericsTypes().length == 0) {
+                    BinaryExpression enclosingBinaryExpression = typeCheckingContext.getEnclosingBinaryExpression();
+                    ClassNode lType = null;
+                    if (enclosingBinaryExpression != null && isAssignment(enclosingBinaryExpression.getOperation().getType())) {
+                        lType = getType(enclosingBinaryExpression.getLeftExpression());
+                    }
+                    if (lType != null) { // assignment target type is the primary JLS 15.9.3 witness
+                        inferDiamondType(call, lType);
+                    }
+                    if (diamondTarget.getGenericsTypes() != null && diamondTarget.getGenericsTypes().length == 0) {
+                        // no target yet (or it was not enough): recover T from overrides so the AIC body is checked against a filled diamond
+                        Map<String, ClassNode> spec = GenericsUtils.inferGenericsSpecFromOverrides(anonType, diamondTarget, Collections.emptyMap());
+                        if (!spec.isEmpty()) {
+                            GenericsUtils.applyGenericsSpec(diamondTarget, spec);
+                        }
+                    }
+                }
+                Set<MethodNode> methods = typeCheckingContext.methodsToBeVisited;
+                if (!methods.isEmpty()) { // indicates specific methods have STC
+                    typeCheckingContext.methodsToBeVisited = Collections.emptySet();
+
+                    visitClass(anonType); // visit anon. inner class inline with method
+                    anonType.putNodeMetaData(StaticTypeCheckingVisitor.class, Boolean.TRUE);
 
-                typeCheckingContext.methodsToBeVisited = methods;
+                    typeCheckingContext.methodsToBeVisited = methods;
+                }
             }
-        }
 
-        extension.afterMethodCall(call);
+            extension.afterMethodCall(call);
+        } finally {
+            typeCheckingContext.popEnclosingMethodCall();
+        }
     }
 
     private boolean looksLikeNamedArgConstructor(final ClassNode receiver, final ClassNode[] argumentTypes) {
@@ -3941,7 +3994,22 @@ out:    if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0
                 boolean typeParametersResolved = false;
                 // first check for explicit type arguments
                 if (isConstructor) {
-                    typeParametersResolved = hasTypeArguments;
+                    Expression emc = typeCheckingContext.getEnclosingMethodCall();
+                    if (emc instanceof ConstructorCallExpression cce) {
+                        GenericsType[] typeArguments = cce.getGenericsTypes();
+                        if (typeArguments != null) {
+                            GenericsType[] methodTypeParameters = method.getGenericsTypes();
+                            if (methodTypeParameters != null && methodTypeParameters.length == typeArguments.length) {
+                                typeParametersResolved = true;
+                                for (int i = 0; i < methodTypeParameters.length; i += 1) {
+                                    context.put(new GenericsTypeName(methodTypeParameters[i].getName()), typeArguments[i]);
+                                }
+                            }
+                        }
+                    }
+                    if (!typeParametersResolved) {
+                        typeParametersResolved = hasTypeArguments;
+                    }
                 } else {
                     Expression emc = typeCheckingContext.getEnclosingMethodCall();
                     if (emc instanceof MethodCallExpression mce) {
@@ -4178,6 +4246,10 @@ out:    if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0
             if (arguments == call.getArguments() || InvocationWriter.makeArgumentList(arguments).getExpressions().stream().anyMatch(arg ->
                     arg instanceof ClosureExpression && DefaultGroovyMethods.contains(InvocationWriter.makeArgumentList(call.getArguments()), arg)))
                 typeArguments = call.getGenericsTypes();
+        } else if (emc instanceof ConstructorCallExpression call) {
+            if (arguments == call.getArguments() || InvocationWriter.makeArgumentList(arguments).getExpressions().stream().anyMatch(arg ->
+                    arg instanceof ClosureExpression && DefaultGroovyMethods.contains(InvocationWriter.makeArgumentList(call.getArguments()), arg)))
+                typeArguments = call.getGenericsTypes();
         }
 
         returnType = inferReturnTypeGenerics(receiver, methodNode, arguments, typeArguments);
@@ -4572,9 +4644,9 @@ out:                if (mn.size() != 1) {
                               case "getThisObject":
                                 returnType = makeThis();
                             }
-                        } else if (isUsingGenericsOrIsArrayUsingGenerics(returnType)) {
+                        } else if (returnType.isGenericsPlaceHolder() || isUsingGenericsOrIsArrayUsingGenerics(returnType)) {
                             ClassNode irtg = inferReturnTypeGenerics(chosenReceiver.getType(), targetMethod, callArguments, call.getGenericsTypes());
-                            if (irtg != null && implementsInterfaceOrIsSubclassOf(irtg, returnType))
+                            if (irtg != null && (returnType.isGenericsPlaceHolder() || implementsInterfaceOrIsSubclassOf(irtg, returnType)))
                                 returnType = irtg;
                         }
                         // GROOVY-7106, GROOVY-7274, GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915, et al.
@@ -6295,6 +6367,13 @@ trying: for (ClassNode[] signature : signatures) {
     protected ClassNode getType(final ASTNode node) {
         ClassNode type = node.getNodeMetaData(INFERRED_TYPE);
         if (type != null) {
+            if (node instanceof VariableExpression vexp && vexp.getOriginType() != null && vexp.getOriginType().getOuterClassType() != null) {
+                if (type.getOuterClassType() == null) {
+                    type = type.getPlainNodeReference();
+                    type.setGenericsTypes(vexp.getOriginType().getGenericsTypes());
+                    type.setOuterClassType(vexp.getOriginType().getOuterClassType());
+                }
+            }
             return type;
         }
 
@@ -6319,7 +6398,15 @@ trying: for (ClassNode[] signature : signatures) {
                 return fieldType;
             }
             if (variable != vexp && variable instanceof VariableExpression) {
-                return getType((VariableExpression) variable);
+                type = getType((VariableExpression) variable);
+                ClassNode originType = ((VariableExpression) variable).getOriginType();
+                if (originType != null && originType.getOuterClassType() != null && type != null
+                        && type.getOuterClassType() == null) {
+                    type = type.getPlainNodeReference();
+                    type.setGenericsTypes(originType.getGenericsTypes());
+                    type.setOuterClassType(originType.getOuterClassType());
+                }
+                return type;
             }
             if (variable instanceof Parameter parameter) {
                 if (getTemporaryTypesForExpression(vexp).isEmpty()) { // not instanceof
@@ -7054,6 +7141,17 @@ out:    for (ClassNode type : todo) {
                 }
             }
             result = extractPlaceHolders(receiver, declaring);
+            ClassNode oc = receiver.getOuterClassType();
+            if (oc == null && receiver.redirect() != null) {
+                oc = receiver.redirect().getOuterClassType();
+            }
+            if (oc == null && argument instanceof VariableExpression vexp && vexp.getOriginType() != null) {
+                oc = vexp.getOriginType().getOuterClassType();
+            }
+            if (oc != null) {
+                Map<GenericsTypeName, GenericsType> outerPlaceHolders = extractPlaceHolders(oc, oc.redirect());
+                outerPlaceHolders.forEach(result::putIfAbsent); // inner type parameters win over outer
+            }
             if (!result.isEmpty()) Optional.ofNullable(method.getGenericsTypes()).ifPresent(methodGenerics ->
                 Arrays.stream(methodGenerics).map(gt -> new GenericsTypeName(gt.getName())).forEach(result::remove)); // GROOVY-10322
         }
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/TypeCheckingContext.java b/src/main/java/org/codehaus/groovy/transform/stc/TypeCheckingContext.java
index 94e461f500..0b798ca866 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/TypeCheckingContext.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/TypeCheckingContext.java
@@ -23,6 +23,7 @@ import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.Parameter;
 import org.codehaus.groovy.ast.expr.BinaryExpression;
 import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.MethodCallExpression;
 import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
@@ -383,13 +384,13 @@ public class TypeCheckingContext {
     /**
      * Pushes a method call into the method call stack.
      *
-     * @param call the call expression to be pushed, either a {@link MethodCallExpression} or a {@link StaticMethodCallExpression}
+     * @param call the call expression to be pushed, either a {@link MethodCallExpression}, {@link StaticMethodCallExpression}, or {@link ConstructorCallExpression}
      */
     public void pushEnclosingMethodCall(final Expression call) {
-        if (call instanceof MethodCallExpression || call instanceof StaticMethodCallExpression) {
+        if (call instanceof MethodCallExpression || call instanceof StaticMethodCallExpression || call instanceof ConstructorCallExpression) {
             enclosingMethodCalls.addFirst(call);
         } else {
-            throw new IllegalArgumentException("Expression must be a method call or a static method call");
+            throw new IllegalArgumentException("Expression must be a method call, a static method call or a constructor call");
         }
     }
 
diff --git a/src/spec/doc/core-operators.adoc b/src/spec/doc/core-operators.adoc
index 207d1a5e06..944a164325 100644
--- a/src/spec/doc/core-operators.adoc
+++ b/src/spec/doc/core-operators.adoc
@@ -909,6 +909,13 @@ same name in Java 7. It is used to indicate that generic types should be inferre
 include::../test/OperatorsTest.groovy[tags=diamond_op,indent=0]
 ----
 
+As of GROOVY-12319, diamond may also be used when creating an anonymous inner class, matching Java 9 (JEP 213):
+
+[source,groovy]
+----
+include::../test/OperatorsTest.groovy[tags=diamond_op_aic,indent=0]
+----
+
 In dynamic Groovy, this is totally unused. In statically type checked Groovy, it is also optional since the Groovy
 type checker performs type inference whether this operator is present or not.
 
diff --git a/src/spec/test/OperatorsTest.groovy b/src/spec/test/OperatorsTest.groovy
index e8ed79159e..92d5e2f4d9 100644
--- a/src/spec/test/OperatorsTest.groovy
+++ b/src/spec/test/OperatorsTest.groovy
@@ -743,6 +743,21 @@ assert function(*args,5,6) == 26
         // end::diamond_op[]
     }
 
+    @Test
+    void testDiamondOperatorOnAnonymousInnerClass() {
+        assertScript '''
+        // tag::diamond_op_aic[]
+        interface Processor<T> {
+            T process(T val)
+        }
+        Processor<String> p = new Processor<>() {
+            String process(String val) { val.toUpperCase() }
+        }
+        assert p.process('ok') == 'OK'
+        // end::diamond_op_aic[]
+        '''
+    }
+
     @Test
     void testCallOperator() {
         assertScript '''
diff --git a/src/test-resources/core/BreakingChange_01x.groovy b/src/test-resources/core/BreakingChange_01x.groovy
index 33bb86d0b6..c23a070075 100644
--- a/src/test-resources/core/BreakingChange_01x.groovy
+++ b/src/test-resources/core/BreakingChange_01x.groovy
@@ -28,7 +28,7 @@ assert b.x == 1
 a<String> b2 = new a<String>()
 assert b2.x == 1
 
-a<String>[] b3 = new a<String>[0]
+a<String>[] b3 = new a[0]
 assert b3.length == 0
 
 core.a b4 = new a()
@@ -37,6 +37,6 @@ assert b4.x == 1
 core.a<String> b5 = new a<String>()
 assert b5.x == 1
 
-core.a<String>[] b6 = new a<String>[0]
+core.a<String>[] b6 = new a[0]
 assert b6.length == 0
 
diff --git a/src/test-resources/core/BreakingChange_04x.groovy b/src/test-resources/core/BreakingChange_04x.groovy
index 477932b17c..0773a3914f 100644
--- a/src/test-resources/core/BreakingChange_04x.groovy
+++ b/src/test-resources/core/BreakingChange_04x.groovy
@@ -28,7 +28,7 @@ b2 = new B<String>()
 assert b2 instanceof B
 
 B<String>[] b3
-b3 = new B<String>[0]
+b3 = new B[0]
 assert b3 instanceof B[]
 
 core.B b4
@@ -40,5 +40,5 @@ b5 = new B<String>()
 assert b5 instanceof B
 
 core.B<String>[] b6
-b6 = new B<String>[0]
+b6 = new B[0]
 assert b6 instanceof B[]
\ No newline at end of file
diff --git a/src/test-resources/core/Expression_19.groovy b/src/test-resources/core/Expression_19.groovy
index 3f893a614b..dd1bff2586 100644
--- a/src/test-resources/core/Expression_19.groovy
+++ b/src/test-resources/core/Expression_19.groovy
@@ -28,14 +28,14 @@ int[][] b = new int[length()][2 * 8];
 ArrayList[] c = new ArrayList[10];
 ArrayList[][] cc = new ArrayList[10][size()];
 java.util.ArrayList[] d = new java.util.ArrayList[10];
-ArrayList[] e = new ArrayList<String>[10];
-java.util.ArrayList[] f = new java.util.ArrayList<String>[10];
-java.util.ArrayList[] g = new java.util.ArrayList<String>[size()];
+ArrayList[] e = new ArrayList<?>[10];
+java.util.ArrayList[] f = new java.util.ArrayList<?>[10];
+java.util.ArrayList[] g = new java.util.ArrayList<?>[size()];
 
 int[][] h = new int[10][];
 int[][][] i = new int[10][][];
 ArrayList[][] j = new ArrayList[10][];
-ArrayList[][] k = new ArrayList<String>[10][];
+ArrayList[][] k = new ArrayList<?>[10][];
 
 def bb = new A.B();
 def bb2 = new A.B[0];
diff --git a/src/test/groovy/bugs/Groovy12319.groovy b/src/test/groovy/bugs/Groovy12319.groovy
new file mode 100644
index 0000000000..2af644c6da
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy12319.groovy
@@ -0,0 +1,1227 @@
+/*
+ *  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 bugs
+
+import org.codehaus.groovy.ast.ClassHelper
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.FieldNode
+import org.codehaus.groovy.ast.GenericsType
+import org.codehaus.groovy.ast.expr.ArgumentListExpression
+import org.codehaus.groovy.ast.expr.ConstantExpression
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression
+import org.codehaus.groovy.control.CompilationFailedException
+import org.codehaus.groovy.control.GenericsVisitor
+import org.codehaus.groovy.control.SourceUnit
+import org.junit.jupiter.api.Test
+import org.objectweb.asm.Opcodes
+
+import static groovy.test.GroovyAssert.assertScript
+import static groovy.test.GroovyAssert.shouldFail
+
+final class Groovy12319 {
+
+    // =========================================================================
+    // 1. Diamond <> on anonymous inner classes
+    // =========================================================================
+
+    @Test
+    void testDiamondOnAnonymousInnerClass_Basic() {
+        assertScript '''
+            interface Processor<T> {
+                T process(T val)
+            }
+
+            Processor<String> p = new Processor<>() {
+                @Override
+                String process(String val) {
+                    return val.toUpperCase()
+                }
+            }
+
+            assert p.process("hello") == "HELLO"
+        '''
+    }
+
+    @Test
+    void testDiamondOnAnonymousInnerClass_ExtendingClass() {
+        assertScript '''
+            abstract class BaseHolder<T> {
+                T value
+                BaseHolder(T value) {
+                    this.value = value
+                }
+                abstract T transform()
+            }
+
+            BaseHolder<Integer> holder = new BaseHolder<>(42) {
+                @Override
+                Integer transform() {
+                    return value * 2
+                }
+            }
+
+            assert holder.transform() == 84
+        '''
+    }
+
+    @Test
+    void testDiamondOnAnonymousInnerClass_MultipleTypeParams() {
+        assertScript '''
+            interface BiTransformer<T, U, R> {
+                R transform(T t, U u)
+            }
+
+            BiTransformer<String, Integer, String> transformer = new BiTransformer<>() {
+                @Override
+                String transform(String s, Integer i) {
+                    return s + ":" + i
+                }
+            }
+
+            assert transformer.transform("score", 100) == "score:100"
+        '''
+    }
+
+    @Test
+    void testDiamondOnAnonymousInnerClass_CompileStatic() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+
+            @CompileStatic
+            class Tester {
+                interface Handler<T> {
+                    T handle(T input)
+                }
+
+                static String run() {
+                    Handler<String> h = new Handler<>() {
+                        @Override
+                        String handle(String input) {
+                            return "handled " + input
+                        }
+                    }
+                    return h.handle("data")
+                }
+            }
+
+            assert Tester.run() == "handled data"
+        '''
+    }
+
+    @Test
+    void testDiamondOnAnonymousInnerClass_TypeChecked() {
+        assertScript '''
+            import groovy.transform.TypeChecked
+
+            @TypeChecked
+            class Tester {
+                static class Box<T> {
+                    T item
+                    Box(T item) { this.item = item }
+                    T get() { return item }
+                }
+
+                static Integer run() {
+                    Box<Integer> b = new Box<>(123) {
+                        @Override
+                        Integer get() {
+                            return super.get() + 1
+                        }
+                    }
+                    return b.get()
+                }
+            }
+
+            assert Tester.run() == 124
+        '''
+    }
+
+    // =========================================================================
+    // 2. Qualified parameterized inner types ("rare" types)
+    // =========================================================================
+
+    @Test
+    void testQualifiedParameterizedInnerType_VariableDeclaration() {
+        assertScript '''
+            class Outer<T> {
+                class Inner<U> {
+                    T t
+                    U u
+                    Inner(T t, U u) {
+                        this.t = t
+                        this.u = u
+                    }
+                    String display() {
+                        return "" + t + "-" + u
+                    }
+                }
+            }
+
+            Outer<String>.Inner<Integer> x = new Outer<String>().new Inner<Integer>("val", 99)
+            assert x.display() == "val-99"
+        '''
+    }
+
+    @Test
+    void testQualifiedParameterizedInnerType_FieldsAndMethods() {
+        assertScript '''
+            class Outer<T> {
+                class Inner<U> {
+                    T t
+                    U u
+                    Inner(T t, U u) { this.t = t; this.u = u }
+                }
+            }
+
+            class Service {
+                Outer<String>.Inner<Integer> field
+
+                Outer<String>.Inner<Integer> echo(Outer<String>.Inner<Integer> param) {
+                    return param
+                }
+            }
+
+            Outer<String> outer = new Outer<>()
+            Outer<String>.Inner<Integer> inner = outer.new Outer.Inner<Integer>("abc", 123)
+            Service s = new Service(field: inner)
+            assert s.echo(s.field).t == "abc"
+            assert s.echo(s.field).u == 123
+        '''
+    }
+
+    @Test
+    void testQualifiedParameterizedInnerType_NestedGenerics() {
+        assertScript '''
+            class Outer<T> {
+                class Inner<U> {
+                    T t; U u
+                    Inner(T t, U u) { this.t = t; this.u = u }
+                }
+            }
+
+            List<Outer<String>.Inner<Integer>> list = new ArrayList<>()
+            Outer<String> o = new Outer<>()
+            list.add(o.new Outer.Inner<Integer>("item", 1))
+            assert list.size() == 1
+            assert list[0].t == "item"
+            assert list[0].u == 1
+        '''
+    }
+
+    @Test
+    void testQualifiedParameterizedInnerType_CompileStatic() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+
+            class Outer<T> {
+                class Inner<U> {
+                    T t
+                    U u
+                    Inner(T t, U u) {
+                        this.t = t
+                        this.u = u
+                    }
+                    T getT() { return t }
+                    U getU() { return u }
+                }
+            }
+
+            @CompileStatic
+            class Tester {
+                static String test() {
+                    Outer<String> outer = new Outer<String>()
+                    Outer<String>.Inner<Integer> inner = outer.new Outer.Inner<Integer>("hello", 42)
+                    return inner.getT() + ":" + inner.getU()
+                }
+            }
+
+            assert Tester.test() == "hello:42"
+        '''
+    }
+
+    @Test
+    void testQualifiedParameterizedInnerType_RawInner() {
+        assertScript '''
+            class Outer<T> {
+                class Inner {
+                    T t
+                    Inner(T t) { this.t = t }
+                    T get() { return t }
+                }
+            }
+
+            Outer<String>.Inner x = new Outer<String>().new Inner("test")
+            assert x.get() == "test"
+        '''
+    }
+
+    // =========================================================================
+    // 3. Explicit type arguments on constructors, this() and super()
+    // =========================================================================
+
+    @Test
+    void testConstructorTypeArguments_Basic() {
+        assertScript '''
+            class Box {
+                String value
+                <T> Box(T t) {
+                    this.value = t.toString()
+                }
+            }
+
+            Box b = new <String>Box("hello")
+            assert b.value == "hello"
+
+            Box b2 = new <Integer>Box(12345)
+            assert b2.value == "12345"
+        '''
+    }
+
+    @Test
+    void testConstructorTypeArguments_SameCompilationUnitTypeResolved() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+            class Token {
+                String id
+                Token(String id) { this.id = id }
+            }
+            class Box {
+                def t
+                <T> Box(T t) { this.t = t }
+            }
+            @CompileStatic
+            class Tester {
+                static String run() {
+                    ((Token) new <Token>Box(new Token('ok')).t).id
+                }
+            }
+            assert Tester.run() == 'ok'
+        '''
+    }
+
+    @Test
+    void testConstructorTypeArguments_GenericClass() {
+        assertScript '''
+            class Pair<K> {
+                K key
+                String desc
+                <V> Pair(K key, V val) {
+                    this.key = key
+                    this.desc = val.toString()
+                }
+            }
+
+            Pair<String> p = new <Integer>Pair<String>("k1", 999)
+            assert p.key == "k1"
+            assert p.desc == "999"
+        '''
+
+        shouldFail CompilationFailedException, '''
+            class Pair<K> {
+                <V> Pair(K key, V val) {}
+            }
+            new <Integer>Pair<>("k2", 888)
+        '''
+    }
+
+    @Test
+    void testConstructorTypeArguments_InnerClass() {
+        assertScript '''
+            class Outer {
+                class Inner {
+                    String result
+                    <T> Inner(T t) {
+                        this.result = "inner:" + t
+                    }
+                }
+            }
+
+            Outer outer = new Outer()
+            Outer.Inner inner = outer.new <String>Outer.Inner("custom")
+            assert inner.result == "inner:custom"
+        '''
+    }
+
+    @Test
+    void testConstructorTypeArguments_ThisCall() {
+        assertScript '''
+            class Target {
+                String text
+                <T> Target(T t) {
+                    this.text = "generic:" + t
+                }
+                Target() {
+                    <String>this("defaultVal")
+                }
+            }
+
+            Target t = new Target()
+            assert t.text == "generic:defaultVal"
+        '''
+    }
+
+    @Test
+    void testConstructorTypeArguments_SuperCall() {
+        assertScript '''
+            class Base {
+                String message
+                <T> Base(T t) {
+                    this.message = "base:" + t
+                }
+            }
+
+            class Derived extends Base {
+                Derived() {
+                    <String>super("fromDerived")
+                }
+            }
+
+            Derived d = new Derived()
+            assert d.message == "base:fromDerived"
+        '''
+    }
+
+    @Test
+    void testConstructorTypeArguments_CompileStatic() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+
+            class Holder {
+                String str
+                <T> Holder(T t) {
+                    this.str = String.valueOf(t)
+                }
+            }
+
+            @CompileStatic
+            class Tester {
+                static String run() {
+                    Holder h = new <String>Holder("static compiled")
+                    return h.str
+                }
+            }
+
+            assert Tester.run() == "static compiled"
+        '''
+    }
+
+    @Test
+    void testDiamondOnAnonymousInnerClass_ExtendingClass_CompileStatic() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+
+            abstract class BaseHolder<T> {
+                T value
+                BaseHolder(T value) {
+                    this.value = value
+                }
+                abstract T transform()
+            }
+
+            @CompileStatic
+            class Tester {
+                static Integer run() {
+                    BaseHolder<Integer> holder = new BaseHolder<>(42) {
+                        @Override
+                        Integer transform() {
+                            return value * 2
+                        }
+                    }
+                    return holder.transform()
+                }
+            }
+
+            assert Tester.run() == 84
+        '''
+    }
+
+    @Test
+    void testDiamondOnAnonymousInnerClass_DefVariable() {
+        assertScript '''
+            interface Formatter<T> {
+                String format(T t)
+            }
+
+            def f = new Formatter<String>() {
+                @Override
+                String format(String s) {
+                    return "fmt:" + s
+                }
+            }
+            assert f.format("hello") == "fmt:hello"
+        '''
+    }
+
+    @Test
+    void testQualifiedParameterizedInnerType_Chained() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+
+            class Level1<A> {
+                A a
+                Level1(A a) { this.a = a }
+
+                class Level2<B> {
+                    B b
+                    Level2(B b) { this.b = b }
+                    String summarize() {
+                        return "" + a + ":" + b
+                    }
+                }
+            }
+
+            @CompileStatic
+            class Tester {
+                static String run() {
+                    Level1<String> l1 = new Level1<String>("hello")
+                    Level1<String>.Level2<Integer> l2 = l1.new Level1.Level2<Integer>(42)
+                    return l2.summarize()
+                }
+            }
+
+            assert Tester.run() == "hello:42"
+        '''
+    }
+
+    @Test
+    void testQualifiedParameterizedInnerType_NestedInTypeArgument() {
+        assertScript '''
+            class Outer<T> {
+                class Inner<U> {
+                    T t
+                    U u
+                    Inner(T t, U u) {
+                        this.t = t
+                        this.u = u
+                    }
+                }
+            }
+
+            List<Outer<String>.Inner<Integer>> list = new ArrayList<Outer<String>.Inner<Integer>>()
+            Outer<String> o = new Outer<String>()
+            list.add(o.new Outer.Inner<Integer>("hello", 123))
+            assert list.get(0).t == "hello"
+            assert list.get(0).u == 123
+        '''
+    }
+
+    @Test
+    void testConstructorTypeArguments_MultipleTypes() {
+        assertScript '''
+            class MultiHolder {
+                String desc
+                <K, V> MultiHolder(K k, V v) {
+                    this.desc = k + " -> " + v
+                }
+            }
+
+            MultiHolder m = new <String, Integer>MultiHolder("key", 100)
+            assert m.desc == "key -> 100"
+        '''
+    }
+
+    @Test
+    void testConstructorTypeArguments_CompileStatic_ThisAndSuper() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+
+            class BaseClass {
+                String value
+                <T> BaseClass(T t) {
+                    this.value = "base:" + t
+                }
+            }
+
+            @CompileStatic
+            class ChildClass extends BaseClass {
+                ChildClass(int n) {
+                    <Integer>super(n)
+                }
+                ChildClass() {
+                    <String>this("default-child")
+                }
+                <T> ChildClass(T t) {
+                    <T>super(t)
+                }
+            }
+
+            assert new ChildClass(123).value == "base:123"
+            assert new ChildClass().value == "base:default-child"
+            assert new ChildClass("explicit").value == "base:explicit"
+        '''
+    }
+
+    @Test
+    void testDiamondOnAnonymousInnerClass_MethodArgument() {
+        assertScript '''
+            interface Processor<T> {
+                T process(T val)
+            }
+
+            static String apply(Processor<String> p) {
+                p.process('ok')
+            }
+
+            assert apply(new Processor<>() {
+                @Override
+                String process(String val) { val.toUpperCase() }
+            }) == 'OK'
+        '''
+    }
+
+    @Test
+    void testQualifiedParameterizedInnerType_ThreeLevels() {
+        assertScript '''
+            class A<T> {
+                T a
+                A(T a) { this.a = a }
+                class B<U> {
+                    U b
+                    B(U b) { this.b = b }
+                    class C<V> {
+                        V c
+                        C(V c) { this.c = c }
+                        String all() { '' + a + ':' + b + ':' + c }
+                    }
+                }
+            }
+
+            A<String> a = new A<String>('x')
+            A<String>.B<Integer> b = a.new A.B<Integer>(1)
+            A<String>.B<Integer>.C<Long> c = b.new A.B.C<Long>(2L)
+            assert c.all() == 'x:1:2'
+        '''
+    }
+
+    @Test
+    void testConstructorTypeArguments_NestedThisCall() {
+        assertScript '''
+            class Box {
+                String value
+                <T> Box(T t) { this.value = String.valueOf(t) }
+                Box(int ignored) { <Double>this(1.5d) }
+            }
+
+            assert new Box(0).value == '1.5'
+        '''
+    }
+
+    @Test
+    void testInvalidDiamondUsageStillFails() {
+        shouldFail CompilationFailedException, '''
+            class MyList extends ArrayList<> {}
+        '''
+
+        shouldFail CompilationFailedException, '''
+            class Test {
+                List<> list
+            }
+        '''
+    }
+
+    @Test
+    void testConstructorTypeArgumentsAndDiamondRejected() {
+        shouldFail CompilationFailedException, '''
+            class Box {
+                <T> Box(T t) {}
+            }
+            new <String>Box<>("x")
+        '''
+    }
+
+    @Test
+    void testConstructorTypeArgumentsAndDiamondRejectedOnInterfaceAIC() {
+        shouldFail CompilationFailedException, '''
+            interface Processor<T> {
+                T process(T val)
+            }
+            new <String>Processor<>() {
+                String process(String val) { val }
+            }
+        '''
+    }
+
+    @Test
+    void testDiamondOnNonGenericAnonymousClassRejected() {
+        shouldFail CompilationFailedException, '''
+            new Object<>() {}
+        '''
+    }
+
+    @Test
+    void testRareTypeArityMismatchRejected() {
+        shouldFail CompilationFailedException, '''
+            class Outer<T> {
+                class Inner<U> {}
+            }
+            Outer<String, Integer>.Inner<Integer> x
+        '''
+    }
+
+    @Test
+    void testConstructorTypeArgumentsArityMismatchCompileStatic() {
+        shouldFail CompilationFailedException, '''
+            import groovy.transform.CompileStatic
+            class Box {
+                <T> Box(T t) {}
+            }
+            @CompileStatic
+            void m() {
+                new <String, Integer>Box("x")
+            }
+            m()
+        '''
+    }
+
+    @Test
+    void testDiamondOnAnonymousInnerClass_MethodArgument_CompileStatic() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+
+            interface Processor<T> {
+                T process(T val)
+            }
+
+            @CompileStatic
+            class Tester {
+                static String apply(Processor<String> p) {
+                    p.process('ok')
+                }
+                static String run() {
+                    apply(new Processor<>() {
+                        @Override
+                        String process(String val) { val.toUpperCase() }
+                    })
+                }
+            }
+
+            assert Tester.run() == 'OK'
+        '''
+    }
+
+    @Test
+    void testDiamondOnAnonymousInnerClass_MethodArgument_NestedGenerics() {
+        assertScript '''
+            interface Box<T> {
+                List<T> items()
+            }
+
+            static List<String> take(Box<String> b) {
+                b.items()
+            }
+
+            assert take(new Box<>() {
+                @Override
+                List<String> items() { ['a'] }
+            }) == ['a']
+        '''
+    }
+
+    @Test
+    void testDiamondOnAnonymousInnerClass_MethodArgument_NestedGenerics_CompileStatic() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+
+            interface Box<T> {
+                List<T> items()
+            }
+
+            @CompileStatic
+            class Tester {
+                static List<String> take(Box<String> b) {
+                    b.items()
+                }
+                static List<String> run() {
+                    take(new Box<>() {
+                        @Override
+                        List<String> items() { ['a'] }
+                    })
+                }
+            }
+
+            assert Tester.run() == ['a']
+        '''
+    }
+
+    @Test
+    void testQualifiedParameterizedInnerType_UnparameterizedMiddle() {
+        assertScript '''
+            class A<T> {
+                T a
+                A(T a) { this.a = a }
+                class B {
+                    class C<U> {
+                        U c
+                        C(U c) { this.c = c }
+                        String all() { '' + a + ':' + c }
+                    }
+                }
+            }
+
+            A<String> a = new A<String>('x')
+            A<String>.B b = a.new A.B()
+            A<String>.B.C<Integer> c = b.new A.B.C<Integer>(7)
+            assert c.all() == 'x:7'
+        '''
+    }
+
+    @Test
+    void testRareTypeInstanceCreationInsideOuter() {
+        assertScript '''
+            class Outer<T> {
+                class Inner<U> {
+                    U u
+                    Inner(U u) { this.u = u }
+                }
+                Inner<Integer> make() {
+                    return new Outer<T>.Inner<Integer>(7)
+                }
+            }
+
+            assert new Outer<String>().make().u == 7
+        '''
+    }
+
+    @Test
+    void testDiamondOnAnonymousInnerClass_ArrayTypeParameter() {
+        assertScript '''
+            interface ArrProc<T> {
+                T[] process(T[] val)
+            }
+            ArrProc<String> p = new ArrProc<>() {
+                @Override
+                String[] process(String[] val) { val }
+            }
+            assert p.process(['a', 'b'] as String[]).join('-') == 'a-b'
+        '''
+    }
+
+    @Test
+    void testConstructorCallExpressionTypeArgumentsText() {
+        def stringType = new GenericsType(ClassHelper.STRING_TYPE)
+        def cce = new ConstructorCallExpression(ClassHelper.make('Box'), new ArgumentListExpression(new ConstantExpression('x')))
+        assert !cce.isUsingGenerics()
+        cce.setGenericsTypes([stringType] as GenericsType[])
+        assert cce.isUsingGenerics()
+        assert cce.text.startsWith('new <java.lang.String>Box')
+
+        def thisCall = new ConstructorCallExpression(ClassNode.THIS, new ArgumentListExpression())
+        thisCall.setGenericsTypes([stringType] as GenericsType[])
+        assert thisCall.text.contains('<java.lang.String>this')
+
+        def superCall = new ConstructorCallExpression(ClassNode.SUPER, new ArgumentListExpression())
+        superCall.setGenericsTypes([stringType] as GenericsType[])
+        assert superCall.text.contains('<java.lang.String>super')
+
+        def transformed = cce.transformExpression { it }
+        assert transformed instanceof ConstructorCallExpression
+        assert transformed.isUsingGenerics()
+        assert transformed.genericsTypes.length == 1
+
+        def emptyArgs = new ConstructorCallExpression(ClassHelper.make('Box'), new ArgumentListExpression())
+        emptyArgs.setGenericsTypes(GenericsType.EMPTY_ARRAY)
+        assert !emptyArgs.isUsingGenerics()
+        assert emptyArgs.text.startsWith('new Box')
+    }
+
+    @Test
+    void testStaticFieldViaParameterizedTypeRejected() {
+        def err = shouldFail CompilationFailedException, '''
+            class Cell<T> {
+                static final String ID = 'cell'
+            }
+            Cell<String>.ID
+        '''
+        assert err.message.contains('parameterization')
+    }
+
+    @Test
+    void testMethodTypeParameterAdditionalBoundMustBeInterface() {
+        def err = shouldFail CompilationFailedException, '''
+            class C {
+                <T extends Number & String> void m() {}
+            }
+        '''
+        assert err.message.contains('interfaces')
+    }
+
+    @Test
+    void testWildcardOnEnclosingConstructorTypeRejected() {
+        def err = shouldFail CompilationFailedException, '''
+            class Outer<T> {
+                static class Inner<U> {}
+            }
+            new Outer<?>.Inner<Integer>()
+        '''
+        assert err.message.contains('wildcard type')
+    }
+
+    @Test
+    void testGenericArrayCreationOfRareTypeRejected() {
+        def err = shouldFail CompilationFailedException, '''
+            class Outer<T> {
+                class Inner {}
+            }
+            new Outer<String>.Inner[1]
+        '''
+        assert err.message.contains('generic array creation')
+    }
+
+    @Test
+    void testReifiableArrayOfUnboundedWildcardEnclosingType() {
+        // javac accepts this: Inner is a non-static member type of the reifiable
+        // enclosing type Outer<?> (JLS 4.7 / 15.10.1). The static nested
+        // counterpart Outer<?>.Nested is rejected — see the next test.
+        assertScript '''
+            class Outer<T> {
+                class Inner {}
+            }
+            def arr = new Outer<?>.Inner[0]
+            assert arr.length == 0
+        '''
+    }
+
+    @Test
+    void testReifiableArrayOfUnboundedWildcardGenericInner() {
+        // javac also accepts the generic-member form when every type argument
+        // is an unbounded wildcard (JLS 4.7).
+        assertScript '''
+            class Outer<T> {
+                class InnerG<U> {}
+            }
+            def arr = new Outer<?>.InnerG<?>[0]
+            assert arr.length == 0
+        '''
+    }
+
+    @Test
+    void testStaticNestedTypeViaParameterizedEnclosingRejected() {
+        def err = shouldFail CompilationFailedException, '''
+            class Outer<T> {
+                static class Nested {}
+            }
+            new Outer<?>.Nested[0]
+        '''
+        assert err.message.contains('parameterization')
+
+        err = shouldFail CompilationFailedException, '''
+            class Outer<T> {
+                static class Nested {}
+            }
+            Outer<?>.Nested x
+        '''
+        assert err.message.contains('parameterization')
+
+        // Nested interfaces are implicitly static (JLS 9.5); Map.Entry is the
+        // JDK example of the same rule.
+        err = shouldFail CompilationFailedException, '''
+            new java.util.Map<?,?>.Entry[0]
+        '''
+        assert err.message.contains('parameterization')
+    }
+
+    @Test
+    void testRawGenericMemberOfParameterizedEnclosingRejected() {
+        def err = shouldFail CompilationFailedException, '''
+            class Outer<T> {
+                class InnerG<U> {}
+            }
+            new Outer<?>.InnerG[0]
+        '''
+        assert err.message.contains('parameterized enclosing type')
+    }
+
+    @Test
+    void testInstanceofParameterizedRareTypeRejected() {
+        def err = shouldFail CompilationFailedException, '''
+            class Outer<T> {
+                class Inner {}
+            }
+            def x = null
+            x instanceof Outer<String>.Inner
+        '''
+        assert err.message.contains('parameterized type')
+
+        err = shouldFail CompilationFailedException, '''
+            def x = ([] instanceof java.util.ArrayList<String>)
+        '''
+        assert err.message.contains('parameterized type')
+    }
+
+    @Test
+    void testConstructorTypeArgumentsWithFunctionalArgumentCompileStatic() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+            import java.util.function.Supplier
+
+            class Box {
+                String s
+                <T> Box(Supplier<String> f) {
+                    this.s = f.get()
+                }
+            }
+
+            @CompileStatic
+            class Tester {
+                static String run() {
+                    new <Integer>Box({ -> 'ok' }).s
+                }
+            }
+
+            assert Tester.run() == 'ok'
+        '''
+    }
+
+    @Test
+    void testCompileStaticRareTypeVariableAndMethodCall() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+            class Outer<T> {
+                class Inner<U> {
+                    U u
+                    Inner(U u) { this.u = u }
+                    U get() { u }
+                }
+            }
+            @CompileStatic
+            class Tester {
+                static Integer run() {
+                    Outer<String>.Inner<Integer> inner = new Outer<String>().new Inner<Integer>(9)
+                    def copy = inner
+                    copy.get()
+                }
+            }
+            assert Tester.run() == 9
+        '''
+    }
+
+    @Test
+    void testImplementsStaticNestedViaParameterizedEnclosingRejected() {
+        // Nested interfaces are implicitly static, so Outer<String>.Inner is
+        // the same JLS 6.5.5 error as Outer<?>.Nested. javac 25 rejects this.
+        def err = shouldFail CompilationFailedException, '''
+            class Outer<T> {
+                interface Inner<U> {
+                    U id(U u)
+                }
+            }
+            class Impl implements Outer<String>.Inner<Integer> {
+                Integer id(Integer u) { u }
+            }
+        '''
+        assert err.message.contains('parameterization')
+    }
+
+    @Test
+    void testImplementsNestedInterfaceOfRawEnclosing() {
+        // The legal counterpart: select the nested interface from the raw
+        // enclosing type name (JLS 4.5.2 / 6.5.5).
+        assertScript '''
+            class Outer<T> {
+                interface Inner<U> {
+                    U id(U u)
+                }
+            }
+            class Impl implements Outer.Inner<Integer> {
+                Integer id(Integer u) { u }
+            }
+            assert new Impl().id(3) == 3
+        '''
+    }
+
+    @Test
+    void testInvalidDiamondOnFieldMentionsDiamond() {
+        def fieldType = ClassHelper.LIST_TYPE.getPlainNodeReference()
+        fieldType.genericsTypes = GenericsType.EMPTY_ARRAY
+        def cn = new ClassNode('C', Opcodes.ACC_PUBLIC, ClassHelper.OBJECT_TYPE)
+        cn.addField(new FieldNode('f', Opcodes.ACC_PUBLIC, fieldType, cn, null))
+        def source = SourceUnit.create('C.groovy', '')
+        new GenericsVisitor(source).visitClass(cn)
+        assert source.errorCollector.errorCount > 0
+        assert source.errorCollector.errors.any { it.cause.message.contains('invalid Diamond') }
+    }
+
+    @Test
+    void testMultiDimensionalArrayCreationIsReifiable() {
+        assertScript '''
+            def a = new String[0][0]
+            assert a.length == 0
+            def b = new String[][]{ new String[]{'a'} }
+            assert b[0][0] == 'a'
+        '''
+    }
+
+    @Test
+    void testPrimitiveArrayCreatedName() {
+        assertScript '''
+            def a = new int[2]
+            assert a.length == 2
+        '''
+    }
+
+    @Test
+    void testMethodLevelTypeCheckedAnonymousClass() {
+        assertScript '''
+            interface Processor<T> {
+                T process(T val)
+            }
+            class Host {
+                @groovy.transform.TypeChecked
+                String run() {
+                    Processor<String> p = new Processor<>() {
+                        @Override
+                        String process(String val) { val.toUpperCase() }
+                    }
+                    p.process('ok')
+                }
+            }
+            assert new Host().run() == 'OK'
+        '''
+    }
+
+    @Test
+    void testNamedArgConstructorCompileStatic() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+            class Person {
+                String name
+            }
+            @CompileStatic
+            class Tester {
+                static String run() {
+                    Person p = new Person(name: 'Ada')
+                    p.name
+                }
+            }
+            assert Tester.run() == 'Ada'
+        '''
+    }
+
+    @Test
+    void testSpreadConstructorArgumentCompileStatic() {
+        def err = shouldFail CompilationFailedException, '''
+            import groovy.transform.CompileStatic
+            @CompileStatic
+            void m(List<String> args) {
+                new ArrayList<String>(*args)
+            }
+        '''
+        assert err.message != null
+    }
+
+    @Test
+    void testGenericClassConstructorWithSamAndConstructorTypeArguments() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+            import java.util.function.Supplier
+
+            class Box<A> {
+                String s
+                <T> Box(Supplier<String> f) {
+                    this.s = f.get()
+                }
+            }
+
+            @CompileStatic
+            class Tester {
+                static String run() {
+                    def a = new <Integer>Box<String>({ -> 'ok' }).s
+                    def b = new Box<String>({ -> 'ok' }).s
+                    a + b
+                }
+            }
+            assert Tester.run() == 'okok'
+        '''
+    }
+
+    @Test
+    void testDiamondAicConstructorArityMismatchFallsBack() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+            abstract class Base<T> {
+                T value
+                Base(int a, int b) { this.value = null }
+                abstract T get()
+            }
+            @CompileStatic
+            class Tester {
+                static String run() {
+                    Base<String> b = new Base<String>(1, 2) {
+                        @Override String get() { 'x' }
+                    }
+                    b.get()
+                }
+            }
+            assert Tester.run() == 'x'
+        '''
+    }
+
+    @Test
+    void testDiamondInterfaceAicInsideInstanceMethod() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+            @CompileStatic
+            class Outer {
+                interface I<T> { T n() }
+                String run() {
+                    I<String> i = new I<>() {
+                        @Override String n() { 'x' }
+                    }
+                    i.n()
+                }
+            }
+            assert new Outer().run() == 'x'
+        '''
+    }
+
+    @Test
+    void testUnresolvedRareOuterTypeRejected() {
+        def err = shouldFail CompilationFailedException, '''
+            UnknownOuter<String>.Inner x
+        '''
+        assert err.message.toLowerCase().contains('unable to resolve') || err.message.contains('UnknownOuter')
+    }
+
+    @Test
+    void testGrabGeneratesClassLoaderPlainReference() {
+        def err = shouldFail '''
+            @Grab(group='org.apache.groovy', module='does-not-exist', version='0.0.0')
+            class GrabDummy12319 {
+                def x() { 1 }
+            }
+            new GrabDummy12319()
+        '''
+        assert err != null
+    }
+
+    @Test
+    void testStaticSuperPropertyAndMethodPlainNodeReference() {
+        assertScript '''
+            class Base<T> {
+                static String NAME = 'base'
+                static String m() { 'm' }
+            }
+            class Sub extends Base<String> {
+                static String read() { super.NAME + super.m() }
+            }
+            assert Sub.read() == 'basem'
+        '''
+    }
+}
+
diff --git a/src/test/groovy/bugs/Groovy5318.groovy b/src/test/groovy/bugs/Groovy5318.groovy
index 83ecebf088..c3249b33d3 100644
--- a/src/test/groovy/bugs/Groovy5318.groovy
+++ b/src/test/groovy/bugs/Groovy5318.groovy
@@ -26,11 +26,12 @@ import static groovy.test.GroovyAssert.shouldFail
 final class Groovy5318 {
 
     @Test
-    void testTypeArgumentsOnlyOnTheLastComponent() {
+    void testTypeArgumentsOnNonClassQualifier() {
         def err = shouldFail '''
             def a = new java.util<Integer>.ArrayList<ArrayList<Integer>>()
         '''
 
-        assert err.message =~ 'Unexpected input: \'.\''
+        // GROOVY-12319: parsed as a rare type; java.util is a package, not a class
+        assert err.message.contains('unable to resolve class java.util')
     }
 }
diff --git a/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy b/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy
index e996753a80..6cca7ae739 100644
--- a/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy
+++ b/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy
@@ -50,13 +50,37 @@ import static groovy.test.GroovyAssert.shouldFail
  * reflective Signature surface ({@link Class#getTypeParameters()},
  * {@link Class#getGenericSuperclass()}, {@link Method#getGenericReturnType()},
  * and so on) plus observable compile / run behaviour.
+ *
+ * Spec citations are the Java Language Specification, Java SE 25 Edition.
+ * The suite is organised around the four JCK/JLS dimensions for generics:
+ * <ol>
+ * <li>compile-time type safety and erasure, including bridge methods
+ *     (JLS 4.6, 8.4.8.3);</li>
+ * <li>advanced syntax and bounds — wildcards, F-bounds, nested and
+ *     multi-parameter types (JLS 4.4–4.5, 4.9);</li>
+ * <li>runtime reflection of {@code Signature} attributes
+ *     ({@link Class#getGenericSuperclass()},
+ *     {@link java.lang.reflect.ParameterizedType#getActualTypeArguments()},
+ *     {@link java.lang.reflect.GenericArrayType});</li>
+ * <li>raw-type backward compatibility (JLS 4.8, 5.1.9).</li>
+ * </ol>
+ * Categories 1–15 below map onto those dimensions. Category 16 spells out
+ * the JCK-shaped cases that were not previously a dedicated test.
+ * Each test method names the JLS sections it exercises. Gaps where static
+ * Groovy currently diverges from javac are marked {@link org.junit.jupiter.api.Disabled}.
+ * The oracle is javac as shipped with Java SE 25.
  */
 final class GenericsJavaCompatibilityTest {
 
     // =========================================================================
-    // Category 1: Generic Class & Interface Declarations and Type Bounds
+    // Category 1: Generic class and interface declarations and type bounds (JLS 4.4, 8.1.2, 9.1.2)
     // =========================================================================
 
+    /**
+     * JLS 8.1.2, 4.4, 4.5: a generic class {@code Box<T>} declares a type parameter
+     * that each parameterization substitutes. {@code new Box<>("...")} uses diamond
+     * inference (JLS 15.9.3).
+     */
     @Test
     void testGenericClassSingleTypeParameter() {
         final String className = 'gls.generics.test.SingleTypeParam'
@@ -79,6 +103,11 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'Apache Groovy Generics', true)
     }
 
+    /**
+     * JLS 8.1.2, 4.5: a generic class may declare several type parameters
+     * ({@code Pair<K, V>}); the parameterization must supply exactly that many
+     * arguments.
+     */
     @Test
     void testGenericClassMultipleTypeParameters() {
         final String className = 'gls.generics.test.MultipleTypeParams'
@@ -105,6 +134,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'score=100', true)
     }
 
+    /**
+     * JLS 9.1.2, 8.1.5, 8.4.8.1: a class may implement a parameterization of a
+     * generic interface and override its methods with the substituted argument types.
+     */
     @Test
     void testGenericInterfaceImplementation() {
         final String className = 'gls.generics.test.GenericInterfaceImpl'
@@ -129,6 +162,11 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 21, true)
     }
 
+    /**
+     * JLS 4.4: a type-variable bound {@code T extends Number} restricts each
+     * type argument to a subtype of that bound; members of {@code Number} are
+     * available on values of type {@code T}.
+     */
     @Test
     void testSingleUpperBound() {
         final String className = 'gls.generics.test.SingleUpperBound'
@@ -151,6 +189,11 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 45.14d, true)
     }
 
+    /**
+     * JLS 4.4, 4.9: an intersection bound {@code T extends Number & Comparable<T> & Serializable}
+     * requires a class type (or type variable) first, then interfaces. The erasure of
+     * {@code T} is the leftmost bound.
+     */
     @Test
     void testMultipleBounds() {
         final String className = 'gls.generics.test.MultipleBounds'
@@ -182,6 +225,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 29.0d, true)
     }
 
+    /**
+     * JLS 4.4, 8.4.4: F-bounded polymorphism {@code T extends Comparable<T>} lets a
+     * generic method compare elements of {@code List<T>} using the bound's members.
+     */
     @Test
     void testRecursiveTypeBoundsFBounded() {
         final String className = 'gls.generics.test.RecursiveBounds'
@@ -210,9 +257,13 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Category 2: Generic Methods & Constructors
+    // Category 2: Generic methods and constructors (JLS 8.4.4, 8.8.4, 15.12, 18.5)
     // =========================================================================
 
+    /**
+     * JLS 8.4.4, 18.5.1, 18.5.2: invocation of a generic method {@code <T> List<T> makePairList(T, T)}
+     * infers {@code T} from the argument types and the assignment target.
+     */
     @Test
     void testGenericMethodInference() {
         final String className = 'gls.generics.test.MethodInference'
@@ -236,6 +287,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 4)
     }
 
+    /**
+     * JLS 15.12.2: type arguments of a generic method may be written explicitly
+     * ({@code Helper.<String>identity(...)}) instead of being inferred.
+     */
     @Test
     void testGenericMethodExplicitTypeArguments() {
         final String className = 'gls.generics.test.ExplicitTypeArgs'
@@ -261,6 +316,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'Explicit:123')
     }
 
+    /**
+     * JLS 8.8.4, 15.9.3: a constructor may declare its own type parameters, independent
+     * of any class type parameters, and callers may pass explicit constructor type arguments.
+     */
     @Test
     void testGenericConstructors() {
         final String className = 'gls.generics.test.GenericConstructors'
@@ -275,8 +334,8 @@ final class GenericsJavaCompatibilityTest {
                     public String getTypeName() { return typeName; }
                 }
                 public static String test() {
-                    TypeHolder h1 = new TypeHolder("text");
-                    TypeHolder h2 = new TypeHolder(42);
+                    TypeHolder h1 = new <String>TypeHolder("text");
+                    TypeHolder h2 = new <Integer>TypeHolder(42);
                     return h1.getTypeName() + "-" + h2.getTypeName();
                 }
             }
@@ -284,6 +343,11 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'String-Integer')
     }
 
+    /**
+     * JLS 8.4.1, 9.6.4.7: a generic varargs method {@code <T> List<T> collect(T...)} is
+     * applicable by variable-arity invocation (JLS 15.12.2.4). {@code @SafeVarargs}
+     * documents that the heap-pollution risk is contained.
+     */
     @Test
     void testGenericVarargs() {
         final String className = 'gls.generics.test.GenericVarargs'
@@ -313,9 +377,14 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Category 3: Wildcards & PECS
+    // Category 3: Wildcards, containment and capture conversion (JLS 4.5.1, 5.1.10)
     // =========================================================================
 
+    /**
+     * JLS 4.5.1 (Example 4.5.1-1): {@code Collection<?>} accepts any parameterization.
+     * {@code ? extends Object} is equivalent to unbounded {@code ?}. Elements are
+     * readable as {@code Object}.
+     */
     @Test
     void testUnboundedWildcard() {
         final String className = 'gls.generics.test.UnboundedWildcard'
@@ -340,6 +409,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 6)
     }
 
+    /**
+     * JLS 4.5.1: {@code List<? extends Number>} is a producer (contains {@code Integer},
+     * {@code Double}, ...). Subtyping follows containment: {@code List<Integer> <= List<? extends Number>}.
+     */
     @Test
     void testUpperBoundedWildcardProducer() {
         final String className = 'gls.generics.test.UpperBoundedWildcard'
@@ -364,6 +437,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 10.0d)
     }
 
+    /**
+     * JLS 4.5.1: {@code List<? super Integer>} is a consumer; {@code Integer} may be
+     * added because the unknown element type is a supertype of {@code Integer}.
+     */
     @Test
     void testLowerBoundedWildcardConsumer() {
         final String className = 'gls.generics.test.LowerBoundedWildcard'
@@ -390,6 +467,78 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 9)
     }
 
+    /**
+     * JLS 4.5: a wildcard type argument is within bounds of {@code T extends Number}
+     * when capture conversion yields a well-formed glb with {@code Number}.
+     * Unbounded {@code ?} is always within bounds. {@code ? extends Object} is
+     * also legal: {@code glb(Object, Number)} is {@code Number}. javac 25 accepts
+     * both; it does <em>not</em> reject {@code NumberBox<? extends Object>}.
+     */
+    @Test
+    void testBoundedTypeParameterAcceptsUnboundedAndObjectWildcards() {
+        final String className = 'gls.generics.test.NumberBoxWildcards'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class NumberBoxWildcards {
+                public static class NumberBox<T extends Number> {
+                    public final T value;
+                    public NumberBox(T value) { this.value = value; }
+                }
+                public static int test() {
+                    NumberBox<Integer> box = new NumberBox<Integer>(Integer.valueOf(7));
+                    NumberBox<?> unbounded = box;
+                    NumberBox<? extends Object> objectBound = box;
+                    NumberBox<? extends Number> numberBound = box;
+                    NumberBox<? extends Integer> integerBound = box;
+                    NumberBox<? super Integer> superInteger = box;
+                    NumberBox<? super Number> superNumber = new NumberBox<Number>(Integer.valueOf(1));
+                    return (unbounded != null && objectBound != null && numberBound != null
+                            && integerBound != null && superInteger != null && superNumber != null) ? 1 : 0;
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 1)
+    }
+
+    /**
+     * JLS 4.5: {@code ? extends String} is not within bounds of {@code T extends Number}
+     * because {@code glb(String, Number)} is an empty intersection of two classes.
+     * javac 25: "type argument ? extends String is not within bounds of type-variable T".
+     */
+    @Test
+    void testNegativeWildcardUpperBoundDisjointFromTypeParameterBound() {
+        final String className = 'gls.generics.test.NumberBoxExtendsString'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class NumberBoxExtendsString {
+                public static class NumberBox<T extends Number> {}
+                public NumberBox<? extends String> field;
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "not within bounds", "not a valid substitute for the bounded parameter")
+    }
+
+    /**
+     * JLS 4.5: {@code ? super Object} is not within bounds of {@code T extends Number}
+     * because the lower bound {@code Object} is not a subtype of {@code Number}.
+     */
+    @Test
+    void testNegativeWildcardLowerBoundOutsideTypeParameterBound() {
+        final String className = 'gls.generics.test.NumberBoxSuperObject'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class NumberBoxSuperObject {
+                public static class NumberBox<T extends Number> {}
+                public NumberBox<? super Object> field;
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "not within bounds", "not a valid substitute for the bounded parameter")
+    }
+
+    /**
+     * JLS 5.1.10: capture conversion turns {@code List<?>} into {@code List<capture#1-of ?>}
+     * so a helper {@code <T> void swapHelper(List<T>, ...)} can read and write the same {@code T}.
+     */
     @Test
     void testWildcardCaptureHelper() {
         final String className = 'gls.generics.test.WildcardCapture'
@@ -416,9 +565,13 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Category 4: Diamond Operator <> & Type Inference
+    // Category 4: Diamond and target-type inference (JLS 15.9.3, 18.5.2)
     // =========================================================================
 
+    /**
+     * JLS 15.9.1, 15.9.3, 18.5.2: diamond {@code <>} infers nested type arguments of
+     * {@code Map<String, List<Map<Integer, String>>>} from the assignment target.
+     */
     @Test
     void testDiamondOperatorWithNestedGenerics() {
         final String className = 'gls.generics.test.NestedDiamond'
@@ -440,6 +593,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 3)
     }
 
+    /**
+     * JLS 15.9.3, 15.9.5 (JEP 213): diamond is legal on an anonymous class
+     * {@code new Processor<>() { ... }} when the target type supplies the type arguments.
+     */
     @Test
     void testDiamondAnonymousInnerClass() {
         final String className = 'gls.generics.test.DiamondAnonymous'
@@ -461,10 +618,14 @@ final class GenericsJavaCompatibilityTest {
                 }
             }
         '''
-        // Groovy cannot use diamond <> with anonymous inner classes; keep the rest identical.
-        compileAndCompare(className, javaSrc, toGroovy(javaSrc.replace('new Processor<>()', 'new Processor<String>()')), 'Processed: Data', false)
+        assertPositive(className, javaSrc, 'Processed: Data')
     }
 
+    /**
+     * JLS 18.5.2: the assignment target of a poly invocation {@code <T> List<T> empty()}
+     * is a witness for {@code T}, so {@code List<String> s = empty()} and
+     * {@code List<Integer> i = empty()} are both well-typed.
+     */
     @Test
     void testTargetTypeInference() {
         final String className = 'gls.generics.test.TargetTypeInference'
@@ -486,9 +647,14 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Category 5: Covariance, Bridge Methods & Overriding
+    // Category 5: Covariant returns and bridges (JLS 8.4.8.3, 15.12.4.5)
     // =========================================================================
 
+    /**
+     * JLS 8.4.8.3 (covariant returns) and 15.12.4.5: {@code String get()} overrides
+     * {@code T get()} of {@code Supplier<String>}. A bridge with the erased signature
+     * is invoked through a raw {@code Supplier} and still returns the {@code String}.
+     */
     @Test
     void testCovariantReturnAndBridgeMethod() {
         final String className = 'gls.generics.test.BridgeAndCovariance'
@@ -517,9 +683,14 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Category 6: Raw Types & Interoperability
+    // Category 6: Raw types and unchecked conversion (JLS 4.8, 5.1.9)
     // =========================================================================
 
+    /**
+     * JLS 4.8, 5.1.9: assignment of a parameterized type to a raw type (and the reverse)
+     * is an unchecked conversion. The raw {@code List} may be mutated without compile-time
+     * argument checks.
+     */
     @Test
     void testRawTypeInterop() {
         final String className = 'gls.generics.test.RawTypeInterop'
@@ -542,9 +713,14 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Category 7: Generic Exceptions & Parametric Throwables
+    // Category 7: Type variables in throws (JLS 8.4.6)
     // =========================================================================
 
+    /**
+     * JLS 8.4.6 (Example 8.4.6-1): a type variable whose bound is a subtype of
+     * {@code Throwable} may appear in a {@code throws} clause, so
+     * {@code <E extends Exception> void runAction(Action<E>) throws E} is legal.
+     */
     @Test
     void testGenericThrowsClause() {
         final String className = 'gls.generics.test.GenericThrows'
@@ -579,13 +755,15 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Category 8: Strict Negative Verification (Compile-time & Runtime)
+    // Category 8: Negative well-formedness (JLS 4.5, 4.10.2, 8.1.2, 8.1.4, 8.1.5)
     // =========================================================================
 
+    /**
+     * JLS 8.1.2: it is a compile-time error if a generic class is a direct or
+     * indirect subclass of {@code Throwable}.
+     */
     @Test
     void testNegativeGenericSubclassOfThrowable() {
-        // JLS 8.1.2: a generic class may not extend Throwable. javac rejects this;
-        // static Groovy currently accepts the equivalent source, so only the Java side is asserted.
         final String className = 'gls.generics.test.GenEx'
         final String javaSrc = '''
             package gls.generics.test;
@@ -593,14 +771,13 @@ final class GenericsJavaCompatibilityTest {
                 private T info;
             }
         '''
-        JavaShell js = new JavaShell()
-        Throwable javaEx = shouldFail(JavaShellCompilationException) {
-            js.compile(className, javaSrc)
-        }
-        assert javaEx.message.contains("generic class may not extend 'java.lang.Throwable'") ||
-            javaEx.message.contains("a generic class may not extend java.lang.Throwable")
+        assertNegativeCompile(className, javaSrc, "generic class may not extend", "generic class may not extend")
     }
 
+    /**
+     * JLS 4.5: a parameterized type is well-formed only if the number of type
+     * arguments equals the number of type parameters ({@code List} has one).
+     */
     @Test
     void testNegativeTypeArityMismatch() {
         final String className = 'gls.generics.test.ArityMismatch'
@@ -614,6 +791,10 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "wrong number of type arguments", "supplied with 2 type parameters")
     }
 
+    /**
+     * JLS 4.5: the same arity rule applies wherever a parameterized type is used,
+     * including a method formal ({@code Map} has two type parameters).
+     */
     @Test
     void testNegativeMethodParamArityMismatch() {
         final String className = 'gls.generics.test.MethodArityMismatch'
@@ -627,6 +808,10 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "wrong number of type arguments", "supplied with 1 type parameter")
     }
 
+    /**
+     * JLS 4.10.2: parameterized types are invariant in their type arguments.
+     * {@code ArrayList<Integer>} is not a subtype of {@code List<Number>}.
+     */
     @Test
     void testNegativeGenericInvarianceAssignment() {
         final String className = 'gls.generics.test.InvarianceFail'
@@ -642,6 +827,11 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "incompatible types", "Cannot assign")
     }
 
+    /**
+     * JLS 4.5, 4.4: a type argument must be a subtype of every type in the
+     * corresponding bound after substitution. {@code Object} is not a
+     * {@code Number & Comparable<Object>}.
+     */
     @Test
     void testNegativeMultipleBoundsMismatch() {
         final String className = 'gls.generics.test.MultiBoundFail'
@@ -657,6 +847,10 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "not within bounds", "not a valid substitute for the bounded parameter")
     }
 
+    /**
+     * JLS 8.1.4 (and 8.1.5): if a superclass or superinterface type has type
+     * arguments, none of those arguments may be wildcards.
+     */
     @Test
     void testNegativeWildcardInExtendsClause() {
         final String className = 'gls.generics.test.WildcardSuper'
@@ -669,6 +863,26 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "unexpected type", "A supertype may not specify a wildcard type")
     }
 
+    /**
+     * JLS 8.1.5: a superinterface type may not specify a wildcard
+     * ({@code implements List<?>}).
+     */
+    @Test
+    void testNegativeWildcardInImplementsClause() {
+        final String className = 'gls.generics.test.WildcardImplements'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.List;
+            public class WildcardImplements implements List<?> {
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "unexpected type", "A supertype may not specify a wildcard type")
+    }
+
+    /**
+     * JLS 4.5: a parameterized type {@code C<T1,...,Tn>} is well-formed only if
+     * {@code C} names a generic class or interface. {@code Date} takes no type parameters.
+     */
     @Test
     void testNegativeParametricTypeForNonGenericClass() {
         final String className = 'gls.generics.test.NonGenericParameterized'
@@ -684,6 +898,11 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "does not take parameters", "which takes no parameters")
     }
 
+    /**
+     * JLS 8.1.5 (Example 8.1.5-3): a class may not implement two different
+     * parameterizations of the same generic interface (needed for translation by
+     * erasure, JLS 4.6).
+     */
     @Test
     void testNegativeDuplicateGenericInterfaceInheritance() {
         final String className = 'gls.generics.test.InterfaceConflict'
@@ -697,6 +916,10 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "different arguments", "Duplicate interfaces in implements list")
     }
 
+    /**
+     * JLS 5.3, 4.5.2: invocation of {@code List<String>#add} requires an argument
+     * compatible with the substituted element type {@code String}.
+     */
     @Test
     void testNegativeMethodArgumentTypeMismatch() {
         final String className = 'gls.generics.test.CallMismatch'
@@ -713,6 +936,11 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "incompatible types", "Cannot call")
     }
 
+    /**
+     * JLS 4.8, 4.12.2, 5.1.9: heap pollution through a raw {@code Sink} makes a later
+     * use of the erased {@code accept(T)} throw {@code ClassCastException} at the
+     * compiler-inserted cast (JLS 15.5).
+     */
     @Test
     void testNegativeHeapPollutionClassCastException() {
         final String className = 'gls.generics.test.HeapPollution'
@@ -742,9 +970,13 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Category 9: Inheritance, enclosing types, and bound forwarding
+    // Category 9: Inheritance, enclosing types and bound forwarding (JLS 8.1.3–8.1.5, 4.5)
     // =========================================================================
 
+    /**
+     * JLS 8.1.4, 4.5.2: a non-generic subclass may extend a parameterization
+     * ({@code Named extends Holder<String>}); inherited members have the substituted types.
+     */
     @Test
     void testGenericClassExtendsParameterizedType() {
         final String className = 'gls.generics.test.ExtendsParameterized'
@@ -768,6 +1000,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'ok', true)
     }
 
+    /**
+     * JLS 8.1.2, 4.5.2: a subclass may forward its type parameter to a generic
+     * superclass ({@code Box<T> extends Holder<T>}).
+     */
     @Test
     void testTypeParameterForwardedToSuperclass() {
         final String className = 'gls.generics.test.ForwardTypeParam'
@@ -791,6 +1027,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'fwd', true)
     }
 
+    /**
+     * JLS 9.1.2, 9.1.3: a generic interface may extend a parameterization of
+     * another generic interface ({@code SrcNum extends Src<Number>}).
+     */
     @Test
     void testGenericInterfaceExtendsGenericInterface() {
         final String className = 'gls.generics.test.GenericIfaceExtends'
@@ -815,6 +1055,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 7, true)
     }
 
+    /**
+     * JLS 8.1.3, 6.5.5.1: a non-static inner class is not a static context, so it
+     * may refer to a type parameter of the enclosing generic class.
+     */
     @Test
     void testNonStaticInnerClassUsesEnclosingTypeParameter() {
         final String className = 'gls.generics.test.EnclosingTypeParam'
@@ -836,6 +1080,116 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'inner')
     }
 
+    /**
+     * JLS 4.5: a nested parameterization such as {@code Outer<String>.Inner<Integer>}
+     * (a "rare" type) is well-formed when both the enclosing type and the member
+     * type are generic.
+     */
+    @Test
+    void testRareTypeQualifiedInnerClass() {
+        final String className = 'gls.generics.test.RareTypeInner'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class RareTypeInner {
+                public static class Outer<T> {
+                    public class Inner<U> {
+                        private final U u;
+                        Inner(U u) { this.u = u; }
+                        public U getU() { return u; }
+                    }
+                    public Inner<Integer> make(Integer u) { return new Inner<Integer>(u); }
+                }
+                public static String test() {
+                    Outer<String> outer = new Outer<String>();
+                    Outer<String>.Inner<Integer> inner = outer.make(Integer.valueOf(7));
+                    return String.valueOf(inner.getU());
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, '7')
+    }
+
+    /**
+     * JLS 4.5, 4.11: a rare type may appear as a field type and a method parameter
+     * or return type; the Signature attribute records the enclosing type arguments.
+     */
+    @Test
+    void testRareTypeFieldAndMethodSignatures() {
+        final String className = 'gls.generics.test.RareTypeSignatures'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class RareTypeSignatures {
+                public static class Outer<T> {
+                    public class Inner<U> {
+                        public U u;
+                    }
+                }
+                public Outer<String>.Inner<Integer> field;
+                public Outer<String>.Inner<Integer> echo(Outer<String>.Inner<Integer> p) { return p; }
+                public static String test() { return "ok"; }
+            }
+        '''
+        assertPositive(className, javaSrc, 'ok', true)
+    }
+
+    /**
+     * JLS 4.5: {@code A<String>.B.C<Integer>} is a parameterized type even though
+     * the middle member {@code B} is not generic, because type arguments appear
+     * in the enclosing type.
+     */
+    @Test
+    void testRareTypeUnparameterizedMiddleSignatures() {
+        final String className = 'gls.generics.test.RareTypeMiddle'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class RareTypeMiddle {
+                public static class A<T> {
+                    public class B {
+                        public class C<U> {
+                            public U u;
+                        }
+                    }
+                }
+                public A<String>.B.C<Integer> field;
+                public static String test() { return "ok"; }
+            }
+        '''
+        assertPositive(className, javaSrc, 'ok', true)
+    }
+
+    /**
+     * JLS 15.9, 8.1.3: a qualified class instance creation
+     * {@code new Outer<T>.Inner<Integer>(...)} instantiates the inner class of the
+     * enclosing parameterization.
+     */
+    @Test
+    void testRareTypeInstanceCreationInsideOuter() {
+        final String className = 'gls.generics.test.RareTypeNew'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class RareTypeNew {
+                public static class Outer<T> {
+                    public class Inner<U> {
+                        private final U u;
+                        Inner(U u) { this.u = u; }
+                        public U getU() { return u; }
+                    }
+                    public Inner<Integer> make() {
+                        return new Outer<T>.Inner<Integer>(Integer.valueOf(7));
+                    }
+                }
+                public static String test() {
+                    return String.valueOf(new Outer<String>().make().getU());
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, '7')
+    }
+
+    /**
+     * JLS 8.1.1.4, 8.1.2: a {@code static} nested class has no enclosing instance
+     * and must not depend on the outer class's type parameters; it may declare its own.
+     */
     @Test
     void testStaticNestedClassHasIndependentTypeParameters() {
         final String className = 'gls.generics.test.StaticNestedIndependent'
@@ -858,6 +1212,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, '42', true)
     }
 
+    /**
+     * JLS 4.4: a type-variable bound may itself be a type variable
+     * ({@code U extends T}). {@code T} is inferred as a supertype of the argument.
+     */
     @Test
     void testTypeParameterUsedAsBoundOfAnother() {
         final String className = 'gls.generics.test.TypeParamAsBound'
@@ -876,6 +1234,11 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'bound')
     }
 
+    /**
+     * JLS 8.1.2 (Example 8.1.2-1): type parameters in one section may mention each
+     * other, including a later parameter ({@code T extends List<X>, X extends Number}).
+     * A parameter may not depend on itself.
+     */
     @Test
     void testForwardReferencedTypeParameterBounds() {
         final String className = 'gls.generics.test.ForwardRefBounds'
@@ -900,6 +1263,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 9.0d, true)
     }
 
+    /**
+     * JLS 4.4: an F-bound {@code T extends Entity<T>} encodes a self type so
+     * {@code fluent()} can return the concrete subclass rather than the raw base.
+     */
     @Test
     void testFBoundedSelfType() {
         final String className = 'gls.generics.test.FBoundedSelfType'
@@ -923,6 +1290,11 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'user', true)
     }
 
+    /**
+     * JLS 4.4 (Example 4.4-1), 4.9: the members of {@code T extends Number & Comparable<T>}
+     * are the members of that intersection, so both {@code intValue()} and
+     * {@code compareTo} are applicable.
+     */
     @Test
     void testIntersectionBoundUsedAsBothTypes() {
         final String className = 'gls.generics.test.IntersectionDispatch'
@@ -940,6 +1312,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'Integer:5:0')
     }
 
+    /**
+     * JLS 4.4, 8.1.2: independent type parameters may each carry their own bound
+     * ({@code A extends CharSequence, B extends Number}).
+     */
     @Test
     void testIndependentBoundedTypeParameters() {
         final String className = 'gls.generics.test.IndependentBoundedParams'
@@ -961,9 +1337,13 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Category 10: Generic methods on generic types, arrays as type arguments
+    // Category 10: Generic methods of generic types; arrays as type arguments (JLS 8.4.4, 4.5.1)
     // =========================================================================
 
+    /**
+     * JLS 8.4.4: a generic method of a generic class introduces method type
+     * parameters distinct from the class's ({@code Box<T>#<U> U map(Function<T, U>)}).
+     */
     @Test
     void testGenericMethodOnGenericClass() {
         final String className = 'gls.generics.test.GenericMethodOnGenericClass'
@@ -987,6 +1367,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 4)
     }
 
+    /**
+     * JLS 4.5.1: a type argument is a reference type or a wildcard. An array type
+     * such as {@code String[]} is a reference type, so {@code List<String[]>} is well-formed.
+     */
     @Test
     void testArrayAsTypeArgument() {
         final String className = 'gls.generics.test.ArrayAsTypeArg'
@@ -1005,6 +1389,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 2)
     }
 
+    /**
+     * JLS 18.5.2: nested poly invocations {@code id(id("ok"))} constrain the inner
+     * and outer inference variables from the same target type.
+     */
     @Test
     void testChainedGenericMethodInference() {
         final String className = 'gls.generics.test.ChainedInference'
@@ -1022,6 +1410,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'ok')
     }
 
+    /**
+     * JLS 8.4.2, 8.4.8.1: a generic method overrides another when the signatures are
+     * override-equivalent, including after renaming type parameters with matching bounds.
+     */
     @Test
     void testOverrideGenericMethodWithMatchingBounds() {
         final String className = 'gls.generics.test.OverrideMatchingBounds'
@@ -1047,9 +1439,13 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Category 11: PECS, nested wildcards, function variance
+    // Category 11: PECS, nested wildcards and function variance (JLS 4.5.1, 4.10.2)
     // =========================================================================
 
+    /**
+     * JLS 4.5.1 containment and 4.10.2: {@code Collections.copy(List<? super T>, List<? extends T>)}
+     * is the PECS form of a copy — destination is a consumer, source is a producer.
+     */
     @Test
     void testPecsCollectionsCopy() {
         final String className = 'gls.generics.test.PecsCopy'
@@ -1074,6 +1470,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, '1,3')
     }
 
+    /**
+     * JLS 4.5.1: wildcards nest. {@code List<? extends List<? extends Number>>} contains
+     * {@code List<List<Integer>>} by applying containment recursively.
+     */
     @Test
     void testNestedWildcards() {
         final String className = 'gls.generics.test.NestedWildcards'
@@ -1101,6 +1501,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 3)
     }
 
+    /**
+     * JLS 4.5.1: {@code List<Integer>#sort} accepts {@code Comparator<? super Integer>},
+     * so a {@code Comparator<Number>} may order a list of {@code Integer}.
+     */
     @Test
     void testComparatorSuperBoundSort() {
         final String className = 'gls.generics.test.ComparatorSuperBound'
@@ -1124,6 +1528,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, '1,2,3')
     }
 
+    /**
+     * JLS 4.5.1, 9.9: {@code Function<? super T, ? extends R>} is the flexible function
+     * type — argument consumer, result producer — used by {@code apply}.
+     */
     @Test
     void testFunctionWildcardVariance() {
         final String className = 'gls.generics.test.FunctionVariance'
@@ -1143,6 +1551,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 3)
     }
 
+    /**
+     * JLS 4.5.2, 5.1.10: capture of {@code List<? super Integer>} yields an unknown
+     * supertype of {@code Integer}; reading an element therefore has type {@code Object}.
+     */
     @Test
     void testLowerBoundedWildcardGetReturnsObject() {
         final String className = 'gls.generics.test.LowerBoundGet'
@@ -1162,6 +1574,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, '8')
     }
 
+    /**
+     * JLS 4.5.1, 5.2: the null reference is assignment-compatible with every reference
+     * type, so {@code list.add(null)} is legal even on {@code List<?>}.
+     */
     @Test
     void testWildcardAllowsAddingNull() {
         final String className = 'gls.generics.test.WildcardAddNull'
@@ -1181,9 +1597,13 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Category 12: Diamond target typing, unchecked conversion, raw assignment
+    // Category 12: Invocation-context diamond, unchecked casts, nested members (JLS 15.9.3, 5.1.9, 4.5)
     // =========================================================================
 
+    /**
+     * JLS 15.9.3, 18.5.2, 5.3: the invocation context of {@code len(List<String>)} is a
+     * target-type witness for {@code new ArrayList<>()}.
+     */
     @Test
     void testDiamondInferredFromMethodArgument() {
         final String className = 'gls.generics.test.DiamondMethodArg'
@@ -1203,6 +1623,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 0)
     }
 
+    /**
+     * JLS 5.5, 5.1.9: a cast to a non-reifiable parameterized type is unchecked.
+     * The value is then used as {@code List<String>} at compile time.
+     */
     @Test
     void testUncheckedCastToParameterizedType() {
         final String className = 'gls.generics.test.UncheckedCast'
@@ -1222,6 +1646,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'cast')
     }
 
+    /**
+     * JLS 5.1.9, 4.8: assigning a raw type to a parameterization is an unchecked
+     * conversion; subsequent uses see the parameterized type.
+     */
     @Test
     void testRawTypeAssignedToParameterizedType() {
         final String className = 'gls.generics.test.RawToParameterized'
@@ -1242,6 +1670,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 1)
     }
 
+    /**
+     * JLS 4.5, 9.5: {@code Map.Entry<K, V>} is a member interface of a generic type.
+     * {@code Map.Entry<String, Integer>} is a parameterization of that member.
+     */
     @Test
     void testMapEntryParameterizedInnerInterface() {
         final String className = 'gls.generics.test.MapEntryInner'
@@ -1264,9 +1696,13 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Category 13: Covariant override, conditional lub, records, instanceof
+    // Category 13: Covariant override, lub, generic records, reifiable instanceof (JLS 8.4.8.3, 4.10.4, 8.10, 4.7)
     // =========================================================================
 
+    /**
+     * JLS 8.4.8.3: {@code String id(String)} in {@code Child extends Parent<String>}
+     * is return-type-substitutable for {@code T id(T)} and overrides it.
+     */
     @Test
     void testCovariantOverrideInGenericClassHierarchy() {
         final String className = 'gls.generics.test.CovariantClassOverride'
@@ -1289,6 +1725,11 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'ok!', true)
     }
 
+    /**
+     * JLS 15.25.3, 4.10.4: a reference conditional expression has the lub of its
+     * operand types ({@code Class<?>} for class literals, {@code CharSequence} for
+     * {@code String} and {@code StringBuilder}).
+     */
     @Test
     void testConditionalExpressionLubsClassLiterals() {
         final String className = 'gls.generics.test.ConditionalLub'
@@ -1306,6 +1747,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'Integerhello')
     }
 
+    /**
+     * JLS 8.10, 8.1.2: a record class may be generic. Canonical accessors and the
+     * compact constructor use the substituted component types.
+     */
     @Test
     void testGenericRecord() {
         final String className = 'gls.generics.test.GenericRecordPair'
@@ -1323,6 +1768,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 'a1', true)
     }
 
+    /**
+     * JLS 4.7, 15.20.2: {@code List<?>} and {@code Class<?>} are reifiable (unbounded
+     * wildcards), so they may appear as the {@code instanceof} reference type.
+     */
     @Test
     void testReifiableInstanceofUnboundedWildcard() {
         final String className = 'gls.generics.test.ReifiableInstanceof'
@@ -1341,6 +1790,11 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, true)
     }
 
+    /**
+     * JLS 4.4, 4.5.1, 8.4.4, 13.1: the reflective Signature of type parameters,
+     * bounds, wildcards, generic methods and generic superclasses must match
+     * between javac and static Groovy.
+     */
     @Test
     void testDeclaredGenericSignaturesMatch() {
         final String className = 'gls.generics.test.SignatureCatalog'
@@ -1381,9 +1835,13 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Category 14: Additional negative cases (JLS restrictions both sides share)
+    // Category 14: Additional negatives both compilers share (JLS 4.7, 6.5.5.1, 4.5.1)
     // =========================================================================
 
+    /**
+     * JLS 4.7, 15.20.2: {@code List<String>} is not reifiable, so
+     * {@code o instanceof List<String>} is a compile-time error.
+     */
     @Test
     void testNegativeNonReifiableInstanceof() {
         final String className = 'gls.generics.test.NonReifiableInstanceof'
@@ -1399,6 +1857,10 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "cannot be safely cast", "Cannot perform instanceof")
     }
 
+    /**
+     * JLS 4.7, 15.20.2: {@code Class<C>} is not reifiable (the type argument is not
+     * an unbounded wildcard), so it may not be used with {@code instanceof}.
+     */
     @Test
     void testNegativeInstanceofParameterizedClassLiteral() {
         final String className = 'gls.generics.test.InstanceofClassParam'
@@ -1413,6 +1875,10 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "cannot be converted", "Cannot perform instanceof")
     }
 
+    /**
+     * JLS 6.5.5.1, 8.1.2: a type parameter of the enclosing class is not in scope
+     * from a static nested class.
+     */
     @Test
     void testNegativeTypeParameterReferencedFromStaticNestedClass() {
         final String className = 'gls.generics.test.StaticNestedOuterTp'
@@ -1427,6 +1893,10 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "non-static type variable", "unable to resolve class T")
     }
 
+    /**
+     * JLS 8.3.1.1, 6.5.5.1: a {@code static} field is a static context and may not
+     * refer to a type parameter of the enclosing class.
+     */
     @Test
     void testNegativeTypeParameterStaticField() {
         final String className = 'gls.generics.test.StaticTypeParamField'
@@ -1439,6 +1909,11 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "non-static type variable", "unable to resolve class T")
     }
 
+    /**
+     * JLS 4.5.1, 5.1.10: after capture, the element type of {@code List<? extends Number>}
+     * is an unknown subtype of {@code Number}; {@code Integer} is not a subtype of that
+     * capture, so {@code add} is rejected.
+     */
     @Test
     void testNegativeWildcardWriteThroughExtends() {
         final String className = 'gls.generics.test.WildcardWriteExtends'
@@ -1455,28 +1930,954 @@ final class GenericsJavaCompatibilityTest {
     }
 
     // =========================================================================
-    // Test Harness Helper Methods
+    // Category 15: Erasure, reifiable arrays, static members, remaining JLS restrictions
     // =========================================================================
 
     /**
-     * Static Groovy view of {@code javaSrc}: Java annotation array initializers become
-     * Groovy lists. Semicolons and the rest of the Java syntax are kept. CompileStatic
-     * is applied by {@link #newStaticGroovyClassLoader()}, not by rewriting the source.
+     * JLS 4.6, 8.1.2: the erasure of {@code G<T1,...,Tn>} is {@code |G|}. All
+     * parameterizations of a generic class therefore share one run-time class.
      */
-    private static String toGroovy(String javaSrc) {
-        javaSrc.replaceAll(/@SuppressWarnings\(\{([^}]*)\}\)/, '@SuppressWarnings([$1])')
+    @Test
+    void testErasureSharesRuntimeClass() {
+        final String className = 'gls.generics.test.ErasureRuntimeClass'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class ErasureRuntimeClass {
+                public static class Box<T> {
+                    public final T value;
+                    public Box(T value) { this.value = value; }
+                }
+                public static boolean test() {
+                    Box<String> strings = new Box<>("a");
+                    Box<Integer> ints = new Box<>(Integer.valueOf(1));
+                    return strings.getClass() == ints.getClass();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, true, true)
     }
 
     /**
-     * Groovy class loader that always compiles with {@link CompileStatic}.
-     * Dynamic Groovy is not part of this suite.
+     * JLS 4.5.1 containment and 4.10.2: {@code List<Integer>} is a subtype of
+     * {@code List<? extends Number>} even though it is not a subtype of
+     * {@code List<Number>} (invariance is covered by
+     * {@link #testNegativeGenericInvarianceAssignment()}).
      */
-    private static GroovyClassLoader newStaticGroovyClassLoader() {
-        CompilerConfiguration config = new CompilerConfiguration()
-        config.addCompilationCustomizers(new ASTTransformationCustomizer(CompileStatic))
-        new GroovyClassLoader(GenericsJavaCompatibilityTest.classLoader, config)
-    }
-
+    @Test
+    void testWildcardContainmentAssignment() {
+        final String className = 'gls.generics.test.WildcardContainment'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.ArrayList;
+            import java.util.List;
+            public class WildcardContainment {
+                public static int test() {
+                    List<Integer> ints = new ArrayList<Integer>();
+                    ints.add(Integer.valueOf(4));
+                    List<? extends Number> nums = ints;
+                    List<?> any = ints;
+                    return nums.get(0).intValue() + any.size();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 5)
+    }
+
+    /**
+     * JLS 15.10.1, 4.7: an array creation is legal when the component type is
+     * reifiable. {@code List<?>} is reifiable, so {@code new List<?>[n]} is allowed.
+     */
+    @Test
+    void testReifiableGenericArrayCreation() {
+        final String className = 'gls.generics.test.ReifiableArrayCreation'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.ArrayList;
+            import java.util.List;
+            public class ReifiableArrayCreation {
+                public static int test() {
+                    List<?>[] lists = new List<?>[1];
+                    lists[0] = new ArrayList<String>();
+                    return lists.length;
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 1)
+    }
+
+    /**
+     * JLS 4.8: the type of a member of a raw type is the erasure of that member
+     * as declared. {@code Box.get()} therefore has return type {@code Object}.
+     */
+    @Test
+    void testRawTypeMemberErasure() {
+        final String className = 'gls.generics.test.RawMemberErasure'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class RawMemberErasure {
+                public static class Box<T> {
+                    private final T value;
+                    public Box(T value) { this.value = value; }
+                    public T get() { return value; }
+                }
+                @SuppressWarnings({"rawtypes", "unchecked"})
+                public static String test() {
+                    Box raw = new Box("raw");
+                    Object value = raw.get();
+                    return value.getClass().getSimpleName();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'String')
+    }
+
+    /**
+     * JLS 4.5.2: a static member of a generic type must be referred to using the
+     * generic type name, not a parameterization ({@code Cell.id()}, not
+     * {@code Cell<String>.id()}).
+     */
+    @Test
+    void testStaticMemberViaGenericTypeName() {
+        final String className = 'gls.generics.test.StaticViaGenericName'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class StaticViaGenericName {
+                public static class Cell<T> {
+                    public static String id() { return "cell"; }
+                }
+                public static String test() {
+                    return Cell.id();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'cell', true)
+    }
+
+    /**
+     * JLS 15.8.3: in a generic class {@code C<F1,...,Fn>}, the type of {@code this}
+     * is {@code C<F1,...,Fn>}, so a method may return {@code this} at that type.
+     */
+    @Test
+    void testThisTypeInGenericClass() {
+        final String className = 'gls.generics.test.GenericThisType'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class GenericThisType {
+                public static class Box<T> {
+                    public T value;
+                    public Box(T value) { this.value = value; }
+                    public Box<T> self() { return this; }
+                }
+                public static String test() {
+                    return new Box<String>("me").self().value;
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'me', true)
+    }
+
+    /**
+     * JLS 15.9.3, 18.5.2: a diamond class instance creation in a return context
+     * takes the method result type as its target type.
+     */
+    @Test
+    void testDiamondInferredFromReturnType() {
+        final String className = 'gls.generics.test.DiamondReturn'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.ArrayList;
+            import java.util.List;
+            public class DiamondReturn {
+                public static List<String> empty() {
+                    return new ArrayList<>();
+                }
+                public static int test() {
+                    return empty().size();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 0)
+    }
+
+    /**
+     * JLS 4.5: primitive types cannot be type arguments ({@code List<int>} is
+     * not well-formed). The corresponding boxed reference type must be used.
+     */
+    @Test
+    void testNegativePrimitiveTypeArgument() {
+        final String className = 'gls.generics.test.PrimitiveTypeArg'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.List;
+            public class PrimitiveTypeArg {
+                public List<int> list;
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "unexpected type", "primitive type")
+    }
+
+    /**
+     * JLS 15.10.1, 4.7: it is a compile-time error to create an array whose
+     * component type is not reifiable ({@code new List<String>[n]}).
+     */
+    @Test
+    void testNegativeNonReifiableArrayCreation() {
+        final String className = 'gls.generics.test.GenericArrayCreation'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.List;
+            public class GenericArrayCreation {
+                public static Object test() {
+                    return new List<String>[1];
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "generic array creation", "generic array creation")
+    }
+
+    /**
+     * JLS 15.10.1, 4.7: a parameterized type is reifiable only when every type
+     * argument is an unbounded wildcard. {@code List<? extends Number>} is not
+     * reifiable, so {@code new List<? extends Number>[n]} is a compile-time error.
+     * The legal counterpart is {@link #testReifiableGenericArrayCreation()}.
+     */
+    @Test
+    void testNegativeBoundedWildcardArrayCreation() {
+        final String className = 'gls.generics.test.BoundedWildcardArrayCreation'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.List;
+            public class BoundedWildcardArrayCreation {
+                public static Object test() {
+                    return new List<? extends Number>[1];
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "generic array creation", "generic array creation")
+    }
+
+    /**
+     * JLS 4.7, 15.10.1: {@code Outer<?>} is reifiable and a non-static member
+     * type of a reifiable type is reifiable, so {@code new Outer<?>.Inner[n]}
+     * is a legal array creation. javac 25 accepts this; Groovy must as well.
+     */
+    @Test
+    void testReifiableRareTypeArrayCreation() {
+        final String className = 'gls.generics.test.RareTypeReifiableArray'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class RareTypeReifiableArray {
+                public static class Outer<T> {
+                    public class Inner {}
+                }
+                public static int test() {
+                    Outer<?>.Inner[] arr = new Outer<?>.Inner[0];
+                    return arr.length;
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 0)
+    }
+
+    /**
+     * JLS 4.7, 15.10.1: the generic-member counterpart is also reifiable when
+     * every type argument is an unbounded wildcard
+     * ({@code new Outer<?>.InnerG<?>[n]}).
+     */
+    @Test
+    void testReifiableRareTypeGenericInnerArrayCreation() {
+        final String className = 'gls.generics.test.RareTypeReifiableGenericInnerArray'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class RareTypeReifiableGenericInnerArray {
+                public static class Outer<T> {
+                    public class InnerG<U> {}
+                }
+                public static int test() {
+                    Outer<?>.InnerG<?>[] arr = new Outer<?>.InnerG<?>[0];
+                    return arr.length;
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 0)
+    }
+
+    /**
+     * JLS 4.5.2, 6.5.5: a static member type may not be selected from a
+     * parameterized type ({@code new Outer<?>.Nested[n]}).
+     */
+    @Test
+    void testNegativeStaticNestedTypeViaParameterizedEnclosing() {
+        final String className = 'gls.generics.test.StaticNestedViaParameterized'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class StaticNestedViaParameterized {
+                public static class Outer<T> {
+                    public static class Nested {}
+                }
+                public static Object test() {
+                    return new Outer<?>.Nested[0];
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "cannot select a static class from a parameterized type", "parameterization")
+    }
+
+    /**
+     * JLS 9.5, 6.5.5: a nested interface is implicitly static, so
+     * {@code implements Outer<String>.Inner} is the same error as
+     * {@code new Outer<?>.Nested[n]}.
+     */
+    @Test
+    void testNegativeImplementsStaticNestedViaParameterizedEnclosing() {
+        final String className = 'gls.generics.test.ImplementsStaticNestedViaParameterized'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class ImplementsStaticNestedViaParameterized {
+                public static class Outer<T> {
+                    public interface Inner<U> {
+                        U id(U u);
+                    }
+                }
+                public static class Impl implements Outer<String>.Inner<Integer> {
+                    public Integer id(Integer u) { return u; }
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "cannot select a static class from a parameterized type", "parameterization")
+    }
+
+    /**
+     * JLS 9.5, 6.5.5: {@code Map.Entry} is a nested interface, so
+     * {@code new Map<?,?>.Entry[n]} cannot select it from a parameterization.
+     */
+    @Test
+    void testNegativeMapEntryViaParameterizedEnclosing() {
+        final String className = 'gls.generics.test.MapEntryViaParameterized'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.Map;
+            public class MapEntryViaParameterized {
+                public static Object test() {
+                    return new Map<?,?>.Entry[0];
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "cannot select a static class from a parameterized type", "parameterization")
+    }
+
+    /**
+     * A generic member type used raw under a parameterized enclosing type is
+     * malformed ({@code new Outer<?>.InnerG[n]}).
+     */
+    @Test
+    void testNegativeRawGenericMemberOfParameterizedEnclosing() {
+        final String className = 'gls.generics.test.RawMemberOfParameterized'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class RawMemberOfParameterized {
+                public static class Outer<T> {
+                    public class InnerG<U> {}
+                }
+                public static Object test() {
+                    return new Outer<?>.InnerG[0];
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "improperly formed type", "parameterized enclosing type")
+    }
+
+    /**
+     * JLS 15.9.1: a class instance creation may not instantiate a type variable
+     * ({@code new T()}).
+     */
+    @Test
+    void testNegativeInstantiateTypeVariable() {
+        final String className = 'gls.generics.test.NewTypeVariable'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class NewTypeVariable {
+                public static <T> T make() {
+                    return new T();
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "type parameter", "type parameter")
+    }
+
+    /**
+     * JLS 15.8.2: a class literal's {@code TypeName} may not denote a type
+     * variable ({@code T.class}).
+     */
+    @Test
+    void testNegativeTypeVariableClassLiteral() {
+        final String className = 'gls.generics.test.TypeVarClassLiteral'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class TypeVarClassLiteral {
+                public static <T> Class<T> token() {
+                    return T.class;
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "cannot select from a type variable", "type parameter")
+    }
+
+    /**
+     * JLS 4.5.2: it is illegal to refer to a static member of a generic type
+     * through a parameterization ({@code Cell<String>.id()}).
+     */
+    @Test
+    void testNegativeStaticMemberViaParameterizedType() {
+        final String className = 'gls.generics.test.StaticViaParameterized'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class StaticViaParameterized {
+                public static class Cell<T> {
+                    public static String id() { return "cell"; }
+                }
+                public static String test() {
+                    return Cell<String>.id();
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "illegal start of type", "parameterization")
+    }
+
+    /**
+     * JLS 8.4.2, 8.4.8.3 (Example 8.4.8.3-4): two methods of a class may not
+     * have override-equivalent signatures after erasure
+     * ({@code m(List<String>)} and {@code m(List<Integer>)} both erase to {@code m(List)}).
+     */
+    @Test
+    void testNegativeSameErasureOverload() {
+        final String className = 'gls.generics.test.SameErasureOverload'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.List;
+            public class SameErasureOverload {
+                public void m(List<String> a) {}
+                public void m(List<Integer> a) {}
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "erasure", "duplicates another method")
+    }
+
+    /**
+     * JLS 15.9: a wildcard may not appear after {@code new} in a class instance
+     * creation expression ({@code new ArrayList<?>()}).
+     */
+    @Test
+    void testNegativeWildcardAfterNew() {
+        final String className = 'gls.generics.test.WildcardAfterNew'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.ArrayList;
+            public class WildcardAfterNew {
+                public static Object test() {
+                    return new ArrayList<?>();
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "unexpected type", "wildcard type")
+    }
+
+    /**
+     * JLS 15.9: diamond {@code <>} may not be combined with explicit constructor
+     * type arguments.
+     */
+    @Test
+    void testNegativeDiamondAndConstructorTypeArguments() {
+        final String className = 'gls.generics.test.DiamondAndCtorArgs'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class DiamondAndCtorArgs {
+                public static class Box<T> {
+                    public final T value;
+                    public <U> Box(T value, U extra) { this.value = value; }
+                }
+                public static Object test() {
+                    return new <Integer>Box<>("x", Integer.valueOf(1));
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "explicit type parameters for constructor", "Cannot use diamond")
+    }
+
+    /**
+     * JLS 8.9: an enum class may not declare type parameters.
+     */
+    @Test
+    void testNegativeGenericEnum() {
+        final String className = 'gls.generics.test.GenericEnum'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class GenericEnum {
+                public enum Kind<T> { A }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "enum", "enum")
+    }
+
+    /**
+     * JLS 4.4: a class type or type variable may appear only as the first type
+     * of a bound; additional bounds must be interfaces
+     * ({@code T extends Comparable<T> & Number} is illegal).
+     */
+    @Test
+    void testNegativeClassTypeNotFirstInBound() {
+        final String className = 'gls.generics.test.ClassBoundNotFirst'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class ClassBoundNotFirst {
+                public static <T extends Comparable<T> & Number> T id(T value) {
+                    return value;
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "interface", "interface")
+    }
+
+    /**
+     * JLS 15.10.1: the component type of an array creation may not be a type
+     * variable ({@code new T[n]}).
+     */
+    @Test
+    void testNegativeTypeParameterArrayCreation() {
+        final String className = 'gls.generics.test.TypeParamArray'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class TypeParamArray {
+                public static <T> T[] make(int n) {
+                    return new T[n];
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "generic array creation", "generic array creation")
+    }
+
+    // =========================================================================
+    // Category 16: JCK four-dimension coverage (Java SE 25)
+    //   1. erasure and bridges    2. multi-parameter / nested syntax
+    //   3. Signature reflection   4. raw-type backward compatibility
+    // =========================================================================
+
+    /**
+     * JLS 4.6: the erasure of an unbounded type variable is {@code Object};
+     * the erasure of {@code T extends Number} is {@code Number}. The generic
+     * return type remains a {@link TypeVariable}.
+     */
+    @Test
+    void testErasureOfUnboundedAndBoundedTypeVariables() {
+        final String className = 'gls.generics.test.ErasureOfTypeVariables'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.lang.reflect.Method;
+            import java.lang.reflect.TypeVariable;
+            public class ErasureOfTypeVariables {
+                public static class Box<T> {
+                    public T id(T t) { return t; }
+                }
+                public static class NumBox<T extends Number> {
+                    public T id(T t) { return t; }
+                }
+                public static String test() throws Exception {
+                    Method boxId = Box.class.getMethod("id", Object.class);
+                    Method numId = NumBox.class.getMethod("id", Number.class);
+                    boolean boxErasure = boxId.getReturnType() == Object.class
+                            && boxId.getGenericReturnType() instanceof TypeVariable;
+                    boolean numErasure = numId.getReturnType() == Number.class
+                            && numId.getGenericReturnType() instanceof TypeVariable;
+                    return (boxErasure && numErasure) ? "ok" : "fail";
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'ok', true)
+    }
+
+    /**
+     * JLS 8.4.8.3, 15.12.4.5: a covariant override of {@code T get()} as
+     * {@code String get()} is compiled with a bridge {@code Object get()} so a
+     * raw or erased invoke still reaches the specialized method.
+     */
+    @Test
+    void testCovariantOverrideDeclaresBridgeMethod() {
+        final String className = 'gls.generics.test.CovariantBridgePresent'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.lang.reflect.Method;
+            public class CovariantBridgePresent {
+                public interface Supplier<T> {
+                    T get();
+                }
+                public static class StringSupplier implements Supplier<String> {
+                    @Override
+                    public String get() { return "bridged"; }
+                }
+                public static String test() {
+                    boolean bridge = false;
+                    boolean specialized = false;
+                    for (Method m : StringSupplier.class.getDeclaredMethods()) {
+                        if (!"get".equals(m.getName()) || m.getParameterCount() != 0) {
+                            continue;
+                        }
+                        if (m.isBridge() && m.getReturnType() == Object.class) {
+                            bridge = true;
+                        }
+                        if (!m.isBridge() && m.getReturnType() == String.class) {
+                            specialized = true;
+                        }
+                    }
+                    Supplier raw = new StringSupplier();
+                    Object viaErased = raw.get();
+                    return (bridge && specialized && "bridged".equals(viaErased)) ? "ok" : "fail";
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'ok')
+    }
+
+    /**
+     * JLS 8.1.2: three independent class type parameters {@code Triple<A, B, C>}
+     * are substituted together; diamond infers all three from the constructor
+     * arguments and the assignment target.
+     */
+    @Test
+    void testTripleTypeParameters() {
+        final String className = 'gls.generics.test.TripleTypeParams'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class TripleTypeParams {
+                public static class Triple<A, B, C> {
+                    public final A a;
+                    public final B b;
+                    public final C c;
+                    public Triple(A a, B b, C c) {
+                        this.a = a;
+                        this.b = b;
+                        this.c = c;
+                    }
+                }
+                public static String test() {
+                    Triple<String, Integer, Double> t = new Triple<>("k", Integer.valueOf(1), Double.valueOf(2.0d));
+                    return t.a + t.b + t.c.intValue();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'k12', true)
+    }
+
+    /**
+     * JLS 4.5, 13.1: {@code Class#getGenericSuperclass()} of a class that
+     * extends {@code Box<String>} is the parameterized type {@code Box<String>},
+     * and {@code getActualTypeArguments()[0]} is {@code String.class}.
+     */
+    @Test
+    void testGenericSuperclassActualTypeArguments() {
+        final String className = 'gls.generics.test.GenericSuperclassArgs'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.lang.reflect.ParameterizedType;
+            import java.lang.reflect.Type;
+            public class GenericSuperclassArgs {
+                public static class Box<T> {}
+                public static class NamedBox extends Box<String> {}
+                public static class StringIterable implements Iterable<String> {
+                    @Override
+                    public java.util.Iterator<String> iterator() {
+                        return java.util.Collections.emptyIterator();
+                    }
+                }
+                public static String test() {
+                    Type sc = NamedBox.class.getGenericSuperclass();
+                    ParameterizedType box = (ParameterizedType) sc;
+                    Class<?> arg = (Class<?>) box.getActualTypeArguments()[0];
+                    String ifaceArg = "missing";
+                    for (Type t : StringIterable.class.getGenericInterfaces()) {
+                        if (t instanceof ParameterizedType) {
+                            ParameterizedType p = (ParameterizedType) t;
+                            if (p.getRawType() == Iterable.class) {
+                                ifaceArg = ((Class<?>) p.getActualTypeArguments()[0]).getSimpleName();
+                            }
+                        }
+                    }
+                    return arg.getName() + "|" + ifaceArg;
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'java.lang.String|String', true)
+    }
+
+    /**
+     * JLS 4.5, 10.1, 13.1: a field of type {@code List<String>[]} is a generic
+     * array type in the Signature attribute. {@link GenericArrayType} reports
+     * the component as {@code List<String>}. Creation of such an array remains
+     * illegal; see {@link #testNegativeNonReifiableArrayCreation()}.
+     */
+    @Test
+    void testGenericArrayTypeFieldSignature() {
+        final String className = 'gls.generics.test.GenericArrayField'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.lang.reflect.GenericArrayType;
+            import java.lang.reflect.ParameterizedType;
+            import java.lang.reflect.Type;
+            import java.util.List;
+            public class GenericArrayField {
+                public List<String>[] rows;
+                public static String test() throws Exception {
+                    Type t = GenericArrayField.class.getField("rows").getGenericType();
+                    if (!(t instanceof GenericArrayType)) {
+                        return "not-array:" + t;
+                    }
+                    Type component = ((GenericArrayType) t).getGenericComponentType();
+                    if (!(component instanceof ParameterizedType)) {
+                        return "not-param:" + component;
+                    }
+                    return ((Class<?>) ((ParameterizedType) component).getActualTypeArguments()[0]).getSimpleName();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'String', true)
+    }
+
+    /**
+     * JLS 8.8.4, 13.1: a generic constructor {@code <T> CtorBox(T)} records its
+     * type parameter on the constructor, independent of any class type parameters.
+     */
+    @Test
+    void testGenericConstructorTypeParameterSignature() {
+        final String className = 'gls.generics.test.GenericCtorSignature'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.lang.reflect.Constructor;
+            import java.lang.reflect.TypeVariable;
+            public class GenericCtorSignature {
+                public static class CtorBox {
+                    public final Object value;
+                    public <T> CtorBox(T t) { this.value = t; }
+                }
+                public static int test() {
+                    int found = 0;
+                    for (Constructor<?> c : CtorBox.class.getDeclaredConstructors()) {
+                        if (c.getParameterCount() == 1) {
+                            TypeVariable<?>[] tps = c.getTypeParameters();
+                            if (tps.length == 1 && "T".equals(tps[0].getName())) {
+                                found++;
+                            }
+                        }
+                    }
+                    return found;
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 1, true)
+    }
+
+    /**
+     * JLS 4.8: a raw subclass {@code class Raw extends Box} uses the erasure of
+     * {@code Box}. {@code get()} therefore has compile-time type {@code Object}.
+     */
+    @Test
+    void testRawSubclassOfGenericClass() {
+        final String className = 'gls.generics.test.RawSubclass'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class RawSubclass {
+                public static class Box<T> {
+                    public final T value;
+                    public Box(T value) { this.value = value; }
+                    public T get() { return value; }
+                }
+                @SuppressWarnings({"rawtypes", "unchecked"})
+                public static class Raw extends Box {
+                    public Raw(Object value) { super(value); }
+                }
+                @SuppressWarnings({"rawtypes", "unchecked"})
+                public static String test() {
+                    Raw raw = new Raw("hi");
+                    Object value = raw.get();
+                    return value.toString();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'hi')
+    }
+
+    /**
+     * JLS 8.4.8.1, 4.8, 5.1.9: a raw subclass may override {@code name(T)} with
+     * {@code name(Object)} (the erasure). Invocation through {@code Super<String>}
+     * still dispatches to the subclass. javac 25 accepts this as an unchecked override.
+     */
+    @Test
+    void testUncheckedOverrideOfGenericMethod() {
+        final String className = 'gls.generics.test.UncheckedOverride'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class UncheckedOverride {
+                public static class Super<T> {
+                    public String name(T t) { return "super"; }
+                }
+                @SuppressWarnings({"rawtypes", "unchecked"})
+                public static class Child extends Super {
+                    public String name(Object t) { return "child"; }
+                }
+                public static String test() {
+                    Super<String> s = new Child();
+                    return s.name("x");
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'child')
+    }
+
+    // =========================================================================
+    // Category 17: Overload resolution against erasure; F-bounded copy; intersection
+    // erasure; Collection<Object> vs Collection<?> (JLS 4.4–4.6, 4.8, 5.1.10, 15.12.2)
+    // =========================================================================
+
+    /**
+     * JLS 4.6, 15.12.2: inside {@code <T> generic(T t)} the compile-time type of
+     * {@code t} is the erasure of {@code T} ({@code Object}), so {@code f(t)} binds
+     * to {@code f(Object)} even when the invocation is {@code generic("string")}.
+     */
+    @Test
+    void testGenericMethodOverloadResolvesAgainstErasure() {
+        final String className = 'gls.generics.test.GenericOverloadErasure'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class GenericOverloadErasure {
+                public static String f(Object s) { return "object"; }
+                public static String f(String s) { return "string"; }
+                public static <T> String generic(T t) { return f(t); }
+                public static String test() {
+                    return generic("string");
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'object')
+    }
+
+    /**
+     * JLS 4.4: {@code Copyable<T extends Copyable<T>>} with {@code T copy()} makes
+     * {@code node.copy().copy()} well-typed, because the result of {@code copy()}
+     * is the same F-bounded type.
+     */
+    @Test
+    void testFBoundedCopyAllowsChainedCopy() {
+        final String className = 'gls.generics.test.FBoundedCopy'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class FBoundedCopy {
+                public interface Copyable<T extends Copyable<T>> {
+                    T copy();
+                }
+                public static class Node implements Copyable<Node> {
+                    @Override
+                    public Node copy() { return this; }
+                    public String id() { return "n"; }
+                }
+                public static String test() {
+                    return new Node().copy().copy().id();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'n', true)
+    }
+
+    /**
+     * JLS 4.4, 4.6, 4.9: the {@code Collections.max} shape
+     * {@code <T extends Object & Comparable<? super T>> T max(Collection<? extends T>)}
+     * erases the return type to the leftmost bound {@code Object}.
+     */
+    @Test
+    void testIntersectionMaxErasureIsLeftmostBound() {
+        final String className = 'gls.generics.test.IntersectionMaxErasure'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.lang.reflect.Method;
+            import java.util.Collection;
+            public class IntersectionMaxErasure {
+                public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
+                    return null;
+                }
+                public static String test() throws Exception {
+                    Method m = IntersectionMaxErasure.class.getMethod("max", Collection.class);
+                    return m.getReturnType() == Object.class ? "ok" : m.getReturnType().getName();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'ok')
+    }
+
+    /**
+     * JLS 4.5, 4.8: {@code Collection<Object>} accepts any reference.
+     * {@code Collection<?>} is a capture of some unknown element type; see
+     * {@link #testNegativeAddNonNullThroughUnboundedWildcard()}.
+     */
+    @Test
+    void testCollectionObjectIsHeterogeneous() {
+        final String className = 'gls.generics.test.HeterogeneousCollectionObject'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.ArrayList;
+            import java.util.Collection;
+            public class HeterogeneousCollectionObject {
+                public static int test() {
+                    Collection<Object> hetero = new ArrayList<Object>();
+                    hetero.add("a");
+                    hetero.add(Integer.valueOf(1));
+                    return hetero.size();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 2)
+    }
+
+    /**
+     * JLS 5.1.10: after capture, the element type of {@code Collection<?>} is a
+     * fresh type variable; {@code String} is not a subtype of that capture, so
+     * {@code add("x")} is a compile-time error. {@code add(null)} remains legal
+     * ({@link #testWildcardAllowsAddingNull()}).
+     */
+    @Test
+    void testNegativeAddNonNullThroughUnboundedWildcard() {
+        final String className = 'gls.generics.test.UnboundedWildcardAdd'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.Collection;
+            public class UnboundedWildcardAdd {
+                public static void test(Collection<?> c) {
+                    c.add("x");
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "incompatible types", "Cannot call")
+    }
+
+    // =========================================================================
+    // Test Harness Helper Methods
+    // =========================================================================
+
+    /**
+     * Static Groovy view of {@code javaSrc}: Java annotation array initializers become
+     * Groovy lists. Semicolons and the rest of the Java syntax are kept. CompileStatic
+     * is applied by {@link #newStaticGroovyClassLoader()}, not by rewriting the source.
+     */
+    private static String toGroovy(String javaSrc) {
+        javaSrc.replaceAll(/@SuppressWarnings\(\{([^}]*)\}\)/, '@SuppressWarnings([$1])')
+    }
+
+    /**
+     * Groovy class loader that always compiles with {@link CompileStatic}.
+     * Dynamic Groovy is not part of this suite.
+     */
+    private static GroovyClassLoader newStaticGroovyClassLoader() {
+        CompilerConfiguration config = new CompilerConfiguration()
+        config.addCompilationCustomizers(new ASTTransformationCustomizer(CompileStatic))
+        new GroovyClassLoader(GenericsJavaCompatibilityTest.classLoader, config)
+    }
+
     /**
      * Compiles {@code javaSrc} with javac and the {@link #toGroovy} view with static
      * Groovy, then asserts matching {@code test()} results. When {@code compareSignatures}
@@ -1637,11 +3038,15 @@ final class GenericsJavaCompatibilityTest {
         if (type instanceof ParameterizedType) {
             ParameterizedType p = (ParameterizedType) type
             Type[] typeArgs = p.actualTypeArguments
-            String raw = typeDesc(p.rawType)
-            if (typeArgs == null || typeArgs.length == 0) {
-                return raw
-            }
-            return raw + '<' + typeArgs.collect { typeDesc(it) }.join(', ') + '>'
+            String args = (typeArgs == null || typeArgs.length == 0) ? '' :
+                '<' + typeArgs.collect { typeDesc(it) }.join(', ') + '>'
+            if (p.ownerType != null) {
+                String raw = rawName(p.rawType)
+                int cut = Math.max(raw.lastIndexOf('$'), raw.lastIndexOf('.'))
+                String simple = cut < 0 ? raw : raw.substring(cut + 1)
+                return typeDesc(p.ownerType) + '$' + simple + args
+            }
+            return typeDesc(p.rawType) + args
         }
         if (type instanceof WildcardType) {
             WildcardType w = (WildcardType) type
diff --git a/src/test/groovy/gls/generics/GenericsUsageTest.groovy b/src/test/groovy/gls/generics/GenericsUsageTest.groovy
index 5957cd625c..194b97bafa 100644
--- a/src/test/groovy/gls/generics/GenericsUsageTest.groovy
+++ b/src/test/groovy/gls/generics/GenericsUsageTest.groovy
@@ -323,10 +323,9 @@ final class GenericsUsageTest {
         // constructor call
         assertScript '''
             List<String> list = new LinkedList<>()
+            def aic = new LinkedList<>() { }
+            assert aic instanceof 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'
diff --git a/src/test/groovy/gls/innerClass/InnerClassTest.groovy b/src/test/groovy/gls/innerClass/InnerClassTest.groovy
index 61bfde3f0c..af873da66b 100644
--- a/src/test/groovy/gls/innerClass/InnerClassTest.groovy
+++ b/src/test/groovy/gls/innerClass/InnerClassTest.groovy
@@ -2170,8 +2170,8 @@ final class InnerClassTest {
             })
             void bar(Runnable runner = new Runnable() {
                 @Override void run() {
-                    def comparator = new Comparator<int>() {
-                        int compare(int one, int two) {
+                    def comparator = new Comparator<Integer>() {
+                        int compare(Integer one, Integer two) {
                         }
                     }
                 }
diff --git a/src/test/groovy/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy b/src/test/groovy/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
index f5e6b79980..7686eb3c4e 100644
--- a/src/test/groovy/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
@@ -82,13 +82,17 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
     // GROOVY-10111
     @Test
     void testBoundedComponentTypeInArrayInitializer() {
-        assertScript '''
+        // JLS 15.10.1 / 4.7: a type variable is not reifiable, so
+        // `new X[]{...}` is generic array creation even when X is bounded.
+        // Initializer conversion is checked only for well-formed creations.
+        shouldFailWithMessages '''
             class C<X, Y> {
             }
             def <X extends C<Number, String>> X[] m() {
                 new X[]{new C<Number, String>()}
             }
-        '''
+        ''',
+        'generic array creation'
     }
 
     @Test
diff --git a/src/test/groovy/groovy/transform/stc/DefaultGroovyMethodsSTCTest.groovy b/src/test/groovy/groovy/transform/stc/DefaultGroovyMethodsSTCTest.groovy
index bb4cd311b3..4f46949c95 100644
--- a/src/test/groovy/groovy/transform/stc/DefaultGroovyMethodsSTCTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/DefaultGroovyMethodsSTCTest.groovy
@@ -234,7 +234,7 @@ class DefaultGroovyMethodsSTCTest extends StaticTypeCheckingTestCase {
     @Test
     void testSortMethodsWithComparatorAcceptingSubclass() {
         assertScript '''
-            class SecondLetterComparator implements Comparator<? extends CharSequence> {
+            class SecondLetterComparator implements Comparator<CharSequence> {
                 int compare(CharSequence cs1, CharSequence cs2) {
                     cs1.charAt(1) <=> cs2.charAt(1)
                 }
diff --git a/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy
index e2045aa1e9..b10caf13c6 100644
--- a/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy
@@ -3309,12 +3309,13 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-12319
     @Test
     void testInferDiamondUsingAIC() {
-        shouldFailWithMessages '''
+        assertScript '''
             Map<String,Date> map = new HashMap<>() {}
-        ''',
-            'Cannot use diamond <> with anonymous inner classes'
+            assert map instanceof Map
+        '''
     }
 
     // GROOVY-5614
diff --git a/src/test/groovy/org/codehaus/groovy/ast/ClassNodeTest.java b/src/test/groovy/org/codehaus/groovy/ast/ClassNodeTest.java
index 8eca306adc..e9523860cd 100644
--- a/src/test/groovy/org/codehaus/groovy/ast/ClassNodeTest.java
+++ b/src/test/groovy/org/codehaus/groovy/ast/ClassNodeTest.java
@@ -161,6 +161,21 @@ public final class ClassNodeTest {
         assertEquals(0, classNode.getTypeAnnotations().size());
     }
 
+    @Test // GROOVY-12319
+    public void testOuterClassTypeCopiedOnPlainReference() {
+        ClassNode outer = ClassHelper.makeWithoutCaching("Outer");
+        outer.setGenericsTypes(new GenericsType[]{new GenericsType(ClassHelper.STRING_TYPE)});
+        classNode.setOuterClassType(outer);
+
+        assertEquals(outer, classNode.getOuterClassType());
+        ClassNode plain = classNode.getPlainNodeReference();
+        assertEquals(outer, plain.getOuterClassType());
+
+        classNode.setOuterClassType(null);
+        assertNull(classNode.getOuterClassType());
+        assertNull(classNode.getPlainNodeReference().getOuterClassType());
+    }
+
     @Test
     public void testPermittedSubclasses() throws Exception {
         assumeTrue(Runtime.version().feature() >= 17);
diff --git a/src/test/groovy/org/codehaus/groovy/ast/MethodNodeTest.groovy b/src/test/groovy/org/codehaus/groovy/ast/MethodNodeTest.groovy
index 8e84c4ae9b..f1976f79c2 100644
--- a/src/test/groovy/org/codehaus/groovy/ast/MethodNodeTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/ast/MethodNodeTest.groovy
@@ -21,6 +21,7 @@ package org.codehaus.groovy.ast
 import org.codehaus.groovy.ast.builder.AstBuilder
 import org.codehaus.groovy.control.CompilePhase
 import org.junit.jupiter.api.Test
+import org.objectweb.asm.Opcodes
 
 final class MethodNodeTest {
 
@@ -72,6 +73,15 @@ final class MethodNodeTest {
         }
     }
 
+    @Test // GROOVY-12319
+    void testIsBridge() {
+        def ordinary = new MethodNode('foo', Opcodes.ACC_PUBLIC, ClassHelper.OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null)
+        assert !ordinary.isBridge()
+
+        def bridge = new MethodNode('foo', Opcodes.ACC_PUBLIC | Opcodes.ACC_BRIDGE, ClassHelper.OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null)
+        assert bridge.isBridge()
+    }
+
     @Test
     void testIsDynamicReturnType1() {
         def methodNode = new MethodNode('foo', 0, ClassHelper.dynamicType(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null)
diff --git a/src/test/groovy/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy b/src/test/groovy/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy
index 350bdc75e5..0968b1cba4 100644
--- a/src/test/groovy/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy
@@ -22,9 +22,18 @@ package org.codehaus.groovy.ast.tools
 import org.codehaus.groovy.ast.ClassHelper
 import org.codehaus.groovy.ast.ClassNode
 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.ArgumentListExpression
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression
+import org.codehaus.groovy.ast.expr.ConstantExpression
 import org.codehaus.groovy.control.CompilePhase
 import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor
 import org.junit.jupiter.api.Test
+import org.objectweb.asm.Opcodes
+
+import static org.junit.jupiter.api.Assertions.assertThrows
 
 final class GenericsUtilsTest {
 
@@ -382,4 +391,139 @@ final class GenericsUtilsTest {
         assert listType.genericsTypes[0].type.unresolvedName == '#E'
         assert listType.genericsTypes[0].type.name == 'java.lang.Object'
     }
+
+    @Test // GROOVY-12319
+    void testApplyGenericsSpecFillsMissingFromBounds() {
+        def classNodes = compile '''
+            class BoundBox<T extends Number> {}
+        '''
+        ClassNode boundBox = findClassNode('BoundBox', classNodes).getPlainNodeReference()
+        boundBox.redirect = findClassNode('BoundBox', classNodes)
+        GenericsUtils.applyGenericsSpec(boundBox, [:])
+        assert boundBox.genericsTypes[0].type.name.contains('Number')
+
+        ClassNode list = ClassHelper.LIST_TYPE.getPlainNodeReference()
+        GenericsUtils.applyGenericsSpec(list, [:])
+        assert list.genericsTypes.length == 1
+        assert list.genericsTypes[0].type == ClassHelper.OBJECT_TYPE
+
+        GenericsUtils.applyGenericsSpec(list, [E: ClassHelper.STRING_TYPE])
+        assert list.genericsTypes[0].type == ClassHelper.STRING_TYPE
+
+        ClassNode object = ClassHelper.OBJECT_TYPE.getPlainNodeReference()
+        GenericsUtils.applyGenericsSpec(object, [E: ClassHelper.STRING_TYPE])
+        assert object.genericsTypes == null
+    }
+
+    @Test // GROOVY-12319
+    void testInferGenericsSpecFromOverridesAndDiamondTarget() {
+        assert GenericsUtils.diamondTargetOfAnonymousClass(null) == null
+
+        ClassNode noDiamond = new InnerClassNode(ClassHelper.OBJECT_TYPE, 'C$1', Opcodes.ACC_PUBLIC, ClassHelper.OBJECT_TYPE)
+        noDiamond.anonymous = true
+        assert GenericsUtils.diamondTargetOfAnonymousClass(noDiamond) == null
+
+        ClassNode diamondIface = ClassHelper.LIST_TYPE.getPlainNodeReference()
+        diamondIface.genericsTypes = GenericsType.EMPTY_ARRAY
+        ClassNode anon = new InnerClassNode(ClassHelper.OBJECT_TYPE, 'C$2', Opcodes.ACC_PUBLIC, ClassHelper.OBJECT_TYPE, [diamondIface] as ClassNode[], org.codehaus.groovy.ast.MixinNode.EMPTY_ARRAY)
+        anon.anonymous = true
+        assert GenericsUtils.diamondTargetOfAnonymousClass(anon) === diamondIface
+
+        Map empty = GenericsUtils.inferGenericsSpecFromOverrides(anon, ClassHelper.OBJECT_TYPE.getPlainNodeReference(), [:])
+        assert empty.isEmpty()
+
+        def classNodes = compile '''
+            interface Box<T> {
+                T get()
+            }
+            class Host {
+                def m() {
+                    new Box<>() {
+                        @Override
+                        int get() { 1 }
+                    }
+                }
+            }
+        '''
+        ClassNode host = findClassNode('Host', classNodes)
+        ClassNode aic = host.innerClasses.next()
+        ClassNode target = GenericsUtils.diamondTargetOfAnonymousClass(aic) ?: aic.unresolvedInterfaces[0]
+        Map spec = GenericsUtils.inferGenericsSpecFromOverrides(aic, target, [:])
+        assert spec.T == ClassHelper.Integer_TYPE || spec.T?.name == 'java.lang.Integer'
+    }
+
+    @Test // GROOVY-12319
+    void testPushEnclosingConstructorCall() {
+        def classNodes = compile '''
+            class C {}
+        '''
+        def visitor = new StaticTypeCheckingVisitor(classNodes[0].module.context, classNodes[0])
+        def ctx = visitor.typeCheckingContext
+        def ctor = new ConstructorCallExpression(ClassHelper.OBJECT_TYPE, new ArgumentListExpression())
+        ctx.pushEnclosingMethodCall(ctor)
+        assert ctx.getEnclosingMethodCall() === ctor
+        ctx.popEnclosingMethodCall()
+
+        def ex = assertThrows(IllegalArgumentException) {
+            ctx.pushEnclosingMethodCall(new ConstantExpression('x'))
+        }
+        assert ex.message.contains('constructor call')
+    }
+
+    @Test // GROOVY-12319
+    void testInferOverridesSkipsUnrelatedNameAndArityMatches() {
+        ClassNode box = ClassHelper.makeWithoutCaching('Box')
+        def t = ClassHelper.makeWithoutCaching('T')
+        t.genericsPlaceHolder = true
+        box.genericsTypes = [new GenericsType(t)] as GenericsType[]
+        def declared = new MethodNode('convert', Opcodes.ACC_PUBLIC, ClassHelper.STRING_TYPE,
+                [new Parameter(ClassHelper.STRING_TYPE, 's')] as Parameter[], ClassNode.EMPTY_ARRAY, null)
+        def unrelated = new MethodNode('convert', Opcodes.ACC_PUBLIC, ClassHelper.OBJECT_TYPE,
+                [new Parameter(ClassHelper.MAP_TYPE, 'm')] as Parameter[], ClassNode.EMPTY_ARRAY, null)
+        def superMethod = new MethodNode('convert', Opcodes.ACC_PUBLIC, t,
+                [new Parameter(t, 'v')] as Parameter[], ClassNode.EMPTY_ARRAY, null)
+        ClassNode impl = ClassHelper.makeWithoutCaching('Impl')
+        impl.addMethod(declared)
+        impl.addMethod(unrelated)
+        ClassNode superType = ClassHelper.makeWithoutCaching('Super')
+        superType.redirect = box
+        superType.addMethod(superMethod)
+        Map spec = GenericsUtils.inferGenericsSpecFromOverrides(impl, superType, [:])
+        assert spec['T'] == ClassHelper.STRING_TYPE
+    }
+
+    @Test // GROOVY-12319
+    void testInferOverridesSkipsUnrelatedParameterErasure() {
+        def t = ClassHelper.makeWithoutCaching('T')
+        t.genericsPlaceHolder = true
+        ClassNode box = ClassHelper.makeWithoutCaching('Box2')
+        box.genericsTypes = [new GenericsType(t)] as GenericsType[]
+        ClassNode listOfT = ClassHelper.LIST_TYPE.getPlainNodeReference()
+        listOfT.genericsTypes = [new GenericsType(t)] as GenericsType[]
+        ClassNode superList = ClassHelper.makeWithoutCaching('SuperList')
+        superList.redirect = box
+        superList.addMethod(new MethodNode('convert', Opcodes.ACC_PUBLIC, t,
+                [new Parameter(listOfT, 'xs')] as Parameter[], ClassNode.EMPTY_ARRAY, null))
+        ClassNode impl2 = ClassHelper.makeWithoutCaching('Impl2')
+        impl2.addMethod(new MethodNode('convert', Opcodes.ACC_PUBLIC, ClassHelper.STRING_TYPE,
+                [new Parameter(ClassHelper.Integer_TYPE, 'n')] as Parameter[], ClassNode.EMPTY_ARRAY, null))
+        Map skipped = GenericsUtils.inferGenericsSpecFromOverrides(impl2, superList, [:])
+        assert skipped['T'] == null
+    }
+
+    @Test // GROOVY-12319
+    void testInferOverridesBoxesPrimitiveActualType() {
+        def t = ClassHelper.makeWithoutCaching('T')
+        t.genericsPlaceHolder = true
+        t.redirect = ClassHelper.OBJECT_TYPE
+        ClassNode box = ClassHelper.makeWithoutCaching('Box')
+        box.genericsTypes = [new GenericsType(t)] as GenericsType[]
+        ClassNode superType = ClassHelper.makeWithoutCaching('Super')
+        superType.redirect = box
+        superType.addMethod(new MethodNode('n', Opcodes.ACC_PUBLIC, t, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null))
+        ClassNode impl = ClassHelper.makeWithoutCaching('Impl')
+        impl.addMethod(new MethodNode('n', Opcodes.ACC_PUBLIC, ClassHelper.int_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null))
+        Map spec = GenericsUtils.inferGenericsSpecFromOverrides(impl, superType, [:])
+        assert spec['T'] == ClassHelper.Integer_TYPE
+    }
 }
diff --git a/src/test/groovy/org/codehaus/groovy/classgen/BytecodeHelperTest.groovy b/src/test/groovy/org/codehaus/groovy/classgen/BytecodeHelperTest.groovy
index b6c579f183..d2d2794278 100644
--- a/src/test/groovy/org/codehaus/groovy/classgen/BytecodeHelperTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/classgen/BytecodeHelperTest.groovy
@@ -20,6 +20,7 @@ package org.codehaus.groovy.classgen
 
 import org.codehaus.groovy.ast.ClassHelper
 import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.GenericsType
 import org.codehaus.groovy.ast.MethodNode
 import org.codehaus.groovy.ast.Parameter
 import org.codehaus.groovy.ast.stmt.EmptyStatement
@@ -81,6 +82,55 @@ class BytecodeHelperTest extends AbstractBytecodeTestCase {
                 BytecodeHelper.getMethodDescriptor(new MethodNode('test', 0, ClassHelper.boolean_TYPE, Parameter.EMPTY_ARRAY, [] as ClassNode[], EmptyStatement.INSTANCE)))
     }
 
+    @Test // GROOVY-12319
+    void testRareTypeAndArrayGenericsSignatures() {
+        ClassNode outer = ClassHelper.makeWithoutCaching('com.example.Outer')
+        outer.genericsTypes = [new GenericsType(ClassHelper.STRING_TYPE)] as GenericsType[]
+        ClassNode inner = ClassHelper.makeWithoutCaching('com.example.Outer$Inner')
+        inner.outerClassType = outer
+        inner.genericsTypes = [new GenericsType(ClassHelper.Integer_TYPE)] as GenericsType[]
+
+        String typeSig = BytecodeHelper.getTypeGenericsSignature(inner)
+        assert typeSig.contains('Outer')
+        assert typeSig.contains('Inner')
+        assert typeSig.contains('String')
+
+        ClassNode list = ClassHelper.LIST_TYPE.getPlainNodeReference()
+        list.genericsTypes = [new GenericsType(ClassHelper.STRING_TYPE)] as GenericsType[]
+        String arrayBounds = BytecodeHelper.getGenericsBounds(list.makeArray())
+        assert arrayBounds.startsWith('[')
+        assert arrayBounds.contains('String')
+
+        assert BytecodeHelper.getGenericsBounds(ClassHelper.STRING_TYPE.makeArray()) == null
+
+        ClassNode diamond = ClassHelper.LIST_TYPE.getPlainNodeReference()
+        diamond.genericsTypes = GenericsType.EMPTY_ARRAY
+        assert BytecodeHelper.getTypeGenericsSignature(diamond) == null
+
+        ClassNode placeholder = ClassHelper.makeWithoutCaching('T')
+        placeholder.genericsPlaceHolder = true
+        placeholder.genericsTypes = [new GenericsType(placeholder)] as GenericsType[]
+        placeholder.redirect = ClassHelper.OBJECT_TYPE
+        assert BytecodeHelper.getGenericsBounds(placeholder) != null
+
+        ClassNode mismatchedInner = ClassHelper.makeWithoutCaching('totally.Different$Item')
+        mismatchedInner.outerClassType = outer
+        mismatchedInner.genericsTypes = [new GenericsType(ClassHelper.Integer_TYPE)] as GenericsType[]
+        String fallback = BytecodeHelper.getTypeGenericsSignature(mismatchedInner)
+        assert fallback.contains('Item')
+
+        ClassNode superDiamond = ClassHelper.LIST_TYPE.getPlainNodeReference()
+        superDiamond.genericsTypes = GenericsType.EMPTY_ARRAY
+        ClassNode t = ClassHelper.makeWithoutCaching('T')
+        t.genericsPlaceHolder = true
+        ClassNode genericClass = new ClassNode('C', ACC_PUBLIC, superDiamond)
+        genericClass.genericsTypes = [new GenericsType(t)] as GenericsType[]
+        assert BytecodeHelper.getGenericsSignature(genericClass) != null
+
+        MethodNode mn = new MethodNode('echo', 0, inner, [new Parameter(inner, 'p')] as Parameter[], [] as ClassNode[], EmptyStatement.INSTANCE)
+        assert BytecodeHelper.getGenericsMethodSignature(mn).contains('Outer')
+    }
+
     @Test
     void testPushConstantUsesCompactIntegerInstructions() {
         def bytecode = emitBytecode {
diff --git a/subprojects/groovy-console/src/test/groovy/groovy/console/ui/AstNodeToScriptAdapterTest.groovy b/subprojects/groovy-console/src/test/groovy/groovy/console/ui/AstNodeToScriptAdapterTest.groovy
index 370bc3d605..fbe5c88328 100644
--- a/subprojects/groovy-console/src/test/groovy/groovy/console/ui/AstNodeToScriptAdapterTest.groovy
+++ b/subprojects/groovy-console/src/test/groovy/groovy/console/ui/AstNodeToScriptAdapterTest.groovy
@@ -208,14 +208,20 @@ b $$v"""/$,
 
     @Test
     void testGenericBoundsOnClass() {
+        // JLS 8.1.5: a superinterface may not specify a wildcard, so Callable<Number>
+        // is the well-formed implements type. Wildcard bounds are still exercised on
+        // the field, which is a legal type position for ? super Number.
         String script = '''import java.util.concurrent.Callable
-            abstract class MyClass<T extends String & Callable<String>, U extends Integer> extends AbstractList<String> implements Callable<? super Number> { }
+            abstract class MyClass<T extends String & Callable<String>, U extends Integer> extends AbstractList<String> implements Callable<Number> {
+                Callable<? super Number> callback
+            }
         '''
         String result = compileToScript(script)
 
         assert result.contains('MyClass<T extends java.lang.String & java.util.concurrent.Callable<String>, U extends java.lang.Integer> ' +
                 'extends java.util.AbstractList<String> ' +
-                'implements java.util.concurrent.Callable<? super java.lang.Number> {')
+                'implements java.util.concurrent.Callable<Number> {')
+        assert result.contains('java.util.concurrent.Callable<? super java.lang.Number>')
     }
 
     @Test