[ 
https://issues.apache.org/jira/browse/GROOVY-12319?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18109734#comment-18109734
 ] 

ASF GitHub Bot commented on GROOVY-12319:
-----------------------------------------

paulk-asert commented on PR #2845:
URL: https://github.com/apache/groovy/pull/2845#issuecomment-5472695848

   I am generally in favor of this change but want to draw attention to one 
aspect before we merge as per this AI read.
   
   > Alongside the three new syntax forms, the `GenericsVisitor` changes add 
JLS well-formedness checks that run on **every class — dynamic Groovy and 
scripts included, before AST transforms, with no opt-out**. I ran probes on 
4.0.27, 5.0.8, 6.0.0-beta-3 and builds of both PR heads (`f0de882dd4`, 
`86f1ab3e3c`): **eleven forms that compile and run today (and have since at 
least 4.0) become compile errors**. Each is Java-consistent, and several expose 
silently-wrong code, so break-and-document is defensible for all of them — but 
we should decide that knowingly, per form:
   > 
   > | Form | Behaviour ≤ beta-3 (dynamic) | Suggest |
   > |---|---|---|
   > | `new T[n]`, `new T[]{t}` in `class G<T>` | `Object[]` typed as `T[]` — 
the Groovy shortcut for Java's `(T[]) new Object[n]` | **team call** — the one 
form with a real constituency; a small carve-out in `isReifiable` for bare type 
variables is possible |
   > | `class E<T> extends Exception` | works; no JVM/Groovy reason to forbid, 
pure javac parity | **team call** — cheapest to leave if we want to minimise 
breakage |
   > | `new List<String>[n]` | raw array, args ignored | break & document (`new 
List<?>[n]` still works) |
   > | `List<int>` (and nested) | dynamic: treated as raw | break & document 
(STC already rejected it with a worse message) |
   > | `new T()` / `T.class` | silently `Object` / `Object.class` | break & 
document — exposes wrong code; point to the `Class<T>` token idiom |
   > | `implements X<? extends …>` | works (superclass form was already 
rejected) | break & document |
   > | `new ArrayList<?>()` | works, harmless | break & document (parity) |
   > | class type in additional bound `<T extends A & B>` | works | break & 
document |
   > | `Foo<String>.class` | evaluates to `Foo.class` | break & document, but 
the message needs fixing (see below) |
   > | `Outer.Inner<String>` with raw generic `Outer` (added in the follow-up) 
| works | break & document (fix: `Outer<?>.Inner<String>`, expressible thanks 
to this PR) |
   > 
   > Not breaking, for the record: `new Comparator<int>() { … }` already failed 
at runtime with a corrupt `Signature` (`GenericSignatureFormatError`) since 4.x 
— the new error is a fix; the stricter `instanceof` forms and the 
static-context `T` reference were already errors (clearer messages now); 
third-party AST transforms are safe (they run after the `GenericsVisitor` phase 
op — verified); and since the follow-up, inner-class signatures of existing 
code are unchanged.
   > 
   > Two message nits: `Foo<String>.class` still gets "Cannot refer to a static 
member of a generic type through a parameterization" (the class-literal path 
missed the message pass — javac says "cannot select from parameterized type"); 
and `new T[n]` reports "generic array creation **of Object**", which names the 
erasure rather than the type variable and reads like a different complaint.
   > 
   > One API note: the follow-up stores the enclosing type as a `ClassNode` 
field and no longer writes the `"outer.class"` node metadata — fine by me, just 
noting that any external code reading that key (GROOVY-10646 era) now sees 
`null`.
   
   Most breaking changes seem fine - we need to add a table like above in 
release notes and possibly in `core-object-orientation` §Generics. I am happy 
to work on these separately. It is really only the first two rows which I 
wonder about - spelt out below.
   
   > Both workarounds verified on the PR install (dynamic and 
`@CompileStatic`). Here are the two team-call rows as complete, paste-ready 
samples — each with the code that works today, the exact PR error, and the 
verified workaround.
   > 
   > ## Team call 1 — `new T[n]` / `new T[]{…}` (generic array creation)
   > 
   > **Works on ≤ 6.0.0-beta-3 (and 5.x, 4.x), dynamic and `@CS`; errors on the 
