[
https://issues.apache.org/jira/browse/GROOVY-12126?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18094504#comment-18094504
]
ASF GitHub Bot commented on GROOVY-12126:
-----------------------------------------
codecov-commenter commented on PR #2677:
URL: https://github.com/apache/groovy/pull/2677#issuecomment-4911774018
##
[Codecov](https://app.codecov.io/gh/apache/groovy/pull/2677?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
Report
:x: Patch coverage is `83.15789%` with `48 lines` in your changes missing
coverage. Please review.
:white_check_mark: Project coverage is 68.7355%. Comparing base
([`23780cf`](https://app.codecov.io/gh/apache/groovy/commit/23780cf25faf3fd44e7631736b7cd152849ea419?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache))
to head
([`da4e8fb`](https://app.codecov.io/gh/apache/groovy/commit/da4e8fbd9f19a048285e3a6d9bff4b84e8eb31ab?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)).
| [Files with missing
lines](https://app.codecov.io/gh/apache/groovy/pull/2677?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
| Patch % | Lines |
|---|---|---|
|
[.../codehaus/groovy/runtime/DefaultGroovyMethods.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Fruntime%2FDefaultGroovyMethods.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L3J1bnRpbWUvRGVmYXVsdEdyb292eU1ldGhvZHMuamF2YQ==)
| 63.6364% | [16 Missing and 16 partials :warning:
](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
|
|
[...us/groovy/control/NonLocalControlFlowRewriter.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Fcontrol%2FNonLocalControlFlowRewriter.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L2NvbnRyb2wvTm9uTG9jYWxDb250cm9sRmxvd1Jld3JpdGVyLmphdmE=)
| 93.2515% | [1 Missing and 10 partials :warning:
](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
|
|
[...va/org/apache/groovy/parser/antlr4/AstBuilder.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fapache%2Fgroovy%2Fparser%2Fantlr4%2FAstBuilder.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvYXBhY2hlL2dyb292eS9wYXJzZXIvYW50bHI0L0FzdEJ1aWxkZXIuamF2YQ==)
| 71.4286% | [1 Missing and 1 partial :warning:
](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
|
|
[.../org/codehaus/groovy/ast/stmt/ReturnStatement.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Fast%2Fstmt%2FReturnStatement.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L2FzdC9zdG10L1JldHVyblN0YXRlbWVudC5qYXZh)
| 75.0000% | [0 Missing and 1 partial :warning:
](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
|
|
[...va/org/codehaus/groovy/runtime/NonLocalReturn.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Fruntime%2FNonLocalReturn.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L3J1bnRpbWUvTm9uTG9jYWxSZXR1cm4uamF2YQ==)
| 87.5000% | [1 Missing :warning:
](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
|
|
[...roovy/transform/stc/StaticTypeCheckingVisitor.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Ftransform%2Fstc%2FStaticTypeCheckingVisitor.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L3RyYW5zZm9ybS9zdGMvU3RhdGljVHlwZUNoZWNraW5nVmlzaXRvci5qYXZh)
| 87.5000% | [0 Missing and 1 partial :warning:
](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
|
<details><summary>Additional details and impacted files</summary>
[](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
```diff
@@ Coverage Diff @@
## master #2677 +/- ##
==================================================
+ Coverage 68.7019% 68.7355% +0.0336%
- Complexity 34123 34215 +92
==================================================
Files 1536 1539 +3
Lines 128912 129153 +241
Branches 23369 23451 +82
==================================================
+ Hits 88565 88774 +209
- Misses 32517 32521 +4
- Partials 7830 7858 +28
```
| [Files with missing
lines](https://app.codecov.io/gh/apache/groovy/pull/2677?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
| Coverage Δ | |
|---|---|---|
|
[...a/org/codehaus/groovy/control/CompilationUnit.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Fcontrol%2FCompilationUnit.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L2NvbnRyb2wvQ29tcGlsYXRpb25Vbml0LmphdmE=)
| `80.6794% <100.0000%> (+0.1239%)` | :arrow_up: |
|
[.../java/org/codehaus/groovy/runtime/LoopControl.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Fruntime%2FLoopControl.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L3J1bnRpbWUvTG9vcENvbnRyb2wuamF2YQ==)
| `100.0000% <100.0000%> (ø)` | |
|
[.../org/codehaus/groovy/ast/stmt/ReturnStatement.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Fast%2Fstmt%2FReturnStatement.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L2FzdC9zdG10L1JldHVyblN0YXRlbWVudC5qYXZh)
| `78.9474% <75.0000%> (-2.3026%)` | :arrow_down: |
|
[...va/org/codehaus/groovy/runtime/NonLocalReturn.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Fruntime%2FNonLocalReturn.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L3J1bnRpbWUvTm9uTG9jYWxSZXR1cm4uamF2YQ==)
| `87.5000% <87.5000%> (ø)` | |
|
[...roovy/transform/stc/StaticTypeCheckingVisitor.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Ftransform%2Fstc%2FStaticTypeCheckingVisitor.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L3RyYW5zZm9ybS9zdGMvU3RhdGljVHlwZUNoZWNraW5nVmlzaXRvci5qYXZh)
| `87.4585% <87.5000%> (+0.0001%)` | :arrow_up: |
|
[...va/org/apache/groovy/parser/antlr4/AstBuilder.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fapache%2Fgroovy%2Fparser%2Fantlr4%2FAstBuilder.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvYXBhY2hlL2dyb292eS9wYXJzZXIvYW50bHI0L0FzdEJ1aWxkZXIuamF2YQ==)
| `86.6978% <71.4286%> (-0.0456%)` | :arrow_down: |
|
[...us/groovy/control/NonLocalControlFlowRewriter.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Fcontrol%2FNonLocalControlFlowRewriter.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L2NvbnRyb2wvTm9uTG9jYWxDb250cm9sRmxvd1Jld3JpdGVyLmphdmE=)
| `93.2515% <93.2515%> (ø)` | |
|
[.../codehaus/groovy/runtime/DefaultGroovyMethods.java](https://app.codecov.io/gh/apache/groovy/pull/2677?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Forg%2Fcodehaus%2Fgroovy%2Fruntime%2FDefaultGroovyMethods.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3JjL21haW4vamF2YS9vcmcvY29kZWhhdXMvZ3Jvb3Z5L3J1bnRpbWUvRGVmYXVsdEdyb292eU1ldGhvZHMuamF2YQ==)
| `74.9473% <63.6364%> (-0.0461%)` | :arrow_down: |
... and [7 files with indirect coverage
changes](https://app.codecov.io/gh/apache/groovy/pull/2677/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
</details>
<details><summary> :rocket: New features to boost your workflow: </summary>
- :snowflake: [Test
Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests,
report on failures, and find test suite problems.
- :package: [JS Bundle
Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save
yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
> Non-local control flow from closures (return / break / continue)
> ----------------------------------------------------------------
>
> Key: GROOVY-12126
> URL: https://issues.apache.org/jira/browse/GROOVY-12126
> Project: Groovy
> Issue Type: Improvement
> Reporter: Paul King
> Priority: Major
> Labels: GEP
> Fix For: 7.x
>
>
> h1. Non-local control flow from closures (return / break / continue)
> h2. Status
> Proposed — pending team review. Core feasibility de-risked by spikes (see
> Evidence).
> h2. Summary
> Give closures explicit, opt-in non-local control flow:
> * {{return@methodName expr}} — return a value from the lexically enclosing
> method.
> * {{break}} / {{continue}} inside cooperating iteration methods, via a
> repaired
> loop-control protocol.
> Bare {{return}} inside a closure keeps its current meaning (return from the
> closure) —
> unchanged. This enables Smalltalk/Kotlin-style control-flow DSLs (custom
> iteration with
> early exit, {{ifFalse}}/{{unless}}, builder short-circuits) that today force
> users into
> {{find}}/{{any}}/{{findResult}} or hand-rolled sentinel exceptions.
> h2. Motivation
> Today {{return}} in a closure returns only from the closure; there is no
> language form to
> abort the enclosing method, nor to {{break}}/{{continue}} an iteration, from
> within a passed
> block. The existing {{Closure.DONE}}/{{SKIP}} "directive" is a half-built
> attempt: DONE is
> honored by only 3 DGM methods, SKIP is honored nowhere, and it is mutable
> state on the shared
> Closure instance. This GEP provides a coherent, type-safe,
> backward-compatible replacement.
> h2. Prior art & relationship
> * GROOVY-8301 ("break/return/continue in appended block closures") proposes
> an {{@Inline}}
> annotation (Kotlin-style) that inlines the closure so the keywords compile
> to real jumps.
> * GROOVY-6880 is the underlying inline-AST-transform infrastructure.
> This GEP is complementary, not competing. It provides mechanisms that work in
> DYNAMIC Groovy
> with no inlining machinery; {{@Inline}} remains an optional zero-cost
> optimization (Part 3).
> ============================================================
> h2. Part 1 — Non-local return: return@methodName
> ============================================================
> h3. Syntax (proposed, pending team review)
> {code:groovy}
> def firstMatch(rows, pred) {
> rows.each { row ->
> row.each { cell ->
> if (pred(cell)) return@firstMatch cell // unwinds both
> closures, returns from firstMatch
> }
> }
> null
> }
> {code}
> * Bare {{return}} — unchanged (returns from the closure). 100% backward
> compatible.
> * {{return@name expr}} — new; {{@name}} must be an enclosing method's name
> (compiler-verified).
> Script bodies use the reserved label {{return@script}}.
> Rationale: after {{return}}, {{@}} cannot begin an expression today, so
> {{return@x}} is
> unparseable now and free to claim; Kotlin-familiar, self-documenting, visible
> at the call site.
> Alternatives considered: {{^expr}} (Smalltalk caret; cryptic, names no
> target); {{returnFrom
> name expr}} (Common Lisp; wordier). Both rejected as primary.
> h3. Two Groovy-specific facts
> # A closure's non-local target is the NEAREST enclosing method. Method scopes
> CAN nest — via
> anonymous inner classes (today) or hypothetical method-local classes — so
> {{@name}} is
> REQUIRED to disambiguate; it is load-bearing, not mere documentation.
> Resolution stays fully
> static (closure and target are in the same source).
> # Closures have indefinite extent (may be stored/returned/run on another
> thread), so a
> non-local return CANNOT be a guaranteed jump; it must fail loudly when the
> target activation
> is gone.
> h3. Boundary rule
> Non-local control flow does NOT cross a class boundary. From a closure inside
> an anonymous or
> method-local inner class, the legal {{@name}} targets are the methods of the
> NEAREST enclosing
> class only; targeting an outer class's method (e.g. {{return@outer}} from
> inside a local
> class's {{inner()}}) is a COMPILE ERROR. Precedent: Java forbids non-local
> return from local/
> anonymous class methods; Kotlin permits it only from inline lambdas, never
> from object/local-
> class method bodies. This keeps @CompileStatic resolution and @Inline
> lowering clean and
> confines the "target frame gone" failure surface. The token mechanism COULD
> cross the
> boundary (tokens are captured like any enclosing value), so this is a
> deliberate policy choice,
> not a limitation.
> h3. Implementation (compiler-generated, no runtime magic)
> # At entry of a targeted method, synthesize a unique per-activation token
> (recursion-safe).
> # Wrap the body: {{try { ... } catch (NonLocalReturn e) { if (e.token == tok)
> return (T) e.value; else throw e; }}}.
> # At the {{return@m}} site: {{throw new NonLocalReturn(capturedToken,
> value)}}.
> # {{NonLocalReturn}} is a lightweight throwable with a SUPPRESSED stack trace
> (mandatory — see perf).
> Because the closure and its target method are always in the same source,
> resolution and
> type-checking are fully static; {{@CompileStatic}} checks {{expr}} against
> {{m}}'s return type.
> h3. Semantics
> || Situation || Behavior ||
> | Closure invoked in-place within the method | Non-local return (intended
> path) |
> | Code after {{return@m}} in same block | Unreachable (compile error) |
> | {{@name}} not an enclosing method | Compile error |
> | Returned type vs method return type | Static under @CompileStatic; runtime
> otherwise |
> | Closure invoked after {{m}} returned, or on another thread | Loud runtime
> failure (frame gone) |
> | Nested closures | Unwinds through all intermediate closure frames to {{m}} |
> | {{return@outer}} across an inner-class boundary (anon/local class) |
> Compile error (see Boundary rule) |
> | {{try/finally}} between site and {{m}} | {{finally}} runs during unwind
> (exception semantics) |
> h3. Performance
> ~15 ns overhead per non-local return (JIT-warm), ~5x a plain return. A NAIVE
> traced exception
> is ~562 ns (31x worse) — hence stack-trace suppression is mandatory. Fine for
> DSL/early-exit;
> document "not for hottest inner loops."
> ============================================================
> h2. Part 2 — break / continue via a loop-control protocol
> ============================================================
> h3. Current state (the half-feature being repaired)
> {{Closure.DONE}} honored by 3 DGM methods
> (collect(Iterable,Collection,Closure), collectNested,
> times) via a POST-call check; {{Closure.SKIP}} honored nowhere; directive is
> mutable state on
> the shared Closure instance. Consequences: fragile under
> re-entrancy/concurrency, and a
> semantic wart — {{break}} in {{collect}} still collects the current element
> (value added before
> the DONE check).
> h3. Two facts
> # {{continue}} ~= "return from the closure body". For value-IGNORING
> iterators (each,
> eachWithIndex, times, upto, step) it needs NO protocol — it lowers to a
> plain closure-local
> return. Universal, zero cooperation.
> # {{break}} inherently requires the loop to cooperate — no closure can stop
> an arbitrary
> caller's iteration without the caller checking something (also true of
> @Inline).
> h3. Design — keyword sugar over a compiler-internal loop signal (NOT a
> return-value convention)
> CRITICAL: {{break}}/{{continue}} are CONTROL-FLOW STATEMENTS, not
> expressions, and are lowered
> by the COMPILER to an internal, NON-USER-CONSTRUCTIBLE loop signal. This is
> what preserves type
> safety and prevents data collision:
> * Type safety: the static checker type-checks only the value-returning paths
> of the body
> against {{T}} and treats {{break}}/{{continue}} like loop
> {{break}}/{{return}} — they
> contribute nothing to the inferred type. The closure's source-level return
> type stays {{T}},
> so e.g. {{collect}} stays {{List<T>}}. The signal is injected during
> CODEGEN, after type
> checking, riding the Object-typed {{call()}} that all closures already have
> — invisible to
> inference. (A naive "body returns a public {{LoopControl}} enum" convention
> does NOT work:
> under @CompileStatic the body's type widens to LUB(T, signal) and
> {{collect}} degrades away
> from {{List<T>}}. Verified in spike.)
> * No data collision: because the signal is non-user-constructible (only the
> compiler emits it
> via the keywords), no genuine data value can equal it, so the identity
> check never misfires.
> h3. Keyword desugaring
> * {{continue}} -> internal CONTINUE signal (or plain {{return}} for
> value-ignoring iterators)
> * {{break}} -> internal BREAK signal
> SAFETY GATE: the keyword sugar is enabled ONLY when the target method is
> statically resolved
> and annotated {{@SupportsLoopControl}}; otherwise it is a COMPILE ERROR.
> (Without the gate,
> {{break}} on a non-cooperating method would silently degrade to "return from
> closure" — a
> silent correctness bug.) This trades @Inline's inlinability requirement for a
> protocol-conformance requirement, while remaining dynamic-dispatch capable.
> h3. Per-iterator semantics
> || Iterator kind || break || continue ||
> | value-ignoring (each, times, upto, step) | stop | proceed (== plain return)
> |
> | value-consuming (collect, findAll, inject) | stop, do NOT contribute
> current element | skip contributing current element |
> The value-consuming BREAK correctly excludes the breaking element (unlike the
> legacy post-hoc
> directive, which includes it). Verified in spike: legacy {{collect}} ->
> [10,20,30,999];
> protocol -> [10,20,30].
> h3. SPI / rollout
> * Add {{@SupportsLoopControl}} and roll the signal checks across DGM
> iterators.
> * Expose the annotation + a detection helper (e.g. {{LoopSignal.of(r)}} ->
> BREAK/CONTINUE/VALUE)
> so third-party iterators (the issue's {{sql.forEachRow}}, GPars, custom
> builders) opt in.
> SPI authors detect via the helper; they never compare against a
> user-returnable value.
> * Give the previously-dead SKIP a real meaning ("skip contributing"), or
> explicitly scope
> break/continue to value-ignoring iterators in v1.
> h3. Performance
> Below measurement noise (~0 ns/element; identity checks), vs ~15 ns/break for
> the Part-1
> exception path. The protocol is the cheap path for break/continue.
> ============================================================
> h2. Part 3 — @Inline as an optional zero-cost lowering
> ============================================================
> Where a callee is {{@Inline}} (GROOVY-8301 / GROOVY-6880), statically
> resolved, and the closure
> is non-escaping, the compiler may LOWER Part-1 return@m and Part-2
> break/continue to real jumps
> (0 ns). It is the fast path, not the only path, and is out of scope for THIS
> GEP's first
> increment.
> h2. Compatibility
> * Bare {{return}} semantics unchanged; {{return@ident}} is new,
> previously-unparseable syntax.
> * Legacy {{Closure.DONE}}/{{SKIP}}/{{directive}}: keep honoring on the
> current 3 methods;
> deprecate in favor of the loop-control protocol.
> * No existing code is affected: new signal returns are compiler-internal and
> additive.
> h2. Scope / Non-goals
> * In scope: value-returning non-local return ({{return@m}}); break/continue
> via the
> loop-control protocol.
> * Out of scope: changing bare {{return}} semantics; making {{break}} work
> through an arbitrary
> NON-cooperating method (impossible); non-local control flow across a class
> boundary (Boundary
> rule); full closure inlining (the @Inline / GROOVY-6880 track, referenced
> as the zero-cost
> optimization).
> h2. Evidence (spikes)
> # @CompileStatic desugaring of return@m (token + method try/catch + throw
> from captured nested
> closure) compiles statically and is correct.
> # Escaped/cross-thread closure surfaces {{NonLocalReturn}} loudly — no silent
> corruption.
> # Perf: baseline plain return ~3.4 ns/op; return@m no-trace exception ~18
> ns/op (~5x);
> naive traced exception ~562 ns/op (31x) — suppression mandatory.
> # Loop-control protocol: break/each stops early; break/collect excludes the
> breaking element;
> continue/collect skips it; legacy directive collect includes it (wart).
> Overhead below noise.
> # Type safety: a public-enum return convention widens the body type under
> @CompileStatic
> (rejected assignment to Integer) — confirming the feature must be keyword
> sugar with a
> compiler-internal signal, not a return-value convention.
> # Nesting: anonymous inner classes already nest method scopes (outer -> inner
> -> closure),
> confirming {{@name}} must be required and motivating the Boundary rule.
> h2. Open questions (pending team review)
> * RESOLVED: whether {{@name}} is required — yes. Because method scopes nest
> (anonymous/local
> inner classes), the label is required for disambiguation, not optional
> sugar.
> * return@m surface syntax: {{return@m}} vs {{^}} vs {{returnFrom}}.
> * Whether value-ignoring {{continue}} lowers to bare {{return}} or always to
> the signal.
> * Labeled {{break@method}}/{{continue@method}} for nested loops, mirroring
> {{return@method}}
> and adopting the same Boundary rule (no crossing a class boundary).
> * Best-effort lint when a closure containing non-local control flow is
> stored/returned rather
> than invoked in place.
> * Cautionary precedent: Scala DEPRECATED non-local {{return}} — but theirs
> was IMPLICIT; this
> proposal is EXPLICIT and opt-in, which is the mitigation.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)