add yield

This commit is contained in:
2026-06-27 04:12:04 +02:00
parent b4ce2c2117
commit f610be1b59
8 changed files with 426 additions and 16 deletions
+79 -1
View File
@@ -305,7 +305,32 @@
a bare block is built and spliced inline, and a deferred statement is built once at the
`defer` site and its hir replayed at each exit, so lowering/codegen are unchanged
20. add `yield` statement (see below)
20. add `yield` statement (implemented; first pass — value blocks only; see below)
- a `{ ... }` on the right of a declaration or assignment is a *value block*: its final
statement must be `yield <expr>`, which supplies the block's value (the block analogue
of `return`). Supported: `x :: { ...; yield v }` (untyped — the local takes the yield's
natural type), `x T = { ... }` (coerces to `T`), and `target = { ... }` (coerces to the
target's type, including complex targets like `a[i] = { ... }`)
- the yielded value is captured *before* the block's defers run (a defer that mutates a
block local can't change what is yielded), reusing the `return` spill-to-temp pattern
- `yield` is valid *only* as the final statement of a value block. A `yield` nested in an
`if`/loop/inner block, or in a non-value block, is rejected ("'yield' is only valid as
the final statement of a value block"); a value block not ending in `yield` is rejected
too. This no-early-exit restriction keeps it a lexer/parser/checker-only change with no
HIR/lowering touch (like milestones 18/19)
- new `Keyword_Yield` token + `.Yield` AST stmt (reuses `expr`); a block-initialized
`Declaration`/`Assignment` reuses the existing `body` field with `expr` invalid. The
checker's `build_value_block` builds the leading statements (via `build_block` with a
new `close=false` flag that keeps the scope open), evaluates the final yield, spills and
flushes the block's defers, then feeds the value into an ordinary `Declaration`/
`Assignment`. HIR never holds a `.Yield` (final yield → `Declaration`/`Assignment`,
misplaced yield → `Trap`), so lowering/codegen are unchanged
- deferred to a later milestone (needs labeled blocks + rules for whether an `if`/loop
always produces a value, e.g. optionals): yield from inside `if`/loops, labeled blocks
(`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)
21. unions and tagged unions
@@ -546,6 +571,59 @@ data []u8 = read(path) catch |e| {
}
```
### Yielding from if-statements and loops
Yielding from if-statements is possible with the constraint that all branches must resolve to the same yield type.
```
# yielding to a constant
result :: if a {
yield 1
} else if b {
yield 2
} else {
yield 3
}
# yielding to a variable
result int = if a {
yield 1
} else if b {
yield 2
} else {
yield 3
}
# ILLEGAL: branches with different yield types
result :: if a {
yield 1
} else {
yield Color{ r = 255, g = 0, b = 0 }
}
```
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
# 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).
}
# 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
}
# BAD: likewise for loops
for 0..10 |i| blk: { # bad, no name binds returned value
if is_active(some_entity, i) yield :blk i
}
```
## A word on memory allocation
(NOTE THAT SYNTAX MAY NOT MATCH BROLANG EXACTLY AND SHOULD BE TAKEN WITH A GRAIN OF SALT - INSPIRATION ONLY)