PR:**
   > 
   > ```groovy
   > // a typed fixed-capacity stack — the kind of generic utility class where 
this idiom shows up
   > class Stack1<T> {
   >     private T[] items
   >     private int size = 0
   >     Stack1(int capacity) { items = new T[capacity] }       // PR: generic 
array creation of Object
   >     void push(T t) { items[size++] = t }
   >     T pop() { items[--size] }
   >     T[] snapshot() { new T[] { items[0] } }                // PR: generic 
array creation of Object
   > }
   > def s = new Stack1<String>(4)
   > s.push('a'); s.push('b')
   > assert s.pop() == 'b'
   > ```
   > 
   > On beta-3 both array creations quietly build an `Object[]` (erasure) and 
everything runs. On the PR each line fails with `generic array creation of 
Object` — note the message names the erasure, not `T`, which is the wording nit 
in the draft comment.
   > 
   > **Workaround (the Java idiom; verified OK on the PR build, both modes):**
   > 
   > ```groovy
   > class Stack2<T> {
   >     private T[] items
   >     private int size = 0
   >     Stack2(int capacity) { items = (T[]) new Object[capacity] }   // 
reifiable creation + unchecked cast
   >     void push(T t) { items[size++] = t }
   >     T pop() { items[--size] }
   > }
   > def s = new Stack2<String>(4)
   > s.push('a'); s.push('b')
   > assert s.pop() == 'b'
   > ```
   > 
   > The team question: is forcing that rewrite worth the Java parity, or do we 
carve out bare type variables in `isReifiable` (keeping `new List<String>[n]` 
rejected while `new T[n]` stays legal)?
   > 
   > ## Team call 2 — generic class extending `Throwable`
   > 
   > **Works on ≤ beta-3, dynamic and `@CS`; errors on the PR:**
   > 
   > ```groovy
   > // a DSL-style exception carrying a typed payload — the plausible 
real-world shape
   > class ValidationException<T> extends RuntimeException {
   >     final T details                                        // PR: A 
generic class may not extend java.lang.Throwable
   >     ValidationException(String msg, T details) { super(msg); this.details 
= details }
   > }
   > 
   > try {
   >     throw new ValidationException<Map>('bad input', [field: 'name'])
   > } catch (ValidationException<Map> ignored) {               // note: 
parameterized catch never made sense…
   >     // …
   > } catch (ValidationException e) {
   >     assert e.details.field == 'name'
   > }
   > ```
   > 
   > Java forbids this (JLS 8.1.2) because `catch` clauses can't be reified — 
but Groovy's `catch` is erased anyway, so the class runs fine on the JVM; the 
rejection is pure javac parity. That's why this is the cheapest row to *leave* 
if the team wants to minimise breakage.
   > 
   > **Workaround (verified OK on the PR build, both modes):**
   > 
   > ```groovy
   > class ValidationException extends RuntimeException {
   >     private final Object payload
   >     ValidationException(String msg, Object payload) { super(msg); 
this.payload = payload }
   >     def <T> T payload() { (T) payload }        // generics move from the 
class to the accessor
   > }
   > 
   > try {
   >     throw new ValidationException('bad input', [field: 'name'])
   > } catch (ValidationException e) {
   >     Map m = e.<Map>payload()
   >     assert m.field == 'name'
   > }
   > ```
   > 
   > (A variant that keeps static typing without the explicit type witness — 
implementing `Supplier<Map>` or exposing a concretely-typed field — also 
verified fine.)
   




