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/31 18:06:51 UTC
(groovy) branch generics-tck updated: GROOVY-12319: centralize generic type-use helpers and rare-type copies
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
The following commit(s) were added to refs/heads/generics-tck by this push:
new 76344606e1 GROOVY-12319: centralize generic type-use helpers and rare-type copies
76344606e1 is described below
commit 76344606e1b2159a337286fd49793481a12fc315
Author: Daniel Sun <su...@apache.org>
AuthorDate: Mon Aug 31 18:05:45 2026 +0000
GROOVY-12319: centralize generic type-use helpers and rare-type copies
Share parameterized/raw/reifiable classification and describeTypeUse in
TypeUseUtils. Write outerClassType onto INFERRED_TYPE at storeType, resolve
type-parameter headers only at declaration sites, extract nested Signature
writing from BytecodeHelper, and copy genericDeclaration through asGenericsType.
---
.../org/apache/groovy/ast/tools/TypeUseUtils.java | 182 +++++++++++++++++++++
.../apache/groovy/parser/antlr4/AstBuilder.java | 33 +---
.../java/org/codehaus/groovy/ast/ClassNode.java | 6 +-
.../codehaus/groovy/ast/tools/GenericsUtils.java | 1 +
.../groovy/classgen/asm/BytecodeHelper.java | 64 +-------
.../groovy/classgen/asm/TypeSignatureWriter.java | 86 ++++++++++
.../codehaus/groovy/control/GenericsVisitor.java | 182 ++++-----------------
.../codehaus/groovy/control/ResolveVisitor.java | 64 +++-----
.../transform/stc/StaticTypeCheckingVisitor.java | 45 ++---
.../generics/GenericsJavaCompatibilityTest.groovy | 2 +-
.../groovy/ast/tools/TypeUseUtilsTest.groovy | 109 ++++++++++++
.../org/codehaus/groovy/ast/ClassNodeTest.java | 18 ++
12 files changed, 490 insertions(+), 302 deletions(-)
diff --git a/src/main/java/org/apache/groovy/ast/tools/TypeUseUtils.java b/src/main/java/org/apache/groovy/ast/tools/TypeUseUtils.java
new file mode 100644
index 0000000000..e84e9b4dad
--- /dev/null
+++ b/src/main/java/org/apache/groovy/ast/tools/TypeUseUtils.java
@@ -0,0 +1,182 @@
+/*
+ * 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 org.apache.groovy.ast.tools;
+
+import groovy.transform.Internal;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.GenericsType;
+
+import static org.codehaus.groovy.ast.ClassHelper.isObjectType;
+import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
+
+/**
+ * Classification of a {@link ClassNode} as a type <em>use</em> (JLS 4.5, 4.7,
+ * 4.8). Distinct from a generic class <em>declaration</em>, whose
+ * {@link ClassNode#getGenericsTypes()} are the formals.
+ *
+ * @since 6.0.0
+ */
+@Internal
+public final class TypeUseUtils {
+
+ private TypeUseUtils() {
+ }
+
+ /**
+ * 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).
+ */
+ public static boolean isParameterizedTypeUsage(final ClassNode type) {
+ if (type == null || type.isGenericsPlaceHolder()) return false;
+ GenericsType[] gt = type.getGenericsTypes();
+ return gt != null && gt.length > 0 && type.isRedirectNode();
+ }
+
+ /**
+ * True when {@code type} or any enclosing rare type is a parameterized
+ * usage. Used for class literals (JLS 15.8.2): {@code Outer<String>.Inner.class}.
+ * Declaration formals on {@code Map} must not make {@code Map.Entry} a
+ * class-literal error.
+ */
+ public static boolean isParameterizedTypeUsageIncludingEnclosing(final ClassNode type) {
+ if (type == null) return false;
+ if (isParameterizedTypeUsage(type)) return true;
+ ClassNode oc = type.getOuterClassType();
+ return oc != null && (isParameterizedTypeUsage(oc) || isParameterizedTypeUsageIncludingEnclosing(oc));
+ }
+
+ /**
+ * True when {@code type} carries type arguments, including wildcards
+ * ({@code Outer<?>}). Raw names ({@code Outer}) are not parameterized.
+ * Unlike {@link #isParameterizedTypeUsage(ClassNode)}, this does not
+ * require a redirect node: an enclosing type on a nested use may still
+ * be the parser's unresolved node.
+ */
+ public static boolean isParameterizedEnclosingType(final ClassNode type) {
+ if (type == null || type.isGenericsPlaceHolder()) return false;
+ GenericsType[] gt = type.getGenericsTypes();
+ return gt != null && gt.length > 0;
+ }
+
+ /**
+ * True when {@code type} is a raw use of a generic declaration ({@code Outer}
+ * for {@code class Outer<T>}).
+ */
+ public static boolean isRawGenericType(final ClassNode type) {
+ return type != null
+ && type.getGenericsTypes() == null
+ && type.redirect().getGenericsTypes() != null;
+ }
+
+ /**
+ * JLS 4.7: types available in full at run time. Used for array creation
+ * (JLS 15.10.1) and {@code instanceof} (JLS 15.20.2).
+ */
+ public static boolean isReifiable(ClassNode type) {
+ if (type == null) return false;
+ 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);
+ }
+
+ /**
+ * Nested interfaces and enums are implicitly static; otherwise the
+ * {@code ACC_STATIC} bit on the declaration is authoritative.
+ */
+ public static boolean isStaticMemberType(final ClassNode type) {
+ ClassNode declared = type.redirect();
+ return declared.isStatic()
+ || declared.isInterface()
+ || declared.isEnum();
+ }
+
+ /**
+ * True when a type argument of {@code type} or an enclosing rare type is a
+ * wildcard.
+ */
+ public static boolean hasWildcardTypeArgument(final ClassNode type) {
+ if (type == null) return false;
+ GenericsType[] gt = type.getGenericsTypes();
+ if (gt != null) {
+ for (GenericsType t : gt) {
+ if (t.isWildcard()) return true;
+ }
+ }
+ ClassNode oc = type.getOuterClassType();
+ return oc != null && hasWildcardTypeArgument(oc);
+ }
+
+ /**
+ * Source-like display of a type use, including rare enclosing arguments
+ * ({@code Outer<String>.Inner}) and array brackets.
+ */
+ public static String describeTypeUse(final ClassNode type) {
+ if (type == null) return "null";
+ if (type.isArray()) {
+ return describeTypeUse(type.getComponentType()) + "[]";
+ }
+ if (type.isGenericsPlaceHolder()) {
+ return type.getUnresolvedName();
+ }
+ StringBuilder sb = new StringBuilder();
+ ClassNode outer = type.getOuterClassType();
+ if (outer != null) {
+ sb.append(describeTypeUse(outer)).append('.');
+ String name = type.getName();
+ int sep = Math.max(name.lastIndexOf('.'), name.lastIndexOf('$'));
+ sb.append(sep < 0 ? name : name.substring(sep + 1));
+ } else {
+ sb.append(type.getNameWithoutPackage());
+ }
+ GenericsType[] generics = type.getGenericsTypes();
+ if (generics != null && generics.length > 0 && !type.isGenericsPlaceHolder()) {
+ sb.append('<');
+ for (int i = 0; i < generics.length; i++) {
+ if (i > 0) sb.append(", ");
+ sb.append(generics[i]);
+ }
+ sb.append('>');
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Indicates whether the wildcard has no explicit bounds.
+ */
+ private static boolean isUnboundedWildcard(final GenericsType gt) {
+ if (gt.isWildcard() && gt.getLowerBound() == null) {
+ ClassNode[] upperBounds = gt.getUpperBounds();
+ return (upperBounds == null || upperBounds.length == 0 || (upperBounds.length == 1
+ && isObjectType(upperBounds[0]) && !upperBounds[0].isGenericsPlaceHolder()));
+ }
+ return false;
+ }
+}
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 32387d72d1..4c9db12f1e 100644
--- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
+++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -38,6 +38,7 @@ import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
+import org.apache.groovy.ast.tools.TypeUseUtils;
import org.apache.groovy.parser.antlr4.internal.DescriptiveErrorStrategy;
import org.apache.groovy.parser.antlr4.internal.atnmanager.AtnManager;
import org.apache.groovy.parser.antlr4.util.StringUtils;
@@ -4742,7 +4743,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
private void rejectNonReifiableInstanceof(final GroovyParserRuleContext ctx, final ClassNode classNode) {
if (carriesNonReifiableInstanceofArg(classNode)) { // GROOVY-11585
throw this.createParsingFailedException(
- "Cannot perform instanceof check against parameterized type " + describeTypeUse(classNode)
+ "Cannot perform instanceof check against parameterized type " + TypeUseUtils.describeTypeUse(classNode)
+ " since further generic type information will be erased at runtime", ctx);
}
}
@@ -4768,36 +4769,6 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
return outer != null && carriesNonReifiableInstanceofArg(outer);
}
- /**
- * Source-like display of a type use, including rare enclosing arguments
- * ({@code Outer<String>.Inner}) and array brackets.
- */
- private static String describeTypeUse(final ClassNode type) {
- if (type.isArray()) {
- return describeTypeUse(type.getComponentType()) + "[]";
- }
- StringBuilder sb = new StringBuilder();
- ClassNode outer = type.getOuterClassType();
- if (outer != null) {
- sb.append(describeTypeUse(outer)).append('.');
- String name = type.getName();
- int sep = Math.max(name.lastIndexOf('.'), name.lastIndexOf('$'));
- sb.append(sep < 0 ? name : name.substring(sep + 1));
- } else {
- sb.append(type.getNameWithoutPackage());
- }
- GenericsType[] generics = type.getGenericsTypes();
- if (generics != null && generics.length > 0 && !type.isGenericsPlaceHolder()) {
- sb.append('<');
- for (int i = 0; i < generics.length; i++) {
- if (i > 0) sb.append(", ");
- sb.append(generics[i]);
- }
- sb.append('>');
- }
- return sb.toString();
- }
-
@Override
public GenericsType[] visitTypeArgumentsOrDiamond(final TypeArgumentsOrDiamondContext ctx) {
if (asBoolean(ctx.typeArguments())) {
diff --git a/src/main/java/org/codehaus/groovy/ast/ClassNode.java b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
index e0e866e868..9158c938be 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
@@ -2168,7 +2168,11 @@ faces: if (method == null && asBoolean(getInterfaces())) { // GROOVY-11323
return genericsTypes[0];
} else {
ClassNode upper = (redirect != null ? redirect : this);
- return new GenericsType(this, new ClassNode[]{upper}, null);
+ GenericsType gt = new GenericsType(this, new ClassNode[]{upper}, null);
+ if (genericsTypes != null && genericsTypes[0] != null) {
+ gt.setGenericDeclaration(genericsTypes[0].getGenericDeclaration());
+ }
+ return gt;
}
}
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 4385234072..88bf80cc7f 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
@@ -963,6 +963,7 @@ public class GenericsUtils {
ClassNode newPlaceHolder = ClassHelper.make(old.getName());
GenericsType gt = new GenericsType(newPlaceHolder, newUpper, newLower);
gt.setPlaceholder(true);
+ gt.setGenericDeclaration(old.getGenericDeclaration());
newTypes[i] = gt;
}
}
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 3babbc05ec..0c813c85c5 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java
@@ -218,7 +218,7 @@ public class BytecodeHelper {
*
* @return the ASM type description
*/
- private static String getTypeDescription(ClassNode c, boolean end) {
+ static String getTypeDescription(ClassNode c, boolean end) {
ClassNode d = c;
if (isPrimitiveType(d.redirect())) {
d = d.redirect();
@@ -336,7 +336,7 @@ public class BytecodeHelper {
return false;
}
- private static boolean hasGenerics(ClassNode type) {
+ static boolean hasGenerics(ClassNode type) {
if (type.isArray()) return hasGenerics(type.getComponentType());
if (type.getGenericsTypes() != null && type.getGenericsTypes().length > 0) return true;
ClassNode outer = type.getOuterClassType();
@@ -396,7 +396,7 @@ public class BytecodeHelper {
public static String getTypeGenericsSignature(ClassNode node) {
if (!usesGenericsInTypeSignature(node)) return null;
StringBuilder ret = new StringBuilder(100);
- writeParameterizedClass(ret, node);
+ TypeSignatureWriter.writeParameterizedClass(ret, node);
ret.append(";");
return ret.toString();
}
@@ -467,65 +467,11 @@ public class BytecodeHelper {
ret.append(printType.getGenericsTypes()[0].getName());
ret.append(";");
} else {
- writeParameterizedClass(ret, printType);
+ TypeSignatureWriter.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<...>.Inner<...>} when an enclosing rare type is present.
- */
- private static void writeParameterizedClass(StringBuilder ret, ClassNode printType) {
- ClassNode owner = printType.getOuterClassType();
- // JVMS 4.7.9.1 nested form is only for a parameterized enclosing type.
- // A raw enclosing type must keep the binary name (Outer$Inner<...>).
- if (owner != null && hasGenerics(owner)) {
- writeParameterizedClass(ret, owner);
- ret.append('.');
- ret.append(innerClassSimpleName(printType, owner));
- addSubTypes(ret, printType.getGenericsTypes(), "<", ">");
- return;
- }
- ret.append(getTypeDescription(printType, false));
- addSubTypes(ret, printType.getGenericsTypes(), "<", ">");
- }
-
- /**
- * Simple name of {@code inner} relative to {@code owner} for a JVMS 4.7.9.1
- * nested type signature. Accepts both {@code Outer.Inner} (parser) and
- * {@code Outer$Inner} (binary name after resolve); {@code Foo$1} is the
- * same prefix path. If {@code inner} is not nested under {@code owner},
- * the result is the last identifier only — never extra qualification of
- * {@code owner} (so owner {@code Foo} and inner {@code Bar.X} yield {@code X}).
- */
- private static String innerClassSimpleName(final ClassNode inner, final ClassNode owner) {
- String innerName = inner.getName();
- String ownerName = owner.getName();
- String nested = nestedNameAfterOwner(innerName, ownerName);
- if (nested != null) {
- return nested.replace('$', '.');
- }
- int sep = Math.max(innerName.lastIndexOf('.'), innerName.lastIndexOf('$'));
- return sep < 0 ? innerName : innerName.substring(sep + 1);
- }
-
- /**
- * Remainder of {@code innerName} after {@code ownerName} when nested under
- * it ({@code .} or {@code $} separator); otherwise {@code null}.
- */
- private static String nestedNameAfterOwner(final String innerName, final String ownerName) {
- int ownerLen = ownerName.length();
- if (innerName.length() <= ownerLen || !innerName.startsWith(ownerName)) {
- return null;
- }
- char sep = innerName.charAt(ownerLen);
- if (sep != '.' && sep != '$') {
- return null;
- }
- return innerName.substring(ownerLen + 1);
- }
-
private static void writeGenericsBounds(StringBuilder ret, GenericsType type, boolean writeInterfaceMarker) {
if (type.getUpperBounds() != null) {
ClassNode[] bounds = type.getUpperBounds();
@@ -539,7 +485,7 @@ public class BytecodeHelper {
}
}
- private static void addSubTypes(StringBuilder ret, GenericsType[] types, String start, String end) {
+ 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);
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/TypeSignatureWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/TypeSignatureWriter.java
new file mode 100644
index 0000000000..f9ba1d8617
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/TypeSignatureWriter.java
@@ -0,0 +1,86 @@
+/*
+ * 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 org.codehaus.groovy.classgen.asm;
+
+import org.codehaus.groovy.ast.ClassNode;
+
+/**
+ * Writes the JVMS 4.7.9.1 nested form of a parameterized type for a Signature
+ * attribute ({@code LOuter<...>.Inner<...>}). Package-private helper of
+ * {@link BytecodeHelper}.
+ */
+final class TypeSignatureWriter {
+
+ private TypeSignatureWriter() {
+ }
+
+ /**
+ * Writes a class type and its type arguments, using the JLS 4.5 nested form
+ * {@code LOuter<...>.Inner<...>} when an enclosing rare type is present.
+ */
+ static void writeParameterizedClass(final StringBuilder ret, final ClassNode printType) {
+ ClassNode owner = printType.getOuterClassType();
+ // JVMS 4.7.9.1 nested form is only for a parameterized enclosing type.
+ // A raw enclosing type must keep the binary name (Outer$Inner<...>).
+ if (owner != null && BytecodeHelper.hasGenerics(owner)) {
+ writeParameterizedClass(ret, owner);
+ ret.append('.');
+ ret.append(innerClassSimpleName(printType, owner));
+ BytecodeHelper.addSubTypes(ret, printType.getGenericsTypes(), "<", ">");
+ return;
+ }
+ ret.append(BytecodeHelper.getTypeDescription(printType, false));
+ BytecodeHelper.addSubTypes(ret, printType.getGenericsTypes(), "<", ">");
+ }
+
+ /**
+ * Simple name of {@code inner} relative to {@code owner} for a JVMS 4.7.9.1
+ * nested type signature. Accepts both {@code Outer.Inner} (parser) and
+ * {@code Outer$Inner} (binary name after resolve); {@code Foo$1} is the
+ * same prefix path. If {@code inner} is not nested under {@code owner},
+ * the result is the last identifier only — never extra qualification of
+ * {@code owner} (so owner {@code Foo} and inner {@code Bar.X} yield {@code X}).
+ */
+ static String innerClassSimpleName(final ClassNode inner, final ClassNode owner) {
+ String innerName = inner.getName();
+ String ownerName = owner.getName();
+ String nested = nestedNameAfterOwner(innerName, ownerName);
+ if (nested != null) {
+ return nested.replace('$', '.');
+ }
+ int sep = Math.max(innerName.lastIndexOf('.'), innerName.lastIndexOf('$'));
+ return sep < 0 ? innerName : innerName.substring(sep + 1);
+ }
+
+ /**
+ * Remainder of {@code innerName} after {@code ownerName} when nested under
+ * it ({@code .} or {@code $} separator); otherwise {@code null}.
+ */
+ static String nestedNameAfterOwner(final String innerName, final String ownerName) {
+ int ownerLen = ownerName.length();
+ if (innerName.length() <= ownerLen || !innerName.startsWith(ownerName)) {
+ return null;
+ }
+ char sep = innerName.charAt(ownerLen);
+ if (sep != '.' && sep != '$') {
+ return null;
+ }
+ return innerName.substring(ownerLen + 1);
+ }
+}
diff --git a/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java b/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java
index de5e51b47a..bdf8d47d5e 100644
--- a/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java
+++ b/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java
@@ -45,6 +45,14 @@ import org.codehaus.groovy.transform.trait.Traits;
import java.util.HashSet;
import java.util.Set;
+import static org.apache.groovy.ast.tools.TypeUseUtils.describeTypeUse;
+import static org.apache.groovy.ast.tools.TypeUseUtils.hasWildcardTypeArgument;
+import static org.apache.groovy.ast.tools.TypeUseUtils.isParameterizedEnclosingType;
+import static org.apache.groovy.ast.tools.TypeUseUtils.isParameterizedTypeUsage;
+import static org.apache.groovy.ast.tools.TypeUseUtils.isParameterizedTypeUsageIncludingEnclosing;
+import static org.apache.groovy.ast.tools.TypeUseUtils.isRawGenericType;
+import static org.apache.groovy.ast.tools.TypeUseUtils.isReifiable;
+import static org.apache.groovy.ast.tools.TypeUseUtils.isStaticMemberType;
import static org.codehaus.groovy.ast.ClassHelper.isObjectType;
import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
import static org.codehaus.groovy.ast.tools.GenericsUtils.diamondTargetOfAnonymousClass;
@@ -102,10 +110,10 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
if (checkWildcard(sc)) return;
boolean isAIC = node instanceof InnerClassNode && ((InnerClassNode) node).isAnonymous();
- checkGenericsUsage(sc, sc.redirect(), isAIC ? Boolean.TRUE : null);
+ checkGenericsUsage(sc, sc.redirect(), isAIC);
for (ClassNode face : node.getInterfaces()) {
checkWildcard(face); // JLS 8.1.5: superinterfaces may not specify a wildcard
- checkGenericsUsage(face, face.redirect(), isAIC ? Boolean.TRUE : null);
+ checkGenericsUsage(face, face.redirect(), isAIC);
}
visitObjectInitializerStatements(node);
@@ -178,7 +186,7 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
}
boolean isAIC = type instanceof InnerClassNode
&& ((InnerClassNode) type).isAnonymous();
- checkGenericsUsage(type, type.redirect(), isAIC);
+ checkGenericsUsage(type, type.redirect(), true);
if (expression.isUsingGenerics()) {
ClassNode created = isAIC ? diamondTargetOfAnonymousClass(type) : type;
if (created != null && created.getGenericsTypes() != null && created.getGenericsTypes().length == 0) {
@@ -223,27 +231,21 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
super.visitArrayExpression(expression);
}
- /**
- * Depth of {@link ClassExpression} qualifiers that are not class
- * literals ({@code Cell<String>.ID}, {@code Cell<String>.id()}).
- * {@link #visitClassExpression} skips the JLS 15.8.2 check while
- * this is positive so those sites keep the JLS 4.5.2 diagnostic.
- */
- private int suppressClassLiteralChecks;
-
/**
* JLS 15.8.2: a class literal may not name a type variable or a
* parameterized type ({@code T.class}, {@code Cell<String>.class}).
* After resolve, {@code Foo.class} is a {@link ClassExpression};
* {@code Foo<String>.class} may still be a {@link PropertyExpression}
* whose object is that class expression — see
- * {@link #visitPropertyExpression}.
+ * {@link #visitPropertyExpression}. {@link ClassExpression} is also
+ * the TypeName of a static qualifier, {@code instanceof} operand, or
+ * method reference; those parents visit the type without entering
+ * this method, so a class-literal check here is only for an actual
+ * class literal.
*/
@Override
public void visitClassExpression(final ClassExpression expression) {
- if (suppressClassLiteralChecks == 0) {
- checkClassLiteral(expression.getType(), expression);
- }
+ checkClassLiteral(expression.getType(), expression);
super.visitClassExpression(expression);
}
@@ -280,12 +282,9 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
Expression object = call.getObjectExpression();
checkStaticMemberViaParameterizedType(object, call);
if (object instanceof ClassExpression) {
- suppressClassLiteralChecks++;
- try {
- super.visitMethodCallExpression(call);
- } finally {
- suppressClassLiteralChecks--;
- }
+ // TypeName of a static qualifier, not a class literal.
+ call.getMethod().visit(this);
+ call.getArguments().visit(this);
} else {
super.visitMethodCallExpression(call);
}
@@ -306,17 +305,13 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
expression.getProperty().visit(this);
return;
}
- checkStaticMemberViaParameterizedType(object, expression);
if (object instanceof ClassExpression) {
- suppressClassLiteralChecks++;
- try {
- super.visitPropertyExpression(expression);
- } finally {
- suppressClassLiteralChecks--;
- }
- } else {
- super.visitPropertyExpression(expression);
+ // TypeName of a static qualifier, not a class literal.
+ checkStaticMemberViaParameterizedType(object, expression);
+ expression.getProperty().visit(this);
+ return;
}
+ super.visitPropertyExpression(expression);
}
/**
@@ -330,13 +325,9 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
public void visitMethodPointerExpression(final MethodPointerExpression expression) {
Expression object = expression.getExpression();
if (object instanceof ClassExpression) {
+ // TypeName of a method or constructor reference, not a class literal.
checkGenericsUsage(object.getType());
- suppressClassLiteralChecks++;
- try {
- super.visitMethodPointerExpression(expression);
- } finally {
- suppressClassLiteralChecks--;
- }
+ expression.getMethodName().visit(this);
} else {
super.visitMethodPointerExpression(expression);
}
@@ -376,7 +367,7 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
addError("Cannot select from a type parameter " + element.getUnresolvedName(), location);
return;
}
- if (isParameterizedClassLiteralType(element)) {
+ if (isParameterizedTypeUsageIncludingEnclosing(element)) {
addError("Cannot select from a parameterized type", location);
}
}
@@ -385,16 +376,6 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
return "class".equals(expression.getPropertyAsString());
}
- private static boolean isParameterizedClassLiteralType(final ClassNode type) {
- if (isParameterizedTypeUsage(type)) {
- return true;
- }
- ClassNode oc = type.getOuterClassType();
- // Use-site parameterization only (Outer<String>.Inner.class).
- // Declaration formals on Map must not make Map.Entry a class-literal error.
- return oc != null && (isParameterizedTypeUsage(oc) || isParameterizedClassLiteralType(oc));
- }
-
private boolean checkWildcard(final ClassNode sn) {
boolean wildcard = false;
if (sn.getGenericsTypes() != null) {
@@ -411,10 +392,10 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
private void checkGenericsUsage(ClassNode cn) {
while (cn.isArray())
cn = cn.getComponentType();
- checkGenericsUsage(cn, cn.redirect(), null);
+ checkGenericsUsage(cn, cn.redirect(), false);
}
- private void checkGenericsUsage(final ClassNode cn, final ClassNode rn, final Boolean isAIC) {
+ private void checkGenericsUsage(final ClassNode cn, final ClassNode rn, final boolean allowDiamond) {
if (cn.isGenericsPlaceHolder()) return;
ClassNode oc = cn.getOuterClassType(); // GROOVY-12319: Outer<T>.Inner
if (oc != null) {
@@ -449,7 +430,7 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
}
// parameterize a type by using all the parameters only
if (cnTypes.length != rnTypes.length) {
- if (isAIC != null && cnTypes.length == 0) {
+ if (allowDiamond && cnTypes.length == 0) {
return; // diamond on constructor calls, including anonymous classes (GROOVY-12319 / JEP 213)
}
String message = "The class " + cn.toString(false) + " (supplied with " + plural("type parameter", cnTypes.length) +
@@ -576,107 +557,6 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
addError("The type parameter " + type.getUnresolvedName() + " is not a valid type for a throws clause", location);
}
- /**
- * True when {@code type} is a parameterized usage ({@code Cell<String>}), as
- * opposed to the generic type name ({@code Cell}) or the class declaration
- * node (whose {@link ClassNode#getGenericsTypes()} are the formals).
- */
- private static boolean isParameterizedTypeUsage(final ClassNode type) {
- GenericsType[] gt = type.getGenericsTypes();
- return gt != null && gt.length > 0 && type.isRedirectNode() && !type.isGenericsPlaceHolder();
- }
-
- /**
- * True when {@code type} carries type arguments, including wildcards
- * ({@code Outer<?>}). Raw names ({@code Outer}) are not parameterized.
- */
- private static boolean isParameterizedEnclosingType(final ClassNode type) {
- GenericsType[] gt = type.getGenericsTypes();
- return gt != null && gt.length > 0 && !type.isGenericsPlaceHolder();
- }
-
- /**
- * True when {@code type} is a raw use of a generic declaration ({@code Outer}
- * for {@code class Outer<T>}).
- */
- private static boolean isRawGenericType(final ClassNode type) {
- return type.getGenericsTypes() == null && type.redirect().getGenericsTypes() != null;
- }
-
- /**
- * Source-like display of a type use, including rare enclosing arguments.
- */
- private static String describeTypeUse(final ClassNode type) {
- if (type.isArray()) {
- return describeTypeUse(type.getComponentType()) + "[]";
- }
- if (type.isGenericsPlaceHolder()) {
- return type.getUnresolvedName();
- }
- StringBuilder sb = new StringBuilder();
- ClassNode outer = type.getOuterClassType();
- if (outer != null) {
- sb.append(describeTypeUse(outer)).append('.');
- String name = type.getName();
- int sep = Math.max(name.lastIndexOf('.'), name.lastIndexOf('$'));
- sb.append(sep < 0 ? name : name.substring(sep + 1));
- } else {
- sb.append(type.getNameWithoutPackage());
- }
- GenericsType[] generics = type.getGenericsTypes();
- if (generics != null && generics.length > 0 && !type.isGenericsPlaceHolder()) {
- sb.append('<');
- for (int i = 0; i < generics.length; i++) {
- if (i > 0) sb.append(", ");
- sb.append(generics[i]);
- }
- sb.append('>');
- }
- return sb.toString();
- }
-
- /**
- * Nested interfaces and enums are implicitly static; otherwise the
- * {@code ACC_STATIC} bit on the declaration is authoritative.
- */
- private static boolean isStaticMemberType(final ClassNode type) {
- ClassNode declared = type.redirect();
- return declared.isStatic()
- || declared.isInterface()
- || declared.isEnum();
- }
-
- private static boolean hasWildcardTypeArgument(final ClassNode type) {
- GenericsType[] gt = type.getGenericsTypes();
- if (gt == null) return false;
- for (GenericsType t : gt) {
- if (t.isWildcard()) return true;
- }
- ClassNode oc = type.getOuterClassType();
- return oc != null && hasWildcardTypeArgument(oc);
- }
-
- /**
- * JLS 4.7: types available in full at run time. Used for array creation
- * (JLS 15.10.1), which requires a reifiable component type.
- */
- private static boolean isReifiable(ClassNode type) {
- while (type.isArray()) {
- type = type.getComponentType();
- }
- if (type.isGenericsPlaceHolder()) return false;
- if (isPrimitiveType(type)) return true;
- GenericsType[] gt = type.getGenericsTypes();
- if (gt != null) {
- if (gt.length == 0) return false;
- for (GenericsType t : gt) {
- if (!isUnboundedWildcard(t)) return false;
- }
- }
- ClassNode oc = type.getOuterClassType();
- return oc == null || isReifiable(oc);
- }
-
private static String plural(final String string, final int count) {
return count + " " + (count == 1 ? string : string + "s");
}
diff --git a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
index e7ac06bf24..8aa5ba0644 100644
--- a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
+++ b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
@@ -86,6 +86,7 @@ import java.util.function.Predicate;
import static groovy.lang.Tuple.tuple;
import static org.apache.groovy.ast.tools.ExpressionUtils.transformInlineConstants;
+import static org.apache.groovy.ast.tools.TypeUseUtils.isParameterizedTypeUsage;
import static org.codehaus.groovy.ast.tools.ClosureUtils.getParametersSafe;
/**
@@ -1199,15 +1200,7 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer {
|| (!Modifier.isPrivate(modifiers) && Objects.equals(innerType.getPackageName(), outerType.getPackageName()));
}
- /**
- * True for a use-site parameterization such as {@code Outer<String>}, not
- * the generic type name {@code Map} whose {@code getGenericsTypes()} are
- * declaration formals.
- */
- private static boolean isParameterizedTypeUsage(final ClassNode type) {
- GenericsType[] gt = type.getGenericsTypes();
- return gt != null && gt.length > 0 && type.isRedirectNode() && !type.isGenericsPlaceHolder();
- }
+
private boolean directlyImplementsTrait(final ClassNode trait) {
ClassNode[] interfaces = currentClass.getInterfaces();
@@ -1746,23 +1739,22 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer {
return resolved;
}
+ /**
+ * Resolves a type-parameter section (class, method or constructor header).
+ * Bound type arguments such as {@code T} in {@code T extends Comparable<T>}
+ * are uses, not declarations, and go through {@link #resolveGenericsType}.
+ */
private void resolveGenericsHeader(final GenericsType[] types, final AnnotatedNode declaration) {
- if (types != null) resolveGenericsHeader(types, null, 0, declaration);
- }
-
- private void resolveGenericsHeader(final GenericsType[] types, final GenericsType rootType, final int level, final AnnotatedNode declaration) {
+ if (types == null) return;
currentClass.setUsingGenerics(true);
List<Tuple2<ClassNode, ClassNode>> upperBoundsToResolve = new LinkedList<>();
- List<Tuple2<ClassNode, GenericsType>> upperBoundsWithGenerics = new LinkedList<>();
+ List<ClassNode> upperBoundsWithGenerics = new LinkedList<>();
for (GenericsType type : types) {
- if (level > 0 && type.getName().equals(rootType.getName())) continue; // cycle!
-
String name = type.getName();
ClassNode typeType = type.getType();
visitTypeAnnotations(typeType); // JSR 308 support
GenericsTypeName gtn = new GenericsTypeName(name);
boolean isWildcardGT = QUESTION_MARK.equals(name);
- boolean dealWithGenerics = (level == 0 || (level > 0 && genericParameterNames.get(gtn) != null));
if (type.getUpperBounds() != null) {
boolean nameAdded = false;
@@ -1771,33 +1763,26 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer {
if (!isWildcardGT) {
if (!nameAdded || !resolve(typeType)) {
- if (dealWithGenerics) {
- type.setPlaceholder(true);
- typeType.setRedirect(upperBound);
- genericParameterNames.put(gtn, type);
- if (level == 0) type.setGenericDeclaration(declaration);
- nameAdded = true;
- }
+ type.setPlaceholder(true);
+ typeType.setRedirect(upperBound);
+ genericParameterNames.put(gtn, type);
+ type.setGenericDeclaration(declaration);
+ nameAdded = true;
}
upperBoundsToResolve.add(tuple(upperBound, typeType));
}
if (upperBound.getGenericsTypes() != null) {
- upperBoundsWithGenerics.add(tuple(upperBound, type));
+ upperBoundsWithGenerics.add(upperBound);
}
}
- } else if (dealWithGenerics && !isWildcardGT) {
+ } else if (!isWildcardGT) {
type.setPlaceholder(true);
- GenericsType last = genericParameterNames.put(gtn, type);
- if (level == 0) {
- // A new type-parameter declaration must not inherit a
- // shadowed variable's erasure. Unbounded T erases to
- // Object even when an enclosing T extends Number.
- typeType.setRedirect(ClassHelper.OBJECT_TYPE);
- type.setGenericDeclaration(declaration);
- } else {
- typeType.setRedirect(last != null ? last.getType().redirect() : ClassHelper.OBJECT_TYPE);
- if (last != null) type.setGenericDeclaration(last.getGenericDeclaration());
- }
+ genericParameterNames.put(gtn, type);
+ // A new type-parameter declaration must not inherit a
+ // shadowed variable's erasure. Unbounded T erases to
+ // Object even when an enclosing T extends Number.
+ typeType.setRedirect(ClassHelper.OBJECT_TYPE);
+ type.setGenericDeclaration(declaration);
}
}
@@ -1810,9 +1795,8 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer {
}
}
- for (var tuple : upperBoundsWithGenerics) {
- GenericsType[] bounds = tuple.getV1().getGenericsTypes();
- resolveGenericsHeader(bounds, level == 0 ? tuple.getV2() : rootType, level + 1, declaration);
+ for (ClassNode upperBound : upperBoundsWithGenerics) {
+ resolveGenericsTypes(upperBound.getGenericsTypes());
}
}
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 e31fb71edc..6b048f15b3 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -5720,6 +5720,8 @@ trying: for (ClassNode[] signature : signatures) {
}
}
+ cn = withRareTypeFromOrigin(exp, cn);
+
ClassNode oldValue = (ClassNode) exp.putNodeMetaData(INFERRED_TYPE, cn);
if (oldValue != null) {
// this may happen when a variable declaration type is wider than the subsequent assignment values
@@ -5756,6 +5758,29 @@ trying: for (ClassNode[] signature : signatures) {
}
}
+ /**
+ * Rare types ({@code Outer<T>.Inner}) store the enclosing parameterization
+ * on the origin type. Inference often produces a redirect of {@code Inner}
+ * without that field; copy it at the {@link #storeType} write site so
+ * {@link #getType} does not have to reconstruct it.
+ */
+ private static ClassNode withRareTypeFromOrigin(final Expression exp, final ClassNode cn) {
+ if (cn == null || cn.getOuterClassType() != null) {
+ return cn;
+ }
+ ClassNode origin = null;
+ if (exp instanceof VariableExpression vexp) {
+ origin = vexp.getOriginType();
+ }
+ if (origin == null || origin.getOuterClassType() == null) {
+ return cn;
+ }
+ ClassNode copy = cn.getPlainNodeReference();
+ copy.setGenericsTypes(origin.getGenericsTypes());
+ copy.setOuterClassType(origin.getOuterClassType());
+ return copy;
+ }
+
/**
* Computes the result type of a binary operation.
*/
@@ -6367,13 +6392,6 @@ 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;
}
@@ -6398,15 +6416,7 @@ trying: for (ClassNode[] signature : signatures) {
return fieldType;
}
if (variable != vexp && variable instanceof VariableExpression) {
- 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;
+ return getType((VariableExpression) variable);
}
if (variable instanceof Parameter parameter) {
if (getTemporaryTypesForExpression(vexp).isEmpty()) { // not instanceof
@@ -7142,9 +7152,6 @@ 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();
}
diff --git a/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy b/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy
index 47dda1c5dc..7f95e2d3fc 100644
--- a/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy
+++ b/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy
@@ -3428,7 +3428,7 @@ final class GenericsJavaCompatibilityTest {
}
// =========================================================================
- // Category 17: Parameter-type bridges, capture conversion, varargs heap
+ // Category 17 (continued): Parameter-type bridges, capture conversion, varargs heap
// pollution, catch of a type variable, nested type arguments, extra
// interface type parameters, and diamond on a generic class with a
// generic constructor (JLS 4.5, 4.12.2, 5.1.10, 8.4.8.3, 8.8.4, 14.20)
diff --git a/src/test/groovy/org/apache/groovy/ast/tools/TypeUseUtilsTest.groovy b/src/test/groovy/org/apache/groovy/ast/tools/TypeUseUtilsTest.groovy
new file mode 100644
index 0000000000..1edd679d8d
--- /dev/null
+++ b/src/test/groovy/org/apache/groovy/ast/tools/TypeUseUtilsTest.groovy
@@ -0,0 +1,109 @@
+/*
+ * 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 org.apache.groovy.ast.tools
+
+import org.codehaus.groovy.ast.ClassHelper
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.GenericsType
+import org.junit.jupiter.api.Test
+
+import static org.apache.groovy.ast.tools.TypeUseUtils.describeTypeUse
+import static org.apache.groovy.ast.tools.TypeUseUtils.isParameterizedEnclosingType
+import static org.apache.groovy.ast.tools.TypeUseUtils.isParameterizedTypeUsage
+import static org.apache.groovy.ast.tools.TypeUseUtils.isParameterizedTypeUsageIncludingEnclosing
+import static org.apache.groovy.ast.tools.TypeUseUtils.isRawGenericType
+import static org.apache.groovy.ast.tools.TypeUseUtils.isReifiable
+import static org.junit.jupiter.api.Assertions.assertEquals
+import static org.junit.jupiter.api.Assertions.assertFalse
+import static org.junit.jupiter.api.Assertions.assertTrue
+
+/**
+ * Tests for {@link TypeUseUtils}.
+ */
+final class TypeUseUtilsTest {
+
+ @Test
+ void 'declaration formals are not a parameterized usage'() {
+ ClassNode list = ClassHelper.LIST_TYPE
+ assertFalse(isParameterizedTypeUsage(list))
+ assertFalse(list.redirectNode)
+ }
+
+ @Test
+ void 'use-site arguments are a parameterized usage'() {
+ ClassNode usage = ClassHelper.LIST_TYPE.plainNodeReference
+ usage.genericsTypes = [new GenericsType(ClassHelper.STRING_TYPE)] as GenericsType[]
+ assertTrue(isParameterizedTypeUsage(usage))
+ assertTrue(isParameterizedEnclosingType(usage))
+ assertFalse(isReifiable(usage))
+ assertEquals('List<java.lang.String>', describeTypeUse(usage))
+ }
+
+ @Test
+ void 'raw generic type is reifiable'() {
+ ClassNode raw = ClassHelper.LIST_TYPE.plainNodeReference
+ raw.genericsTypes = null
+ assertTrue(isRawGenericType(raw))
+ assertTrue(isReifiable(raw))
+ assertFalse(isParameterizedTypeUsage(raw))
+ }
+
+ @Test
+ void 'unbounded wildcard is reifiable'() {
+ ClassNode star = ClassHelper.LIST_TYPE.plainNodeReference
+ GenericsType wildcard = new GenericsType(ClassHelper.OBJECT_TYPE, null, null)
+ wildcard.wildcard = true
+ star.genericsTypes = [wildcard] as GenericsType[]
+ assertTrue(isReifiable(star))
+ assertTrue(isReifiable(star.makeArray()))
+ }
+
+ @Test
+ void 'type parameter and diamond are not reifiable'() {
+ ClassNode placeholder = ClassHelper.makeWithoutCaching('T')
+ placeholder.genericsPlaceHolder = true
+ placeholder.redirect = ClassHelper.OBJECT_TYPE
+ assertFalse(isReifiable(placeholder))
+ assertEquals('T', describeTypeUse(placeholder))
+
+ ClassNode diamond = ClassHelper.LIST_TYPE.plainNodeReference
+ diamond.genericsTypes = GenericsType.EMPTY_ARRAY
+ assertFalse(isReifiable(diamond))
+ }
+
+ @Test
+ void 'generic type name of Map Entry is not a parameterized class literal'() {
+ ClassNode map = ClassHelper.MAP_TYPE
+ ClassNode entry = ClassHelper.makeWithoutCaching('java.util.Map$Entry')
+ entry.redirect = ClassHelper.make(Map.Entry)
+ // Declaration formals on Map must not make Map.Entry a class-literal error.
+ assertFalse(isParameterizedTypeUsage(map))
+ assertFalse(isParameterizedTypeUsageIncludingEnclosing(entry))
+ }
+
+ @Test
+ void 'rare type with parameterized enclosing is a parameterized class literal'() {
+ ClassNode outer = ClassHelper.LIST_TYPE.plainNodeReference
+ outer.genericsTypes = [new GenericsType(ClassHelper.STRING_TYPE)] as GenericsType[]
+ ClassNode inner = ClassHelper.makeWithoutCaching('java.util.List$Inner')
+ inner.outerClassType = outer
+ assertTrue(isParameterizedTypeUsageIncludingEnclosing(inner))
+ assertEquals('List<java.lang.String>.Inner', describeTypeUse(inner))
+ }
+}
diff --git a/src/test/groovy/org/codehaus/groovy/ast/ClassNodeTest.java b/src/test/groovy/org/codehaus/groovy/ast/ClassNodeTest.java
index d9b9ae6b52..247fe1a701 100644
--- a/src/test/groovy/org/codehaus/groovy/ast/ClassNodeTest.java
+++ b/src/test/groovy/org/codehaus/groovy/ast/ClassNodeTest.java
@@ -19,6 +19,7 @@
package org.codehaus.groovy.ast;
import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.ast.stmt.EmptyStatement;
import org.codehaus.groovy.ast.tools.GenericsUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -195,6 +196,23 @@ public final class ClassNodeTest {
assertEquals(outer, classNode.getOuterClassType());
}
+ @Test // GROOVY-12319
+ public void testAsGenericsTypeCopiesGenericDeclaration() {
+ MethodNode method = new MethodNode("m", ACC_PUBLIC, ClassHelper.OBJECT_TYPE,
+ Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, EmptyStatement.INSTANCE);
+ ClassNode placeholder = ClassHelper.makeWithoutCaching("T");
+ placeholder.setGenericsPlaceHolder(true);
+ placeholder.setRedirect(ClassHelper.OBJECT_TYPE);
+ GenericsType header = new GenericsType(placeholder, null, null);
+ header.setPlaceholder(true);
+ header.setGenericDeclaration(method);
+ placeholder.setGenericsTypes(new GenericsType[]{header});
+
+ GenericsType copied = placeholder.asGenericsType();
+ assertEquals(method, copied.getGenericDeclaration());
+ assertTrue(copied.isPlaceholder());
+ }
+
@Test
public void testPermittedSubclasses() throws Exception {
assumeTrue(Runtime.version().feature() >= 17);