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 14:47:37 UTC

(groovy) branch generics-tck updated: GROOVY-12319: cover parameter-type bridges and remaining JLS generics cases

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 dcf1c7a5f0 GROOVY-12319: cover parameter-type bridges and remaining JLS generics cases
dcf1c7a5f0 is described below

commit dcf1c7a5f056425402f7133109daa0997c9bf1fa
Author: Daniel Sun <su...@apache.org>
AuthorDate: Mon Aug 31 14:46:41 2026 +0000

    GROOVY-12319: cover parameter-type bridges and remaining JLS generics cases
    
    Reject catching a type parameter (JLS 14.20). Extend
    GenericsJavaCompatibilityTest with parameter-type bridges, capture
    conversion without a helper, varargs heap pollution, nested type
    arguments, extra interface type parameters, and diamond on a generic
    class with a generic constructor.
---
 .../codehaus/groovy/control/GenericsVisitor.java   |  14 +
 .../generics/GenericsJavaCompatibilityTest.groovy  | 300 +++++++++++++++++++++
 2 files changed, 314 insertions(+)

diff --git a/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java b/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java
index a27b2d5897..bf6a5cfd79 100644
--- a/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java
+++ b/src/main/java/org/codehaus/groovy/control/GenericsVisitor.java
@@ -36,6 +36,7 @@ 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.ast.stmt.CatchStatement;
 import org.codehaus.groovy.syntax.Types;
 import org.codehaus.groovy.transform.trait.Traits;
 
@@ -118,6 +119,19 @@ public class GenericsVisitor extends ClassCodeVisitorSupport {
         super.visitField(node);
     }
 
