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

   Thanks both for the reviews. The branch has been rebased onto current master 
and now has two commits: the squashed implementation (74ed5d53d2) and a 
follow-up that reads record components through their accessors (b11e3fc2dc). 
This comment describes the PR as it stands. The original description above 
tells how it got here, and its public API list is still accurate.
   
   ## What this PR provides
   
   **The language feature (GEP-19)**
   
   - A `case` label can be a pattern, and so can the right operand of 
`instanceof`:
     - type patterns with a binding: `case String s ->`
     - record patterns: `case Point(int x, var y) ->`, `o instanceof Point(var 
x, _)`
     - list patterns with an optional rest: `case [1, var second, var... rest] 
->`
     - map patterns over named entries: `case [name: var n, age: _] ->`
     - `var x` bindings, `_` wildcards, constants, and nested patterns anywhere 
a component is expected
   - A `when` guard refines an arm.
   - Pattern labels are arrow-only. Colon labels, `isCase` labels and the 
existing `case Foo:` class labels keep their meaning, and a switch can mix them 
with pattern arms.
   - Plain `o instanceof T t` keeps JEP 394 flow scoping.
   - Primitive type patterns follow JEP 507.
   
   **Type checking, under `@TypeChecked` / `@CompileStatic`**
   
   - A pattern switch is exhaustive when an arm is unconditional for the 
selector type or the arms cover a sealed hierarchy.
   - A non-exhaustive switch expression is an error, as in Groovy 6.
   - A non-exhaustive switch statement with a pattern label is also an error, 
as in Java (JEP 441). A statement without pattern labels needs no `default`, so 
existing code keeps compiling.
   - The type checker also reports a pattern that can't match the selector, a 
record pattern of the wrong arity, and an arm dominated by an earlier one.
   
   **The AST**
   
   - A pattern is a `MatchPattern`: a sealed model of records in 
`org.apache.groovy.ast.pattern`, `@Incubating`. Its bindings are the declaring 
`VariableExpression`s.
   - A `PatternExpression` holds the `MatchPattern`. It is the case label or 
the `instanceof` operand, and plain `o instanceof T t` is one too.
   - `GroovyCodeVisitor.visitPatternExpression` is the default traversal.
   
   **When lowering happens**
   
   - `PatternLowering` runs as the first operation of instruction selection. 
Until then, everything sees only the pattern as written: resolution, scoping, 
every semantic-analysis and canonicalization transform, compilation customizers 
(including `SecureASTCustomizer`), the console AST browser and the language 
server.
   - After lowering, the type checker and class generation see only ordinary 
AST:
     - a per-switch subject variable
     - arm labels that are boolean tests
     - the JEP 394 `instanceof` shape for type patterns
     - a second per-switch slot, so an `Iterable` or array selector is 
materialised at most once
   - A transform that creates a pattern during or after instruction selection 
calls `PatternLowering.lower(ClassNode, SourceUnit)`. An unlowered pattern that 
reaches class generation is a `GroovyBugError`.
   
   **How components are read**
   
   - A record whose components are known at compile time is read through its 
accessors. That covers native Groovy records and Java records, including 
precompiled ones.
     - Accessors are called in component order, and only as far as the match 
proceeds.
     - Under `@CompileStatic` an arm compiles to a type test and 
`invokevirtual` accessor calls, with no runtime support call.
   - Emulated records and any other value with `toList()` are deconstructed at 
run time. `toList()` is called once per arm tried.
   - Generated code links only to `RecordPatternSupport`, `ListPatternSupport` 
and `MapPatternSupport`, which carry `@GroovyABI(since = "7.0.0")`.
   
   **Documentation and compatibility**
   
   - The language guide has a new section, "Pattern matching in switch and 
instanceof", and its examples run as spec tests.
   - COMPATIBILITY.md records the labels whose meaning changes (`case Foo()`, 
`case []`, `case [:]`, and `_` in a list or map label) and the new AST shape of 
`instanceof T name`.
   - ARCHITECTURE.md notes the lowering phase.
   - apache/groovy-website#88 brings GEP-19 in line with this implementation 
and is meant to be merged at the same time as this PR.
   
   ## Review points
   
   **@blackdrag**
   
   1. **Lifecycle of the pattern and the lowered code.** Lowering has moved 
from the start of semantic analysis to the first operation of instruction 
selection, so everything before it sees only the `PatternExpression` and 
everything after it sees only the lowered code. Pattern, guard and source are 
final, so a transform that wants a different match builds a new node. One 
residual remains: after lowering, the node holds both forms and `setLowered` is 
public, so a transform running inside instruction selection could still put 
them out of step. The window is now narrow and documented, but it isn't closed.
   2. **Tooling sees the semantic form.** Until lowering, the default 
`visitPatternExpression` visits the source, the guard and the literals inside 
the pattern, never generated code. `SecureASTCustomizer` checks the pattern, 
its literals and its binding types, and it no longer sees calls to the runtime 
support classes. The console AST browser shows `Pattern - Point(var x, int y) 
when (x > 0)`, and the script view renders patterns as written.
   3. **`MissingMethodException`.** The catch is gone. The runtime path asks 
the meta-class whether the value responds to `toList()` and invokes it only if 
it does. A non-List result is reported, and an exception thrown inside a user's 
`toList()` propagates unchanged. There are tests for all three cases.
   4. **Iterable materialisation.** A switch with list-pattern arms 
materialises its selector into the per-switch slot the first time a list arm is 
tried, and every later list arm reuses it. A test counts `iterator()` calls for 
switch statements and expressions, in dynamic and static code.
   5. **`@GroovyABI`.** `RecordPatternSupport`, `ListPatternSupport` and 
`MapPatternSupport` now carry `@GroovyABI(since = "7.0.0")`.
   
   **@daniellansun**
   
   1. **`@since`.** Every new type now says 7.0.0.
   2. **Compatibility notes.** See above. `case List l` wasn't valid syntax 
before patterns, so it isn't a carve-out and has no entry.
   3. **Constant-true label.** `lowerArm` now has a comment explaining why list 
and map arms use a constant-true label.
   4. **`elementsOrNull`.** The javadoc now says a `List` is matched in place 
and can observe concurrent modification, and that an array or `Iterable` is 
copied once per switch. I went with documentation rather than a defensive copy, 
so the common `List` case still pays nothing extra.
   5. **Direct accessors.** Done in b11e3fc2dc, in dynamic and static code 
alike.
      - A component read is an accessor call such as `__$$rp1.x()`, not a list 
built through `RecordPatternSupport`.
      - A failing component test leaves the later accessors uncalled.
      - A primitive component matched by its own type binds directly, with no 
test.
      - Emulated records (`@RecordOptions(mode = EMULATE)`), other `toList()` 
deconstructables and wrong-arity patterns still use the runtime path.
      - Bytecode-level tests cover source, precompiled and emulated records, 
and the short-circuit order.
   
   ## Still open
   
   `SwitchBootstraps.typeSwitch` on a 21+ target, plus three language questions 
from #2933:
   - whether a typed component on a reference-typed record component should 
match `null` (Java's rule, which would let `case Box(Object o)` cover `Box`)
   - whether dominance should be a warning or an error
   - whether `case null` should trigger exhaustiveness
   
   The pattern-statement exhaustiveness error described above just needs 
confirming rather than deciding.
   
   ## Verification
   
   core 19,438 tests, groovy-console 228, groovy-macro 66 and groovy-lsp 346, 
all passing. `lintGate` is clean. japicmp against 5.1.3 shows the new API is 
additive only. (Note - this should really check against 6 - I'll update that 
separately)


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