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/29 20:10:07 UTC

(groovy) branch generics-tck updated (696106cec8 -> cb69209dac)

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

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


 discard 696106cec8 GROOVY-12319: Java compatibility: remaining generic type syntax
     new cb69209dac GROOVY-12319: Java compatibility: remaining generic type syntax

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (696106cec8)
            \
             N -- N -- N   refs/heads/generics-tck (cb69209dac)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../org/codehaus/groovy/control/GenericsVisitor.java    | 17 +++++++++++++++++
 src/test/groovy/gls/innerClass/InnerClassTest.groovy    |  4 ++--
 .../transform/stc/ArraysAndCollectionsSTCTest.groovy    |  8 ++++++--
 3 files changed, 25 insertions(+), 4 deletions(-)


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

Posted by su...@apache.org.
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 cb69209dac78d81ec1ae42ce5d63299fe952da48
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    |  26 +
 .../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   | 138 +++-
 .../org/codehaus/groovy/classgen/Verifier.java     |  26 +-
 .../groovy/classgen/asm/BytecodeHelper.java        |  57 +-
 .../codehaus/groovy/control/GenericsVisitor.java   | 170 ++++-
 .../codehaus/groovy/control/ResolveVisitor.java    |  12 +
 .../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            | 812 ++++++++++++++++++++
 src/test/groovy/bugs/Groovy5318.groovy             |   5 +-
 .../generics/GenericsJavaCompatibilityTest.groovy  | 843 ++++++++++++++++++++-
 .../groovy/gls/generics/GenericsUsageTest.groovy   |   5 +-
 .../groovy/gls/innerClass/InnerClassTest.groovy    |   4 +-
 .../stc/ArraysAndCollectionsSTCTest.groovy         |   8 +-
 .../groovy/transform/stc/GenericsSTCTest.groovy    |   7 +-
 28 files changed, 2415 insertions(+), 206 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..1b96d69001 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
@@ -339,6 +339,10 @@ public class ClassNode extends AnnotatedNode {
         if (isArray()) {
             n.componentType = redirect().getComponentType();
         }
+        ClassNode outerType = getOuterClassType();
+        if (outerType != null) {
+            n.setOuterClassType(outerType);
+        }
         return n;
     }
 
@@ -2034,6 +2038,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
      */
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..7cc0a1579b 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,141 @@ 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();
+                for (int i = 0; i < sp.length; i += 1) {
+                    extractOverrideConnection(dp[i].getType(), sp[i].getType(), extracted);
+                }
+                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}.
+     */
+    private static void extractOverrideConnection(final ClassNode actual, final ClassNode declared, final Map<String, ClassNode> extracted) {
+        if (actual == null || declared == null) return;
+        if (!declared.isGenericsPlaceHolder() && !hasUnresolvedGenerics(declared)) return;
+        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;
+        }
+        extractSuperClassGenerics(boxed, declared, extracted);
+    }
+
+    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..19fe0af030 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,32 @@ 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);
+        if (genericsSpec.isEmpty() && classNode instanceof InnerClassNode && ((InnerClassNode) classNode).isAnonymous()
+                && superType.redirect().getGenericsTypes() != null) {
+            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..a747580278 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,20 @@ 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.isPrimitiveType;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.diamondTargetOfAnonymousClass;
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUnboundedWildcard;
 
 /**
@@ -42,6 +50,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 +85,19 @@ 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);
+            checkGenericsUsage(face, face.redirect(), isAIC ? Boolean.TRUE : null);
         }
 
         visitObjectInitializerStatements(node);
@@ -109,6 +124,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 +143,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 +187,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 +256,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 +283,10 @@ 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);
+        }
         GenericsType[] cnTypes = cn.getGenericsTypes();
         // raw type usage is always allowed
         if (cnTypes == null) return;
@@ -215,24 +303,23 @@ 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();
+            if (isPrimitiveType(cnType)) {
+                addError("The type argument " + cnType.getName() + " is of primitive type; a reference type is required", cnTypes[i]);
+                continue;
+            }
             ClassNode rnType = rnTypes[i].getType();
             // check nested type parameters
             checkGenericsUsage(cnType);
@@ -262,6 +349,65 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
         }
     }
 
+    /**
+     * 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();
+    }
+
+    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..ce9dc716f2 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()) {
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 ac48070d09..6ac8a9c294 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);
@@ -1412,10 +1418,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);
@@ -1442,10 +1456,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<>()
@@ -1957,6 +1979,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);
@@ -3083,65 +3106,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) {
@@ -3935,7 +3988,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) {
@@ -4172,6 +4240,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);
@@ -4566,9 +4638,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.
@@ -6289,6 +6361,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;
         }
 
@@ -6313,7 +6392,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
@@ -7048,6 +7135,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..5dfe0ca8fa
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy12319.groovy
@@ -0,0 +1,812 @@
+/*
+ *  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.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.junit.jupiter.api.Test
+
+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_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
+    }
+}
+
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..ae9f8a219e 100644
--- a/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy
+++ b/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy
@@ -50,13 +50,27 @@ 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:
+ * 4.4–4.10 (type variables, parameterized types, erasure, reifiable types,
+ * raw types, intersection types, subtyping), 5.1.9–5.1.10 (unchecked and
+ * capture conversion), 8.1.2 / 8.4.4 / 8.8.4 / 9.1.2 (generic classes,
+ * methods, constructors, interfaces), 15.9–15.12 (diamond, instance creation,
+ * invocation) and 18 (type inference). Each test method names the sections it
+ * exercises. Gaps where static Groovy currently diverges from javac are
+ * marked {@link Disabled} on the corresponding test.
  */
 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 +93,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 +124,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 +152,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 +179,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 +215,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 +247,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 +277,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 +306,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 +324,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 +333,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 +367,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 +399,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 +427,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 +457,10 @@ final class GenericsJavaCompatibilityTest {
         assertPositive(className, javaSrc, 9)
     }
 
+    /**
+     * 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 +487,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 +515,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 +540,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 +569,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 +605,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 +635,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 +677,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 +693,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 +713,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 +730,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 +749,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 +769,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 +785,10 @@ final class GenericsJavaCompatibilityTest {
         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 +804,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 +822,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 +842,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 +876,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 +906,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 +933,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 +961,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 +986,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 +1118,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 +1140,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 +1169,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 +1196,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 +1218,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 +1243,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 +1273,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 +1295,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 +1316,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 +1345,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 +1376,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 +1407,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 +1434,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 +1457,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 +1480,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 +1503,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 +1529,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 +1552,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 +1576,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 +1602,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 +1631,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 +1653,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 +1674,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 +1696,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 +1741,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 +1763,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 +1781,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 +1799,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 +1815,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'
@@ -1454,6 +1835,402 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "incompatible types", "Cannot call")
     }
 
+    // =========================================================================
+    // Category 15: Erasure, reifiable arrays, static members, remaining JLS restrictions
+    // =========================================================================
+
+    /**
+     * 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.
+     */
+    @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)
+    }
+
+    /**
+     * 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()}).
+     */
+    @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 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")
+    }
+
     // =========================================================================
     // Test Harness Helper Methods
     // =========================================================================
@@ -1637,11 +2414,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/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