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

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

daniellansun commented on PR #2773:
URL: https://github.com/apache/groovy/pull/2773#issuecomment-5242278626

   @paulk-asert Thank you for the review. Responses point-by-point.
   
   ---
   
   ## 1. Breaking-change documentation
   
   > *…silent behavior change… no release-notes/docs change. It should be in the
   > beta-2 notes and the Groovy 6 breaking-changes list.*  
   > *(You noted: JIRA `breaking` label added; release notes on your next 
edit.)*
   
   **In-repo documentation added (this PR):**
   
   | Location | Content |
   |----------|---------|
   | [`COMPATIBILITY.md`](COMPATIBILITY.md) | New **Groovy 6 — `instanceof` 
pattern variable flow scoping (GROOVY-12242)** section: who is affected, silent 
dynamic-mode failure mode (`MissingPropertyException`), partial `while` 
contract |
   | [`src/spec/doc/core-semantics.adoc`](src/spec/doc/core-semantics.adoc) | 
New subsection **`instanceof` pattern variables (JEP 394)** under Statements: 
user-facing rules, dynamic vs `@TypeChecked`, while note, link to JIRA / 
COMPATIBILITY |
   
   Beta-2 release-note prose remains with you via the JIRA `breaking` 
cross-check — happy to paste a one-liner into the notes draft if useful:
   
   > GROOVY-12242: `instanceof` / `!instanceof` pattern variables follow Java 
flow
   > scoping; out-of-scope uses are dynamic property lookups in dynamic Groovy
   > (typically `MissingPropertyException`) and compile errors under type 
checking.
   
   ---
   
   ## 2. `while` — partial flow scoping (documented decision)
   
   > *while loops deliberately get only partial flow scoping (no after-loop
   > introduction as Java has — fine, but should be an explicit documented 
decision)*
   
   **Agreed and made explicit.**
   
   **Behaviour (6.0 contract):**
   
   | Path | Support |
   |------|---------|
   | Short-circuit inside the condition (`&&` / `\|\|`) | Yes |
   | `e.whenTrue()` in the **while body** | Yes (aligned with if-then) |
   | `e.whenFalse()` **after** the loop when the body cannot complete normally 
(JLS §6.3.2.3) | **No** — intentional |
   | Leak past the loop | Never |
   
   **Code:** `VariableScopeVisitor.visitWhileLoop` now isolates the condition 
visit,
   declares only `whenTrue` for the body, and does not declare after the loop.
   Javadoc on that method (and on `visitDoWhileLoop`) states the JLS divergence.
   User docs: `core-semantics.adoc` NOTE + `COMPATIBILITY.md`.
   
   **Tests:** AST matrix (`InstanceofScopeTest`) and runtime
   (`InstanceofTest.testWhileNegated_bodyAndAfterNotVisible`) cover body / after
   and abrupt-body “still no after-loop introduction”.
   
   Full after-loop introduction can be a later enhancement if needed; it is not 
a
   silent 6.0 follow-up.
   
   ---
   
   ## 3. `writeIfElse` breakable-frame region (GROOVY-7463)
   
   > *writeIfElse changed the breakable-frame region (it now wraps both arms 
where
   > it previously ended before the else block) — GROOVY-7463 tests pass, but 
the
   > labeled-break-from-else behavior change should be confirmed as 
intentional.*
   
   **Restored historical region; intentional.**
   
   | Region | Pre-12242 | Mid-redesign | **Now** |
   |--------|-----------|--------------|---------|
   | Condition | inside breakable | outer (for survivors) | outer |
   | Then arm | inside | inside | inside |
   | Else arm | **outside** | inside | **outside** |
   
   Pattern slots stay on the **outer** frame (so survivors need no put-back 
API);
   the breakable still ends **before else**, matching GROOVY-7463. Javadoc on
   `StatementWriter.writeIfElse` records that choice.
   
   **Tests added:**
   
   - `BreakContinueLabelTest.testBreakLabelFromElseOfLabeledIf` — break from 
else
     to the if’s label still works (named-break map is method-level; pop does 
not
     un-register names).
   - `BreakContinueLabelTest.testBreakLabelInIfWithInstanceofPattern` — then-arm
     break with a pattern variable.
   
   Existing GROOVY-7463 then-arm tests remain green.
   
   ---
   
   ## 4. Bottom line / Jochen sign-off
   
   > *technically strong and converging fast, but let Jochen sign off on the 