+    /**
+     * JLS 14.20: a catch type must be a reifiable class or union; a type
+     * parameter is not reifiable and may not be caught.
+     */
+    @Override
+    public void visitCatchStatement(final CatchStatement statement) {
+        ClassNode type = statement.getExceptionType();
+        if (type != null && type.isGenericsPlaceHolder()) {
+            addError("Cannot catch type parameter " + type.getUnresolvedName(), statement);
+        }
+        super.visitCatchStatement(statement);
+    }
+
     /**
      * Validates generic usage for a constructor or method signature.
      *
diff --git a/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy b/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy
index e888999e2b..2ca8dca16c 100644
--- a/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy
+++ b/src/test/groovy/gls/generics/GenericsJavaCompatibilityTest.groovy
@@ -3206,6 +3206,306 @@ final class GenericsJavaCompatibilityTest {
         assertNegativeCompile(className, javaSrc, "incompatible types", "Cannot call")
     }
 
+    // =========================================================================
+    // Category 17: 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)
+    // =========================================================================
+
+    /**
+     * JLS 8.4.8.3, 15.12.4.5: {@code IntCell.put(Integer)} does not override
+     * the erasure {@code Cell.put(Object)}. The compiler emits a bridge
+     * {@code put(Object)} that casts to {@code Integer} and delegates. A raw
+     * {@code Cell} call with a {@code String} therefore throws
+     * {@code ClassCastException} at the bridge. This is a parameter-type
+     * bridge, distinct from the covariant-return bridges in
+     * {@link #testCovariantOverrideDeclaresBridgeMethod()}.
+     */
+    @Test
+    void testParameterTypeBridgeMethod() {
+        final String className = 'gls.generics.test.ParameterTypeBridge'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.lang.reflect.Method;
+            public class ParameterTypeBridge {
+                public static class Cell<T> {
+                    public T value;
+                    public Cell(T value) { this.value = value; }
+                    public void put(T value) { this.value = value; }
+                }
+                public static class IntCell extends Cell<Integer> {
+                    public IntCell(Integer value) { super(value); }
+                    public void put(Integer value) { super.put(value); }
+                }
+                public static String test() {
+                    boolean bridge = false;
+                    boolean specialized = false;
+                    for (Method m : IntCell.class.getDeclaredMethods()) {
+                        if (!"put".equals(m.getName()) || m.getParameterCount() != 1) {
+                            continue;
+                        }
+                        if (m.isBridge() && m.getParameterTypes()[0] == Object.class) {
+                            bridge = true;
+                        }
+                        if (!m.isBridge() && m.getParameterTypes()[0] == Integer.class) {
+                            specialized = true;
+                        }
+                    }
+                    IntCell cell = new IntCell(Integer.valueOf(5));
+                    Cell<Integer> typed = cell;
+                    typed.put(Integer.valueOf(7));
+                    boolean cce = false;
+                    try {
+                        invokeRaw(cell, "Hello");
+                    } catch (ClassCastException e) {
+                        cce = true;
+                    }
+                    return (bridge && specialized && Integer.valueOf(7).equals(cell.value) && cce) ? "ok" : "fail";
+                }
+                @SuppressWarnings({"rawtypes", "unchecked"})
+                public static void invokeRaw(Cell cell, Object item) {
+                    cell.put(item);
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'ok', true)
+    }
+
+    /**
+     * JLS 5.1.10: {@code list.set(0, list.get(0))} on {@code List<?>} is a
+     * capture error — {@code get} yields {@code Object}, which is not the
+     * fresh capture of {@code ?}. A helper {@code <T> void f(List<T>)} is
+     * the legal workaround ({@link #testWildcardCaptureHelper()}).
+     */
+    @Test
+    void testNegativeWildcardCaptureWriteWithoutHelper() {
+        final String className = 'gls.generics.test.WildcardCaptureWrite'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.List;
+            public class WildcardCaptureWrite {
+                public static void test(List<?> list) {
+                    list.set(0, list.get(0));
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "incompatible types", "Cannot call")
+    }
+
+    /**
+     * JLS 5.1.10: two {@code List<? extends Number>} formals capture independently.
+     * An element taken from one cannot be stored in the other, even though both
+     * bounds are {@code Number}.
+     */
+    @Test
+    void testNegativeIndependentWildcardCaptures() {
+        final String className = 'gls.generics.test.IndependentCaptures'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.List;
+            public class IndependentCaptures {
+                public static void test(List<? extends Number> from, List<? extends Number> to) {
+                    to.set(0, from.get(0));
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "incompatible types", "Cannot call")
+    }
+
+    /**
+     * JLS 4.10.2, 4.5.1: {@code List<? extends Integer>} is a subtype of
+     * {@code List<? extends Number>} (containment of upper bounds), even though
+     * {@code List<Integer>} is not a subtype of {@code List<Number>}.
+     */
+    @Test
+    void testUpperBoundedWildcardSubtyping() {
+        final String className = 'gls.generics.test.UpperBoundedWildcardSubtyping'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.ArrayList;
+            import java.util.List;
+            public class UpperBoundedWildcardSubtyping {
+                public static int test() {
+                    List<? extends Integer> ints = new ArrayList<Integer>();
+                    List<? extends Number> nums = ints;
+                    return nums.size();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 0)
+    }
+
+    /**
+     * JLS 8.4.4, 4.4: a generic method may invoke members of an F-bound
+     * ({@code <T extends Comparable<T>> int countGreater(T[], T)} uses
+     * {@code compareTo}, not a primitive {@code >}).
+     */
+    @Test
+    void testGenericMethodUsesFBoundMembers() {
+        final String className = 'gls.generics.test.CountGreaterThan'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class CountGreaterThan {
+                public static <T extends Comparable<T>> int countGreaterThan(T[] values, T elem) {
+                    int n = 0;
+                    for (T value : values) {
+                        if (value.compareTo(elem) > 0) {
+                            n++;
+                        }
+                    }
+                    return n;
+                }
+                public static int test() {
+                    return countGreaterThan(new Integer[] {1, 5, 3, 8}, Integer.valueOf(4));
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 2)
+    }
+
+    /**
+     * JLS 4.5: a type argument may itself be parameterized
+     * ({@code Pair<String, Box<Integer>>}).
+     */
+    @Test
+    void testNestedParameterizedTypeArgument() {
+        final String className = 'gls.generics.test.NestedTypeArgument'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class NestedTypeArgument {
+                public static class Box<T> {
+                    public final T value;
+                    public Box(T value) { this.value = value; }
+                }
+                public static class Pair<K, V> {
+                    public final K key;
+                    public final V value;
+                    public Pair(K key, V value) { this.key = key; this.value = value; }
+                }
+                public static int test() {
+                    Pair<String, Box<Integer>> pair = new Pair<>("n", new Box<>(Integer.valueOf(9)));
+                    return pair.key.length() + pair.value.value.intValue();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 10)
+    }
+
+    /**
+     * JLS 15.9.3, 8.8.4: diamond infers the class type parameter from the
+     * assignment target and the constructor type parameter from the argument
+     * ({@code class Holder<X> { <T> Holder(T t, X x) }} instantiated as
+     * {@code new Holder<>("tag", 9)}).
+     */
+    @Test
+    void testDiamondGenericConstructorOfGenericClass() {
+        final String className = 'gls.generics.test.GenericClassGenericCtor'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class GenericClassGenericCtor {
+                public static class Holder<X> {
+                    public final X x;
+                    public final String tag;
+                    public <T> Holder(T t, X x) {
+                        this.tag = t.toString();
+                        this.x = x;
+                    }
+                }
+                public static String test() {
+                    Holder<Integer> h = new Holder<>("n", Integer.valueOf(9));
+                    return h.tag + h.x.intValue();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'n9')
+    }
+
+    /**
+     * JLS 9.1.2, 4.10.2: an interface may add a type parameter while extending
+     * a parameterization of another generic interface. {@code Tagged<E, P>
+     * extends Cell<E>} makes {@code Tagged<String, Integer>} a subtype of
+     * {@code Cell<String>}.
+     */
+    @Test
+    void testInterfaceAddsTypeParameterWhenExtending() {
+        final String className = 'gls.generics.test.TaggedCell'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class TaggedCell {
+                public interface Cell<E> {
+                    E get();
+                }
+                public interface Tagged<E, P> extends Cell<E> {
+                    P tag();
+                }
+                public static class Impl implements Tagged<String, Integer> {
+                    @Override public String get() { return "x"; }
+                    @Override public Integer tag() { return Integer.valueOf(3); }
+                }
+                public static String test() {
+                    Cell<String> cell = new Impl();
+                    Tagged<String, Integer> tagged = new Impl();
+                    return cell.get() + tagged.tag().intValue();
+                }
+            }
+        '''
+        assertPositive(className, javaSrc, 'x3', true)
+    }
+
+    /**
+     * JLS 4.12.2, 8.4.1: a varargs formal of a parameterized type is compiled
+     * as an array of the erasure. Assigning that array to {@code Object[]} and
+     * storing a {@code List<Integer>} pollutes a {@code List<String>...}, so a
+     * later {@code get(0)} as {@code String} throws {@code ClassCastException}.
+     */
+    @Test
+    void testVarargsHeapPollutionClassCastException() {
+        final String className = 'gls.generics.test.VarargsHeapPollution'
+        final String javaSrc = '''
+            package gls.generics.test;
+            import java.util.Arrays;
+            import java.util.List;
+            public class VarargsHeapPollution {
+                @SuppressWarnings({"unchecked", "varargs"})
+                public static void pollute(List<String>... lists) {
+                    store(lists, Arrays.asList(Integer.valueOf(42)));
+                    int n = lists[0].get(0).length();
+                    throw new AssertionError(Integer.valueOf(n));
+                }
+                @SuppressWarnings({"rawtypes", "unchecked"})
+                public static void store(Object[] dest, Object value) {
+                    dest[0] = value;
+                }
+                public static void test() {
+                    pollute(Arrays.asList("a"), Arrays.asList("b"));
+                }
+            }
+        '''
+        assertNegativeRuntime(className, javaSrc, ClassCastException.class)
+    }
+
+    /**
+     * JLS 14.20: a catch type must be a reifiable class or union. A type
+     * parameter, even when bounded by {@code Exception}, is not reifiable.
+     */
+    @Test
+    void testNegativeCatchTypeParameter() {
+        final String className = 'gls.generics.test.CatchTypeParameter'
+        final String javaSrc = '''
+            package gls.generics.test;
+            public class CatchTypeParameter {
+                public static <E extends Exception> void test() {
+                    try {
+                        throw new Exception();
+                    } catch (E e) {
+                    }
+                }
+            }
+        '''
+        assertNegativeCompile(className, javaSrc, "type parameter", "Cannot catch type parameter")
+    }
+
     // =========================================================================
     // Test Harness Helper Methods
     // =========================================================================