> Java compatibility: remaining generic type syntax
> -------------------------------------------------
>
>                 Key: GROOVY-12319
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12319
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Daniel Sun
>            Priority: Major
>
> Groovy already accepts most Java generics. Three Java forms still fail: two 
> in the parser, one in {{GenericsVisitor}}. Each has a local workaround.
> Method type arguments such as {{Helper.<String>identity( x )}} already work 
> and are not part of this request.
> h3. 1. Diamond {{<>}} on an anonymous class
> Java 9+ (JEP 213, JLS 15.9.5) allows diamond when creating an anonymous 
> class, if the target type supplies the type arguments:
> {code:java}
> Processor<String> p = new Processor<>() {
>     public String process(String val) { return val.toUpperCase(); }
> };
> {code}
> This should work in assignment, as a method argument, and under 
> {{@TypeChecked}} / {{@CompileStatic}}, for both interfaces and abstract 
> classes.
> *Actual:* Groovy rejects it with {{Cannot use diamond <> with anonymous inner 
> classes}}.
> *Workaround:* write the type arguments explicitly:
> {code:java}
> Processor<String> p = new Processor<String>() {
>     public String process(String val) { return val.toUpperCase(); }
> };
> {code}
> GROOVY-6730 and GROOVY-7159 were false-positive STC errors when diamond was 
> _not_ used. They did not add this form.
> h3. 2. Qualified parameterized inner types ("rare" types)
> Java allows an inner type to keep the enclosing type's arguments (JLS 4.5):
> {code:java}
> class Outer<T> {
>     class Inner<U> {}
> }
> Outer<String>.Inner<Integer> x = new Outer<String>().new Inner<Integer>("v", 
> 1);
> {code}
> The same qualification appears as a field or method type, nested in another 
> type argument, as a superclass when the member is a non-static class, and in 
> a qualified instance creation inside the outer class:
> {code:java}
> List<Outer<String>.Inner<Integer>> list;
> class Sub extends Outer<String>.Inner {
>     Sub(Outer<String> o) { o.super(); }
> }
> // inside Outer:
> new Outer<T>.Inner<U>(...)
> {code}
> Selecting a static member type from a parameterization is a compile-time 
> error (JLS 4.5.2 / 6.5.5). Nested interfaces and enums are implicitly static 
> (JLS 9.5), so a nested interface must be selected from the raw enclosing name:
> {code:java}
> class Impl implements Outer.Inner<Integer> { ... }           // legal
> class Bad  implements Outer<String>.Inner<Integer> { ... }   // error
> {code}
> *Actual:* Groovy fails to parse {{Outer<String>.Inner}} ({{Unexpected 
> input}}).
> *Workaround:* a factory that returns {{Inner}} without naming 
> {{Outer<T>.Inner}}.
> h3. 3. Explicit type arguments on constructors, {{this()}} and {{super()}}
> Java allows constructor type arguments independently of the class type 
> arguments (JLS 15.9 / 8.8.7.1):
> {code:java}
> class Box {
>     <T> Box(T t) {}
>     Box() { <String>this("x"); }
> }
> class Derived extends Box {
>     Derived() { <String>super("y"); }
> }
> class Outer {
>     class Inner {
>         <T> Inner(T t) {}
>     }
> }
> new <String>Box("x");
> new Outer().new <String>Inner("z");
> {code}
> *Actual:* Groovy fails at {{new <}} with {{Unexpected input: '<'}}.
> *Workaround:* inference, e.g. {{new Box("x")}}.
> Constructor type arguments are already tracked by GROOVY-10501. The 
> {{this()}} / {{super()}} / inner-{{new}} forms are the same JLS production 
> and should be handled together.
> h3. Expected
> All three forms compile in dynamic Groovy and under {{@TypeChecked}} / 
> {{@CompileStatic}}, matching javac on well-formed programs.
> The well-formedness rules are javac's (checked against javac 25). Parsing a 
> rare type does not make every use of it legal.
> These remain legal. {{Inner}} is a non-static member of {{Outer}}; 
> {{Outer<?>}} is reifiable (JLS 4.7), so a non-static member type of that 
> enclosing type is reifiable (JLS 15.10.1):
> {code:java}
> class Outer<T> {
>     class Inner {}
>     class InnerG<U> {}
>     interface Iface<U> { U id(U u); }
> }
> Outer<?>.Inner[] a = new Outer<?>.Inner[0];
> Outer<?>.InnerG<?>[] b = new Outer<?>.InnerG<?>[0];
> Outer<?>.Inner field;
> Outer<String>.Inner concrete;
> class Impl implements Outer.Iface<Integer> {
>     public Integer id(Integer u) { return u; }
> }
> {code}
> These remain compile errors:
> {code:java}
> new Object<>() {}                          // diamond on a non-generic type
> class C extends ArrayList<> {}             // diamond on a class declaration
> List<> list;                               // diamond on a field
> new <String>Box<>("x")                     // diamond combined with 
> constructor type arguments
> Outer<String, Integer>.Inner x;            // wrong arity
> x instanceof Outer<String>.Inner           // parameterized type is not 
> reifiable
> new Outer<String>.Inner[1]                 // generic array creation (JLS 
> 15.10.1)
> new Outer<?>.Nested[0]                     // static member from a 
> parameterized type (JLS 6.5.5)
> Outer<?>.Nested z;                         // same rule as a type name
> new Outer<?>.InnerG[0]                     // raw generic member of a 
> parameterized enclosing type
> class Bad implements Outer<String>.Iface<Integer> {} // nested interface is 
> implicitly static (JLS 9.5)
> new java.util.Map<?,?>.Entry[0]            // Map.Entry is a nested interface
> new Outer<?>().new Inner()                 // constructor type argument may 
> not be a wildcard
> {code}
> The {{Nested}} cases assume {{static class Nested}} inside {{Outer}}. The 
> {{InnerG}} array error is the raw-member form; the legal counterpart is {{new 
> Outer<?>.InnerG<?>[0]}} above.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to