redesign*
   
   Acknowledged. The CompileStack push/hide/pop redesign and
   `InstanceofFlowBindings` as single analysis+metadata type are the answer to
   blackdrag’s layering review; we will wait for Jochen’s explicit OK before 
merge.
   




> instanceof pattern variable scope is not aligned with Java flow scoping (JEP 
> 394)
> ---------------------------------------------------------------------------------
>
>                 Key: GROOVY-12242
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12242
>             Project: Groovy
>          Issue Type: Bug
>            Reporter: Daniel Sun
>            Priority: Major
>              Labels: breaking
>
> h2. Summary
> After {{instanceof}} type patterns landed in GROOVY-11229, pattern variables 
> were still scoped with a coarse lexical approximation. That diverges from 
> Java’s *flow scoping* (JEP 394): a pattern variable must be visible only 
> where the pattern has *definitely* matched.
> The gaps appear as:
>  # variables missing where Java allows them
>  # variables leaking past the statement that introduced them
>  # name resolution and bytecode disagreeing, so an “out of scope” use can 
> still load a local slot
> h2. Background
>  * GROOVY-11229 added {{e instanceof T t}} (parser, AST, store-on-match).
>  * Java (JEP 394 / JLS): scope follows boolean flow and abrupt completion, 
> not simple block poison.
>  * Groovy initially limited leakage with push/pop around statements, but did 
> not implement true/false-path binding or CompileStack polarity.
> h2. Problems (before the fix)
> ||#||Scenario||Java||Groovy (before)||
> |1|negated {{instanceof}} — use pattern var in else|in scope|missing|
> |2|negated {{instanceof}} + early {{return}} — use pattern var after if|in 
> scope|missing|
> |3|positive {{instanceof}} + abrupt else — use pattern var after if|in 
> scope|missing|
> |4|{{boolean b = (o instanceof String s)}} then use {{s}}|not in 
> scope|CompileStack leak (local still loadable)|
> |5|expression statement with pattern, then use pattern var|not in 
> scope|CompileStack leak|
> |6|type-checked: pattern var used on RHS of logical-or|error on RHS|often 
> accepted|
> |7|type-checked ternary false arm uses pattern var|error|often accepted|
> |8|negated {{instanceof}} — use pattern var in then-branch|not in scope|could 
> ALOAD unassigned local (null)|
> h2. Steps to reproduce
> h3. A. Negated instanceof — else branch (should see {{{}s{}}})
> {code:groovy}
> def f = { Object o ->
>     if (!(o instanceof String s)) {
>         return 'not'
>     } else {
>         return s.toUpperCase()   // expected: OK when o is String
>     }
> }
> assert f('hi') == 'HI'
> {code}
> h3. B. Early return after negation (should see {{s}} after if)
> {code:groovy}
> def f = { Object o ->
>     if (!(o instanceof String s)) return 'early'
>     return s.toUpperCase()       // expected: OK when o is String
> }
> assert f('hi') == 'HI'
> {code}
> h3. C. Leak after declaration (must *not* see {{{}s{}}})
> {code:groovy}
> class C {
>     Object m(Object o) {
>         boolean b = (o instanceof String s)
>         return s                 // expected: MissingPropertyException / 
> undeclared
>     }
> }
> new C().m('hi')
> {code}
> h3. D. Type-checked {{||}} RHS must not see true-path binding
> {code:groovy}
> @groovy.transform.TypeChecked
> class C {
>     static void m(Object o) {
>         if (o instanceof String s || s.length() > 0) {
>             // expected: undeclared / apparent variable s on RHS of ||
>         }
>     }
> }
> {code}
> h2. Expected behaviour
> Align with Java JEP 394 flow scoping for the common shapes:
>  * true-path bindings (e.g. {{{}e instanceof T t{}}}) live in then-blocks, 
> {{&&}} RHS, and ternary true arm
>  * false-path bindings (e.g. {{{}!(e instanceof T t){}}}) live in 
> else-blocks, after abrupt then, and the matching ternary arm
>  * pattern variables do not leak past the introducing statement (declaration 
> RHS, expression statement, …)
>  * VariableScope (names) and CompileStack (locals) agree on which path a 
> pattern local is live
> h2. Actual behaviour (before fix)
>  * Lexical push/pop approximated “no leak past statement” but not true/false 
> path polarity.
>  * CompileStack could keep pattern slots after VariableScope had dropped the 
> name (silent local load vs property miss).
>  * Negation and abrupt-completion cases from Java were not supported.
>  



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

Reply via email to