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

   AI read:
   
   > Thanks Eric, I think the direction is promising. The predicate gating on 
`type.dim0()` holds up in practice. Untyped declarations, `Object o = {}`, and 
closures with params, `->`, `;` or labels all stay closures, so the change only 
kicks in where it should. For non-closure element types it's a clear win:
   > 
   > | Code | master | PR |
   > |---|---|---|
   > | `String[] a = {}` | 1-element array holding the closure | `[]` |
   > | `Boolean[] a = { false }` | `[true]` (closure → asBoolean) | `[false]` |
   > | `final int[] a = {1}`, `int[] a = {1,}`, `Object[] a = {1, 2}` | fail | 
work |
   > | `@CompileStatic String[] s = { 'a' }` | STC error | works |
   > 
   > The friction is where the element type can accept a closure, i.e. the 
GROOVY-8561 territory from 2018 (PR 691 didn't get the three +1s back then). 
Here are the edge cases I found, all run against your branch:
   > 
   > **Behaviour changes for Closure/SAM/Object element types**
   > 
   > | Code | master | PR |
   > |---|---|---|
   > | `Closure[] a = {}` | `[Closure]` | `[]` |
   > | `def m(Closure[] cs = {})` | `[Closure]` | `[]` |
   > | `Object[] o = {}` | `[Closure]` | `[]` |
   > | `Closure[] a = { 1 }` | `[Closure]` | runtime failure |
   > | `Closure[] a = { it }` | `[Closure]` | runtime failure (no `it`) |
   > | `String[] s = { it }` | 1-element array holding the closure | runtime 
failure |
   > | `Runnable[] r = { println 'x' }` | `[Closure]` | compile error (command 
chain not allowed in array initializer) |
   > | `Closure[] a = { println('x') }` | `[Closure]` | **prints `x` at 
declaration time and stores `[null]`** |
   > 
   > The last one worries me most because it changes behaviour silently. Any 
`Closure[]`/`Runnable[]`/`Callable[]` declaration whose single body expression 
isn't a command chain now runs eagerly, and its result (often `null`) passes 
the cast.
   > 
   > **Body shape decides the meaning.** `Closure[] a = { it }` is an array but 
`{ it -> it }` is a closure. `String[] s = { 'a' }` is an array but `{ 'a'; 'b' 
}` and `{ 'a' }.call()` are closures. It's consistent with the grammar, but 
hard to explain to users.
   > 
   > **Nested braces crash the parser.** `Object[] o = { {} }` and `Closure[] 
cs = { {}, {} }` fail with "General error during parsing: Cannot invoke 
ClassNode.makeArray() because base is null". This already happens on master 
with the long form (`new Object[]{{}}`), see the `//if (subType == null) 
produce closure or throw exception` TODO in `visitArrayInitializer`. Your 
branch just makes it much easier to hit. It also blocks the escape hatch 8561 
documented (`Closure[] fns = { { } }`). Taking the "produce closure" branch of 
that TODO would fix the crash and restore the escape hatch.
   > 
   > **Other bits**
   > - Only a single declarator is handled: `int[] a = {1}, b = {2}` (valid 
Java) falls through to the closure path and fails at runtime with a cast error.
   > - `def m( {` now reports the missing `)` at line 2 col 2 instead of at the 
`{` (line 1 col 8). That comes from the `formalParameter` restructuring.
   > - `String[] a = { ['a', 'b'] }` gives `["[a, b]"]` (length 1, element is a 
`String`) in both dynamic and `@CompileStatic`: the list is coerced via 
toString. The long form `new String[]{ ['a','b'] }` does exactly that on master 
too, so it isn't new, but the terse form makes it easier to write, and Java 
would reject it.
   > 
   > **Possible paths forward**
   > - **A. Keep the current semantics (Java-faithful).** This has the simplest 
rule but is a breaking change for Closure/SAM/Object arrays, including the 
silent eager-evaluation case. We'd need a dev-list decision (given the 8561 
precedent), a COMPATIBILITY note, and ideally a compile-time diagnostic when a 
Closure/SAM-typed array element isn't a closure/lambda/method ref. That 
diagnostic can't be exact in dynamic code, e.g. `{ getHandler() }` is 
legitimate.
   > - **B. Type-aware fallback (my preference).** Use the array reading, 
except when the initializer is empty or has a single element *and* the leaf 
component type is `Closure`, a SAM type, or (TBD) `Object`. In that case keep 
today's closure promotion. Multi-element and nested initializers would always 
be arrays. Nothing that works today breaks, and Java users get the forms they 
actually paste (`String[]`, `int[]`, `int[][]`, …). The catch is that SAM-ness 
is only known after resolution. AstBuilder could build both readings for the 
empty/single-element shapes and pick one post-resolution. I'm not sure where 
that hook best lives, and I'm curious what you think.
   > - **C. Only shapes no closure body can parse as.** Treat the braces as an 
array only when there's a top-level comma or nested braces. It's cheap and 
non-breaking, but `String[] s = {}`, probably the most common pasted Java 
idiom, would keep today's odd result, so it doesn't really meet the ticket's 
goal.
   > 
   > Whichever way we go, fixing the nested-brace NPE seems worthwhile on its 
own, since it's a crash on master today. Tests pinning down the chosen 
semantics for `{}`, `{ it }`, `{ -> }`, `{ { } }`, `Runnable[] r = { … }` and 
`Object[] o = {}` would also help.


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