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.)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to