expand yield to if-statements and loops

This commit is contained in:
2026-06-27 10:57:35 +02:00
parent f610be1b59
commit 61293a23e7
6 changed files with 669 additions and 17 deletions
+36 -1
View File
@@ -330,7 +330,39 @@
(`blk: { yield :blk v }`), implicit trailing-expression yield, and yield in match arms /
`catch` handlers (milestones 2122)
20.5 `yield` from if-statements and loops (see below)
20.5 `yield` from if-statements and loops (implemented; see below)
- an `if`/`for`/`while` on the right of a declaration or assignment is now a *value
source*, governed by the rule **if one path yields, all paths must yield** (no
optionals-as-a-crutch, so the value is always present and never needs unwrapping):
- value-if: `result :: if a { yield 1 } else if b { yield 2 } else { yield 3 }` — a
mandatory `else`, every branch ends in `yield`, all branches share a type (the first
branch fixes it when untyped; later branches coerce). Typed `T =` and reassignment
`target = if …` are supported too
- value-loop: a labeled body `for/while … blk: { … }` whose early exits are
`yield :blk x` and whose body ends in an unlabeled fall-through `yield` (the value
when the loop completes). The `{T, none}` yields resolve the result to `?T`
(a pure-AST `none`-scan picks optionality; the first concrete yield fixes the element
type). E.g. `active_ent_idx :: for 0..10 |i| blk: { if (cond) yield :blk i; yield none }`
resolves to `?usize`
- new `blk:` / `yield :blk` label surface adds one `label` field to the AST `Stmt`; no new
token (`blk:` is `Identifier Colon`, `:blk` is `Colon Identifier`). The parser carries a
value `if`/`for`/`while` as a one-element block-init `body` (the same `expr`-invalid
signal a value block uses)
- **no HIR/lowering change** (like 18/19/20): a value-if/loop desugars in the checker to a
mutable result *slot* (a poison-/fall-through-initialized local) that branches/iterations
assign and that is read after the construct — the existing alloca-backed local flow. A
`yield :blk x` desugars to `slot = x; break`, reusing the milestone-18 `Break` lowering.
Each value-if branch is a `build_value_block` call; the value-loop reuses the ordinary
`.For`/`.While` build via a peeled-body copy. HIR never holds a `.Yield`
- errors: an `if` value without `else`; a branch/value-block not ending in `yield`; a value
loop body without a trailing fall-through `yield`; a `yield :blk` with no matching value
loop. The TODO "BAD" loops (unlabeled yield from inside an `if`, an unbound labeled loop)
fall out of these naturally
- deferred to a later pass (`// ponytail:`): a value-if/loop nested as the *trailing*
statement of a value block (first pass is the direct `x :: if …`/`for …` form); a branch
that early-`return`s instead of yielding; unwrap-`if` (`if v |x| { yield … }`) as a value
source; `yield none` before any concrete yield in an untyped loop (annotate instead);
`yield :blk` to an outer (non-innermost) loop
21. unions and tagged unions
@@ -608,6 +640,7 @@ Yielding is also possible from loops with the same constraint.
# get active entity
active_ent_idx :: for 0..10 |i| blk: {
if is_active(some_entity, i) yield :blk i
yield none # fall-through: no active ent was found (this should imply a return type matching both the index value and `none`, meaning it should resolve to an optional in this case)
# note that in this case, we have to use the `blk` label to yield from the correct scope.
# otherwise, the yield should return directly from the if-statement's scope (which would be incorrect in this case).
@@ -616,11 +649,13 @@ active_ent_idx :: for 0..10 |i| blk: {
# BAD: yield returned from if-statement, but no name binds it: should miscompile similar to unused return values from functions.
active_ent_idx :: for 0..10 |i| {
if is_active(some_entity, i) yield i # bad
yield none
}
# BAD: likewise for loops
for 0..10 |i| blk: { # bad, no name binds returned value
if is_active(some_entity, i) yield :blk i
yield none
}
```