block labels
This commit is contained in:
@@ -360,7 +360,7 @@
|
|||||||
fall out of these naturally
|
fall out of these naturally
|
||||||
- follow-ups: a branch that early-`return`s instead of yielding, unwrap-`if` as a value
|
- follow-ups: a branch that early-`return`s instead of yielding, unwrap-`if` as a value
|
||||||
source, and `none`-before-concrete typing in untyped loops are done in 20.6; labeled value
|
source, and `none`-before-concrete typing in untyped loops are done in 20.6; labeled value
|
||||||
blocks and `yield`/`break` to an outer loop are 20.7 (need labels in the IR)
|
blocks and `yield`/`break` to an outer loop are done in 20.7
|
||||||
|
|
||||||
20.6 value if/loop follow-ups (implemented; checker-only)
|
20.6 value if/loop follow-ups (implemented; checker-only)
|
||||||
- a value-if branch may end in `yield` **or** exit on every path (`return`/`break`/
|
- a value-if branch may end in `yield` **or** exit on every path (`return`/`break`/
|
||||||
@@ -377,16 +377,23 @@
|
|||||||
`none` yielded before any concrete value still resolves the result to `?T`
|
`none` yielded before any concrete value still resolves the result to `?T`
|
||||||
- still checker-only; no HIR/lowering change
|
- still checker-only; no HIR/lowering change
|
||||||
|
|
||||||
20.7 labeled value blocks + `yield`/`break` to an outer loop (introduces labels in the IR)
|
20.7 labels — value blocks + yield/break to an outer loop (implemented; first lowering change)
|
||||||
- `x :: blk: { …; yield :blk v }` — a labeled value *block* (the disambiguated form of "an
|
- `x :: blk: { …; yield :blk v }` — a labeled value *block* (the disambiguated form of "an
|
||||||
if/loop at the end of a block"; an unlabeled trailing if/loop stays ambiguous and is not a
|
if/loop at the end of a block"; an unlabeled trailing if/loop stays ambiguous and is not a
|
||||||
value source). `yield :blk v` exits the block with a value
|
value source). `yield :blk v` exits the block with a value; every path must yield. Carries
|
||||||
- `yield :outer v` / labeled `break` to a non-innermost loop
|
the same `{T, none}` → `?T` typing, defer-capture, and reassignment forms as value loops
|
||||||
- both need exit targets to carry a label: add `label` to HIR `.While`/`.For`/`.Break` and a
|
- `yield :outer v` to an enclosing (non-innermost) value loop/block, plus plain `break :L` /
|
||||||
labeled `.Block`; generalize the lowering's `Loop_Ctx`/`State.loops` into an exit-target
|
`continue :L` to an enclosing labeled loop
|
||||||
stack keyed by label (plain `break`/`continue` stay innermost-only; a labeled `.Break`
|
- a label now names a first-class exit target: `label` added to the HIR `Stmt` (on
|
||||||
searches by label; a labeled block pushes a non-loop target + an exit label after its body).
|
`.While`/`.For`/`.Break`/`.Continue`) and a new HIR `.Block` kind (lowers to its body + an
|
||||||
First lowering change in the `yield` line
|
exit label). The lowering's `Loop_Ctx`/`State.loops` became a label-keyed exit-target stack
|
||||||
|
(`is_loop` distinguishes loops from value blocks; plain `break`/`continue` take the innermost
|
||||||
|
loop, a labeled one searches by label). The checker tracks a `loop_labels` stack and a
|
||||||
|
`Yield_Target.defer_floor`; a `yield :L v` desugars to `slot = v; flush defers to L's body;
|
||||||
|
break :L`, reusing the milestone-18 break lowering — **no new IR opcode, no emitter change**
|
||||||
|
- deferred (`// ponytail:`): a labeled bare block as a *plain statement* with `break :blk`;
|
||||||
|
`none`-before-concrete typing in an untyped block whose first concrete yield references a
|
||||||
|
block local (annotate instead)
|
||||||
|
|
||||||
21. unions and tagged unions
|
21. unions and tagged unions
|
||||||
|
|
||||||
|
|||||||
+136
-32
@@ -67,6 +67,9 @@ Yield_Target :: struct {
|
|||||||
// True when the loop also yields `none` (a `{T, none}` set → `?T`); set from a
|
// True when the loop also yields `none` (a `{T, none}` set → `?T`); set from a
|
||||||
// pure-AST scan, used to pick the slot's element type on the first concrete yield.
|
// pure-AST scan, used to pick the slot's element type on the first concrete yield.
|
||||||
result_optional: bool,
|
result_optional: bool,
|
||||||
|
// `len(defers)` when this target's body began; a `yield :label` flushes defers down
|
||||||
|
// to here before breaking, so an outer-loop / value-block yield runs inner defers too.
|
||||||
|
defer_floor: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
Build_Ctx :: struct {
|
Build_Ctx :: struct {
|
||||||
@@ -92,6 +95,9 @@ Build_Ctx :: struct {
|
|||||||
// only see loops opened within the defer (those past `loop_floor`).
|
// only see loops opened within the defer (those past `loop_floor`).
|
||||||
defers: ^[dynamic][]hir.Stmt_Id,
|
defers: ^[dynamic][]hir.Stmt_Id,
|
||||||
loop_defer_starts: ^[dynamic]int,
|
loop_defer_starts: ^[dynamic]int,
|
||||||
|
// Parallel to `loop_defer_starts`: the label of each enclosing loop (INVALID when
|
||||||
|
// unlabeled), so a `break :L` / `continue :L` can target an outer labeled loop.
|
||||||
|
loop_labels: ^[dynamic]symbol.Id,
|
||||||
defer_depth: int,
|
defer_depth: int,
|
||||||
loop_floor: int,
|
loop_floor: int,
|
||||||
}
|
}
|
||||||
@@ -4055,7 +4061,7 @@ build_block :: proc(
|
|||||||
if typed {
|
if typed {
|
||||||
expected = type_from_syntax(statement.type)
|
expected = type_from_syntax(statement.type)
|
||||||
}
|
}
|
||||||
value, value_type := build_value_source(ctx, &body, statement.body, expected, statement.span)
|
value, value_type := build_value_source(ctx, &body, statement.body, expected, statement.span, statement.label)
|
||||||
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found {
|
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found {
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
checker.diagnostics, statement.span,
|
checker.diagnostics, statement.span,
|
||||||
@@ -4266,7 +4272,7 @@ build_block :: proc(
|
|||||||
} else if statement.expr == ast.INVALID_EXPR {
|
} else if statement.expr == ast.INVALID_EXPR {
|
||||||
// `target = { ... yield v }`: build the value block against the
|
// `target = { ... yield v }`: build the value block against the
|
||||||
// target's type (build_value_block coerces internally).
|
// target's type (build_value_block coerces internally).
|
||||||
value, _ = build_value_source(ctx, &body, statement.body, target_type, statement.span)
|
value, _ = build_value_source(ctx, &body, statement.body, target_type, statement.span, statement.label)
|
||||||
} else {
|
} else {
|
||||||
value = build_expr(
|
value = build_expr(
|
||||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
@@ -4286,7 +4292,7 @@ build_block :: proc(
|
|||||||
if statement.name == checker.sink_symbol {
|
if statement.name == checker.sink_symbol {
|
||||||
value: hir.Expr_Id
|
value: hir.Expr_Id
|
||||||
if statement.expr == ast.INVALID_EXPR {
|
if statement.expr == ast.INVALID_EXPR {
|
||||||
value, _ = build_value_source(ctx, &body, statement.body, types.INVALID, statement.span)
|
value, _ = build_value_source(ctx, &body, statement.body, types.INVALID, statement.span, statement.label)
|
||||||
} else {
|
} else {
|
||||||
value = build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
value = build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
||||||
}
|
}
|
||||||
@@ -4330,7 +4336,7 @@ build_block :: proc(
|
|||||||
}
|
}
|
||||||
value: hir.Expr_Id
|
value: hir.Expr_Id
|
||||||
if statement.expr == ast.INVALID_EXPR {
|
if statement.expr == ast.INVALID_EXPR {
|
||||||
value, _ = build_value_source(ctx, &body, statement.body, local.type, statement.span)
|
value, _ = build_value_source(ctx, &body, statement.body, local.type, statement.span, statement.label)
|
||||||
} else {
|
} else {
|
||||||
value = build_expr(
|
value = build_expr(
|
||||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
@@ -4596,7 +4602,9 @@ build_block :: proc(
|
|||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
}
|
}
|
||||||
append(ctx.loop_defer_starts, len(ctx.defers^))
|
append(ctx.loop_defer_starts, len(ctx.defers^))
|
||||||
|
append(ctx.loop_labels, statement.label)
|
||||||
loop_body := build_block(ctx, statement.body)
|
loop_body := build_block(ctx, statement.body)
|
||||||
|
pop(ctx.loop_labels)
|
||||||
pop(ctx.loop_defer_starts)
|
pop(ctx.loop_defer_starts)
|
||||||
update := hir.INVALID_STMT
|
update := hir.INVALID_STMT
|
||||||
if statement.update != ast.INVALID_STMT {
|
if statement.update != ast.INVALID_STMT {
|
||||||
@@ -4611,6 +4619,7 @@ build_block :: proc(
|
|||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
kind=.While,
|
kind=.While,
|
||||||
span=statement.span,
|
span=statement.span,
|
||||||
|
label=statement.label,
|
||||||
expr=condition,
|
expr=condition,
|
||||||
then_body=loop_body,
|
then_body=loop_body,
|
||||||
update=update,
|
update=update,
|
||||||
@@ -4690,7 +4699,9 @@ build_block :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
append(ctx.loop_defer_starts, len(ctx.defers^))
|
append(ctx.loop_defer_starts, len(ctx.defers^))
|
||||||
|
append(ctx.loop_labels, statement.label)
|
||||||
loop_body := build_block(ctx, statement.body, capture_start)
|
loop_body := build_block(ctx, statement.body, capture_start)
|
||||||
|
pop(ctx.loop_labels)
|
||||||
pop(ctx.loop_defer_starts)
|
pop(ctx.loop_defer_starts)
|
||||||
resize(ctx.locals, capture_start)
|
resize(ctx.locals, capture_start)
|
||||||
|
|
||||||
@@ -4699,6 +4710,7 @@ build_block :: proc(
|
|||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
kind=.For,
|
kind=.For,
|
||||||
span=statement.span,
|
span=statement.span,
|
||||||
|
label=statement.label,
|
||||||
local=item_local,
|
local=item_local,
|
||||||
index_local=index_local,
|
index_local=index_local,
|
||||||
expr=iterable,
|
expr=iterable,
|
||||||
@@ -4723,11 +4735,29 @@ build_block :: proc(
|
|||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
}
|
}
|
||||||
case .Break, .Continue:
|
case .Break, .Continue:
|
||||||
// Inside a `defer`, `loop_floor` hides the enclosing loops so only loops
|
// `break :L` / `continue :L` targets the innermost enclosing loop labeled `L`;
|
||||||
// opened within the defer count.
|
// an unlabeled one targets the innermost loop. Inside a `defer`, `loop_floor`
|
||||||
if len(ctx.loop_defer_starts^) <= ctx.loop_floor {
|
// hides the loops opened outside the defer.
|
||||||
|
target_index := -1
|
||||||
|
if symbol.is_valid(statement.label) {
|
||||||
|
for i := len(ctx.loop_labels^) - 1; i >= ctx.loop_floor; i -= 1 {
|
||||||
|
if ctx.loop_labels^[i] == statement.label {
|
||||||
|
target_index = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if len(ctx.loop_defer_starts^) > ctx.loop_floor {
|
||||||
|
target_index = len(ctx.loop_defer_starts^) - 1
|
||||||
|
}
|
||||||
|
if target_index < 0 {
|
||||||
keyword := "break" if statement.kind == .Break else "continue"
|
keyword := "break" if statement.kind == .Break else "continue"
|
||||||
id := source.addf(checker.diagnostics, statement.span, "'%s' outside of a loop", keyword)
|
id: source.Diagnostic_Id
|
||||||
|
if symbol.is_valid(statement.label) {
|
||||||
|
id = source.addf(checker.diagnostics, statement.span,
|
||||||
|
"no enclosing loop is labeled '%s'", symbol_text(checker, statement.label))
|
||||||
|
} else {
|
||||||
|
id = source.addf(checker.diagnostics, statement.span, "'%s' outside of a loop", keyword)
|
||||||
|
}
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
@@ -4736,13 +4766,13 @@ build_block :: proc(
|
|||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Exit the loop body and any blocks between here and it: run their
|
// Exit the loop body and any blocks between here and the target loop: run their
|
||||||
// deferred statements down to and including the innermost loop body.
|
// deferred statements down to and including the target loop body.
|
||||||
flush_defers(ctx, &body, ctx.loop_defer_starts^[len(ctx.loop_defer_starts^) - 1])
|
flush_defers(ctx, &body, ctx.loop_defer_starts^[target_index])
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
kind = .Break if statement.kind == .Break else .Continue,
|
kind = .Break if statement.kind == .Break else .Continue,
|
||||||
span = statement.span, expr = hir.INVALID_EXPR,
|
span = statement.span, label = statement.label, expr = hir.INVALID_EXPR,
|
||||||
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Block:
|
case .Block:
|
||||||
@@ -4767,20 +4797,7 @@ build_block :: proc(
|
|||||||
}
|
}
|
||||||
if target_index < 0 {
|
if target_index < 0 {
|
||||||
id := source.addf(checker.diagnostics, statement.span,
|
id := source.addf(checker.diagnostics, statement.span,
|
||||||
"no enclosing value loop is labeled '%s'", symbol_text(checker, statement.label))
|
"no enclosing value loop or block is labeled '%s'", symbol_text(checker, statement.label))
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(&checker.module.statements, hir.Stmt{
|
|
||||||
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
|
||||||
local = hir.INVALID_LOCAL, diagnostic = id,
|
|
||||||
})
|
|
||||||
ctx.problematic^ = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// ponytail: a `yield :blk` lowers to milestone-18 `break`, which targets the
|
|
||||||
// innermost loop only, so the label must name the innermost value loop.
|
|
||||||
if target_index != len(ctx.yield_targets^) - 1 {
|
|
||||||
id := source.addf(checker.diagnostics, statement.span,
|
|
||||||
"'yield :%s' must target the innermost loop", symbol_text(checker, statement.label))
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
@@ -4805,11 +4822,11 @@ build_block :: proc(
|
|||||||
}
|
}
|
||||||
// slot = value (the slot is un-nameable, so no defer can mutate it; no spill).
|
// slot = value (the slot is un-nameable, so no defer can mutate it; no spill).
|
||||||
emit_slot_assign(checker, &body, target.slot, yielded, statement.span)
|
emit_slot_assign(checker, &body, target.slot, yielded, statement.span)
|
||||||
// Exit the loop: flush defers down to the loop body, then break.
|
// Exit the target: flush defers down to its body, then a labeled break.
|
||||||
flush_defers(ctx, &body, ctx.loop_defer_starts^[len(ctx.loop_defer_starts^) - 1])
|
flush_defers(ctx, &body, target.defer_floor)
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
kind = .Break, span = statement.span, expr = hir.INVALID_EXPR,
|
kind = .Break, span = statement.span, label = target.label, expr = hir.INVALID_EXPR,
|
||||||
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
@@ -4818,7 +4835,7 @@ build_block :: proc(
|
|||||||
// is peeled by the value builders (value block / if branch / loop fall-through).
|
// is peeled by the value builders (value block / if branch / loop fall-through).
|
||||||
id := source.add(
|
id := source.add(
|
||||||
checker.diagnostics, statement.span,
|
checker.diagnostics, statement.span,
|
||||||
"'yield' is only valid as the final statement of a value block, or as 'yield :label' inside a labeled value loop",
|
"'yield' is only valid as the final statement of a value block, or as 'yield :label' inside a labeled value loop or block",
|
||||||
)
|
)
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
@@ -4960,16 +4977,21 @@ build_value_block :: proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// build_value_source feeds a declaration/assignment RHS into the right value builder:
|
// build_value_source feeds a declaration/assignment RHS into the right value builder:
|
||||||
// a `{ ... }` block, an `if` whose branches yield, or a `for`/`while` whose iterations
|
// a labeled `blk: { ... }` block, a plain `{ ... }` block, an `if` whose branches yield,
|
||||||
// yield. All three return the produced value and its type for the enclosing binding.
|
// or a `for`/`while` whose iterations yield. All return the produced value and its type
|
||||||
|
// for the enclosing binding. `label` is the labeled-block label (INVALID otherwise).
|
||||||
build_value_source :: proc(
|
build_value_source :: proc(
|
||||||
ctx: ^Build_Ctx,
|
ctx: ^Build_Ctx,
|
||||||
body: ^[dynamic]hir.Stmt_Id,
|
body: ^[dynamic]hir.Stmt_Id,
|
||||||
body_stmts: []ast.Stmt_Id,
|
body_stmts: []ast.Stmt_Id,
|
||||||
expected: types.Type,
|
expected: types.Type,
|
||||||
span: source.Span,
|
span: source.Span,
|
||||||
|
label := symbol.INVALID,
|
||||||
) -> (value: hir.Expr_Id, value_type: types.Type) {
|
) -> (value: hir.Expr_Id, value_type: types.Type) {
|
||||||
checker := ctx.checker
|
checker := ctx.checker
|
||||||
|
if symbol.is_valid(label) {
|
||||||
|
return build_value_labeled_block(ctx, body, body_stmts, label, expected, span)
|
||||||
|
}
|
||||||
if len(body_stmts) == 1 {
|
if len(body_stmts) == 1 {
|
||||||
#partial switch checker.ast_module.statements[body_stmts[0]].kind {
|
#partial switch checker.ast_module.statements[body_stmts[0]].kind {
|
||||||
case .If:
|
case .If:
|
||||||
@@ -5342,6 +5364,83 @@ value_loop_element_type :: proc(ctx: ^Build_Ctx, loop_stmt: ast.Stmt) -> types.T
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// build_value_labeled_block turns `x :: blk: { …; yield :blk v }` into a result slot each
|
||||||
|
// `yield :blk` assigns (via the build_block `.Yield` desugar → `slot = v; break :blk`), then
|
||||||
|
// reads it after the block. Every path must yield (or otherwise exit); HIR holds a `.Block`
|
||||||
|
// that emits the body and the exit label the labeled breaks branch to. No iteration / no
|
||||||
|
// fall-through (unlike a value loop). The type is the annotation when typed, else the first
|
||||||
|
// concrete `yield :blk`'s type (optional when any yield is `none`).
|
||||||
|
build_value_labeled_block :: proc(
|
||||||
|
ctx: ^Build_Ctx,
|
||||||
|
body: ^[dynamic]hir.Stmt_Id,
|
||||||
|
block_stmts: []ast.Stmt_Id,
|
||||||
|
label: symbol.Id,
|
||||||
|
expected: types.Type,
|
||||||
|
span: source.Span,
|
||||||
|
) -> (value: hir.Expr_Id, value_type: types.Type) {
|
||||||
|
checker := ctx.checker
|
||||||
|
result_optional := loop_yields_none(checker, block_stmts)
|
||||||
|
slot := hir.INVALID_LOCAL
|
||||||
|
slot_type := types.INVALID
|
||||||
|
if is_runtime_type(checker, expected) {
|
||||||
|
slot_type = expected
|
||||||
|
slot = new_value_slot(ctx, slot_type)
|
||||||
|
result_optional = types.is_optional(slot_type, &checker.module.types)
|
||||||
|
} else if result_optional {
|
||||||
|
// Untyped block that also yields `none`: pre-type the element from the first
|
||||||
|
// concrete yield (a block has no captures, so a capture-free probe suffices).
|
||||||
|
elem := first_concrete_yield_expr(checker, block_stmts)
|
||||||
|
if elem != ast.INVALID_EXPR {
|
||||||
|
probe := build_expr(checker, elem, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
||||||
|
if checker.module.exprs[probe].kind != .Invalid {
|
||||||
|
slot_type = types.optional(&checker.module.types, checker.module.exprs[probe].type)
|
||||||
|
slot = new_value_slot(ctx, slot_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
append(ctx.yield_targets, Yield_Target{
|
||||||
|
label = label, slot = slot, slot_type = slot_type, result_optional = result_optional,
|
||||||
|
defer_floor = len(ctx.defers^),
|
||||||
|
})
|
||||||
|
built := build_block(ctx, block_stmts)
|
||||||
|
target := pop(ctx.yield_targets)
|
||||||
|
|
||||||
|
if target.slot == hir.INVALID_LOCAL {
|
||||||
|
for s in built {
|
||||||
|
append(body, s)
|
||||||
|
}
|
||||||
|
delete(built, checker.allocator)
|
||||||
|
id := source.add(checker.diagnostics, span,
|
||||||
|
"could not determine the value block's yield type; annotate the binding")
|
||||||
|
ctx.problematic^ = true
|
||||||
|
return invalid_hir_expr(checker, span, id), types.INVALID
|
||||||
|
}
|
||||||
|
// Every path must `yield :blk` (or return/break out); otherwise a path falls through
|
||||||
|
// to the slot read with a poison value.
|
||||||
|
if !all_paths_exit(&checker.module, built) {
|
||||||
|
for s in built {
|
||||||
|
append(body, s)
|
||||||
|
}
|
||||||
|
delete(built, checker.allocator)
|
||||||
|
id := source.add(checker.diagnostics, span, "a labeled value block must 'yield' on every path")
|
||||||
|
ctx.problematic^ = true
|
||||||
|
return invalid_hir_expr(checker, span, id), types.INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
append(body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Declaration, span = span, local = target.slot, expr = hir.INVALID_EXPR,
|
||||||
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append(body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Block, span = span, label = label, then_body = built,
|
||||||
|
local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
value = slot_read(checker, target.slot, target.slot_type, span)
|
||||||
|
return value, target.slot_type
|
||||||
|
}
|
||||||
|
|
||||||
// build_value_loop turns a labeled `for/while ... blk: { … }` whose body ends in a
|
// build_value_loop turns a labeled `for/while ... blk: { … }` whose body ends in a
|
||||||
// fall-through `yield` (and may early-exit via `yield :blk x`) into a result slot:
|
// fall-through `yield` (and may early-exit via `yield :blk x`) into a result slot:
|
||||||
// the fall-through value initializes the slot before the loop, each `yield :blk x`
|
// the fall-through value initializes the slot before the loop, each `yield :blk x`
|
||||||
@@ -5395,6 +5494,7 @@ build_value_loop :: proc(
|
|||||||
}
|
}
|
||||||
append(ctx.yield_targets, Yield_Target{
|
append(ctx.yield_targets, Yield_Target{
|
||||||
label = loop_stmt.label, slot = slot, slot_type = slot_type, result_optional = result_optional,
|
label = loop_stmt.label, slot = slot, slot_type = slot_type, result_optional = result_optional,
|
||||||
|
defer_floor = len(ctx.defers^),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Build the loop with the fall-through peeled off, reusing the normal For/While arm.
|
// Build the loop with the fall-through peeled off, reusing the normal For/While arm.
|
||||||
@@ -5620,6 +5720,8 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
defers.allocator = checker.allocator
|
defers.allocator = checker.allocator
|
||||||
loop_defer_starts: [dynamic]int
|
loop_defer_starts: [dynamic]int
|
||||||
loop_defer_starts.allocator = checker.allocator
|
loop_defer_starts.allocator = checker.allocator
|
||||||
|
loop_labels: [dynamic]symbol.Id
|
||||||
|
loop_labels.allocator = checker.allocator
|
||||||
yield_targets: [dynamic]Yield_Target
|
yield_targets: [dynamic]Yield_Target
|
||||||
yield_targets.allocator = checker.allocator
|
yield_targets.allocator = checker.allocator
|
||||||
ctx := Build_Ctx{
|
ctx := Build_Ctx{
|
||||||
@@ -5635,6 +5737,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
problematic = &problematic,
|
problematic = &problematic,
|
||||||
defers = &defers,
|
defers = &defers,
|
||||||
loop_defer_starts = &loop_defer_starts,
|
loop_defer_starts = &loop_defer_starts,
|
||||||
|
loop_labels = &loop_labels,
|
||||||
yield_targets = &yield_targets,
|
yield_targets = &yield_targets,
|
||||||
}
|
}
|
||||||
block := build_block(&ctx, function.body)
|
block := build_block(&ctx, function.body)
|
||||||
@@ -5685,6 +5788,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
}
|
}
|
||||||
delete(defers)
|
delete(defers)
|
||||||
delete(loop_defer_starts)
|
delete(loop_defer_starts)
|
||||||
|
delete(loop_labels)
|
||||||
delete(yield_targets)
|
delete(yield_targets)
|
||||||
delete(locals)
|
delete(locals)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,6 +156,9 @@ Stmt_Kind :: enum u8 {
|
|||||||
For,
|
For,
|
||||||
Break,
|
Break,
|
||||||
Continue,
|
Continue,
|
||||||
|
// A labeled value block (`blk: { … yield :blk v }`): lowers to its `then_body`
|
||||||
|
// followed by an exit label, so a `yield :blk` (a labeled break) lands after it.
|
||||||
|
Block,
|
||||||
}
|
}
|
||||||
|
|
||||||
Assignment_Op :: enum u8 {
|
Assignment_Op :: enum u8 {
|
||||||
@@ -172,6 +175,9 @@ Stmt :: struct {
|
|||||||
span: source.Span,
|
span: source.Span,
|
||||||
local: Local_Id,
|
local: Local_Id,
|
||||||
index_local: Local_Id,
|
index_local: Local_Id,
|
||||||
|
// `While`/`For`/`Block` may carry a label; a labeled `Break`/`Continue` targets the
|
||||||
|
// matching enclosing loop or value block. `INVALID` when absent.
|
||||||
|
label: symbol.Id,
|
||||||
target: Expr_Id,
|
target: Expr_Id,
|
||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
iterator_type: types.Type,
|
iterator_type: types.Type,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package lower
|
|||||||
import "../hir"
|
import "../hir"
|
||||||
import "../ir"
|
import "../ir"
|
||||||
import "../source"
|
import "../source"
|
||||||
|
import "../symbol"
|
||||||
import "../target"
|
import "../target"
|
||||||
import "../types"
|
import "../types"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
@@ -22,11 +23,15 @@ State :: struct {
|
|||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
|
|
||||||
// `break` branches to `exit_lbl`; `continue` branches to `continue_lbl` (the
|
// An enclosing exit target. `break` branches to `exit_lbl`; `continue` branches to
|
||||||
// loop's update/latch, which runs the update clause then re-tests the condition).
|
// `continue_lbl` (the loop's update/latch, which runs the update clause then re-tests
|
||||||
|
// the condition). A labeled `break`/`continue`/`yield` matches `label`; a value block is
|
||||||
|
// `is_loop = false` (it has no `continue` and is skipped by plain `break`/`continue`).
|
||||||
Loop_Ctx :: struct {
|
Loop_Ctx :: struct {
|
||||||
|
label: symbol.Id,
|
||||||
exit_lbl: i64,
|
exit_lbl: i64,
|
||||||
continue_lbl: i64,
|
continue_lbl: i64,
|
||||||
|
is_loop: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fresh_label :: proc(state: ^State) -> i64 {
|
fresh_label :: proc(state: ^State) -> i64 {
|
||||||
@@ -752,10 +757,25 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
case .Break, .Continue:
|
case .Break, .Continue:
|
||||||
// The checker guarantees these only appear inside a loop, so the
|
// Find the target: a labeled `break`/`continue` matches the innermost target
|
||||||
// stack is non-empty; guard defensively regardless.
|
// (loop or value block) with that label; an unlabeled one takes the innermost
|
||||||
if len(state.loops) > 0 {
|
// loop (`continue` and unlabeled targets skip non-loop block targets). The
|
||||||
target := state.loops[len(state.loops)-1]
|
// checker guarantees a match exists; guard defensively regardless.
|
||||||
|
target_index := -1
|
||||||
|
for i := len(state.loops) - 1; i >= 0; i -= 1 {
|
||||||
|
ctx := state.loops[i]
|
||||||
|
if symbol.is_valid(statement.label) {
|
||||||
|
if ctx.label == statement.label && (ctx.is_loop || statement.kind == .Break) {
|
||||||
|
target_index = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if ctx.is_loop {
|
||||||
|
target_index = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if target_index >= 0 {
|
||||||
|
target := state.loops[target_index]
|
||||||
label := target.exit_lbl if statement.kind == .Break else target.continue_lbl
|
label := target.exit_lbl if statement.kind == .Break else target.continue_lbl
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
op=.Br, span=statement.span, type=types.VOID, integer=label,
|
op=.Br, span=statement.span, type=types.VOID, integer=label,
|
||||||
@@ -763,6 +783,27 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
case .Block:
|
||||||
|
// A labeled value block: lower its body, then emit the exit label that its
|
||||||
|
// `yield :blk` (a labeled break) branches to. Not a loop, so plain
|
||||||
|
// `break`/`continue` skip it (is_loop=false).
|
||||||
|
exit_lbl := fresh_label(state)
|
||||||
|
append(&state.loops, Loop_Ctx{label=statement.label, exit_lbl=exit_lbl, continue_lbl=exit_lbl, is_loop=false})
|
||||||
|
lower_statements(state, statement.then_body)
|
||||||
|
pop(&state.loops)
|
||||||
|
// Explicit fall-through to the exit label so the preceding block is terminated
|
||||||
|
// (dead code after a terminator gets a fresh recovery block in the emitter),
|
||||||
|
// mirroring the `br` a `while`/`for` emits before its labels.
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Br, span=statement.span, type=types.VOID, integer=exit_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Label, span=statement.span, type=types.VOID, integer=exit_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
case .Expression, .Sink:
|
case .Expression, .Sink:
|
||||||
_ = lower_expr(state, statement.expr)
|
_ = lower_expr(state, statement.expr)
|
||||||
case .Trap:
|
case .Trap:
|
||||||
@@ -940,7 +981,7 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
|||||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
append(&state.loops, Loop_Ctx{exit_lbl=exit_lbl, continue_lbl=update_lbl})
|
append(&state.loops, Loop_Ctx{label=statement.label, exit_lbl=exit_lbl, continue_lbl=update_lbl, is_loop=true})
|
||||||
lower_statements(state, statement.then_body)
|
lower_statements(state, statement.then_body)
|
||||||
pop(&state.loops)
|
pop(&state.loops)
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
@@ -1072,7 +1113,7 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
|||||||
// e.g. `for 0..=255 |b: u8| { ... continue }` exits cleanly instead of
|
// e.g. `for 0..=255 |b: u8| { ... continue }` exits cleanly instead of
|
||||||
// overflowing the increment on the final element.
|
// overflowing the increment on the final element.
|
||||||
continue_lbl := fresh_label(state)
|
continue_lbl := fresh_label(state)
|
||||||
append(&state.loops, Loop_Ctx{exit_lbl=exit_lbl, continue_lbl=continue_lbl})
|
append(&state.loops, Loop_Ctx{label=statement.label, exit_lbl=exit_lbl, continue_lbl=continue_lbl, is_loop=true})
|
||||||
lower_statements(state, statement.then_body)
|
lower_statements(state, statement.then_body)
|
||||||
pop(&state.loops)
|
pop(&state.loops)
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
@@ -1257,7 +1298,7 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
|||||||
target=ir.INVALID_REF, a=capture_slot, b=captured,
|
target=ir.INVALID_REF, a=capture_slot, b=captured,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
append(&state.loops, Loop_Ctx{exit_lbl=exit_lbl, continue_lbl=update_lbl})
|
append(&state.loops, Loop_Ctx{label=statement.label, exit_lbl=exit_lbl, continue_lbl=update_lbl, is_loop=true})
|
||||||
lower_statements(state, statement.then_body)
|
lower_statements(state, statement.then_body)
|
||||||
pop(&state.loops)
|
pop(&state.loops)
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
|
|||||||
@@ -1036,10 +1036,20 @@ parse_yield :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
// checker rejects them outside a loop.
|
// checker rejects them outside a loop.
|
||||||
parse_loop_control :: proc(parser: ^Parser, kind: ast.Stmt_Kind) -> ast.Stmt_Id {
|
parse_loop_control :: proc(parser: ^Parser, kind: ast.Stmt_Kind) -> ast.Stmt_Id {
|
||||||
marker := advance(parser) // consume 'break' / 'continue'
|
marker := advance(parser) // consume 'break' / 'continue'
|
||||||
|
// `break :outer` / `continue :outer` targets the enclosing loop labeled `outer`.
|
||||||
|
label := symbol.INVALID
|
||||||
|
if _, ok := allow(parser, .Colon); ok {
|
||||||
|
if name, name_ok := allow(parser, .Identifier); name_ok {
|
||||||
|
label = name.symbol
|
||||||
|
} else {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected a loop label after ':'")
|
||||||
|
}
|
||||||
|
}
|
||||||
id := ast.stmt_id(len(parser.module.statements))
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
append(&parser.module.statements, ast.Stmt{
|
append(&parser.module.statements, ast.Stmt{
|
||||||
kind=kind,
|
kind=kind,
|
||||||
span=marker.span,
|
span=marker.span,
|
||||||
|
label=label,
|
||||||
expr=ast.INVALID_EXPR,
|
expr=ast.INVALID_EXPR,
|
||||||
update=ast.INVALID_STMT,
|
update=ast.INVALID_STMT,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
@@ -1169,6 +1179,26 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
kind = .Declaration
|
kind = .Declaration
|
||||||
immutable = operator.kind == .Colon_Colon
|
immutable = operator.kind == .Colon_Colon
|
||||||
}
|
}
|
||||||
|
// A labeled value block (`x :: blk: { … yield :blk v }`): the label lets a
|
||||||
|
// `yield :blk` exit the block past a nested `if`. Block-init body + label.
|
||||||
|
if current(parser).kind == .Identifier && peek(parser).kind == .Colon {
|
||||||
|
label := parse_optional_loop_label(parser)
|
||||||
|
body := parse_block(parser)
|
||||||
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=kind,
|
||||||
|
span=span_from(name.span, previous(parser).span),
|
||||||
|
name=name.symbol,
|
||||||
|
type=type_syntax,
|
||||||
|
immutable=immutable,
|
||||||
|
label=label,
|
||||||
|
target=ast.INVALID_EXPR,
|
||||||
|
expr=ast.INVALID_EXPR,
|
||||||
|
body=body,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
// A `{` on the right is a value block: parse its statements now; the
|
// A `{` on the right is a value block: parse its statements now; the
|
||||||
// checker turns its final `yield` into the declared/assigned value.
|
// checker turns its final `yield` into the declared/assigned value.
|
||||||
if current(parser).kind == .Left_Brace {
|
if current(parser).kind == .Left_Brace {
|
||||||
@@ -1227,6 +1257,22 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
expr := parse_expression(parser)
|
expr := parse_expression(parser)
|
||||||
if _, ok := allow(parser, .Equal); ok {
|
if _, ok := allow(parser, .Equal); ok {
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
|
// A labeled value block assigned to a complex target (`a[i] = blk: { … }`).
|
||||||
|
if current(parser).kind == .Identifier && peek(parser).kind == .Colon {
|
||||||
|
label := parse_optional_loop_label(parser)
|
||||||
|
body := parse_block(parser)
|
||||||
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=.Assignment,
|
||||||
|
span=span_from(parser.module.exprs[expr].span, previous(parser).span),
|
||||||
|
target=expr,
|
||||||
|
label=label,
|
||||||
|
expr=ast.INVALID_EXPR,
|
||||||
|
body=body,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
// A value block assigned to a complex target (`a[i] = { ... }`, `p.f = { ... }`).
|
// A value block assigned to a complex target (`a[i] = { ... }`, `p.f = { ... }`).
|
||||||
if current(parser).kind == .Left_Brace {
|
if current(parser).kind == .Left_Brace {
|
||||||
brace := current(parser)
|
brace := current(parser)
|
||||||
|
|||||||
+23
-10
@@ -2131,11 +2131,10 @@ yield_compiles_and_runs :: proc(t: ^testing.T) {
|
|||||||
status := compiler_core.compile_package("examples/programs/yield", output)
|
status := compiler_core.compile_package("examples/programs/yield", output)
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
// Value blocks (untyped/typed, defer-spill, reassignment), value if-statements
|
// Value blocks, value if-statements (incl. `return` branches and unwrap-`if`),
|
||||||
// (untyped/typed/reassign/else-if/defer, a branch that `return`s instead of yielding,
|
// `orelse`, value loops, labeled value blocks (`blk: { … yield :blk v }`, incl.
|
||||||
// and an unwrap-`if` as a value source), `orelse`, and value loops (a labeled `for`
|
// defer-capture and `{T,none}` → optional), and outer-loop control (`yield :outer v`
|
||||||
// search yielding `?usize` on the found/not-found paths, a labeled `while`, and an
|
// and `break :outer` from an inner loop) together produce 42.
|
||||||
// untyped loop that yields `none` before any concrete value) together produce 42.
|
|
||||||
testing.expect_value(t, state.exit_code, 42)
|
testing.expect_value(t, state.exit_code, 42)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2180,9 +2179,10 @@ yield_misuse_is_diagnosed :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
yield_control_flow_is_diagnosed :: proc(t: ^testing.T) {
|
yield_control_flow_is_diagnosed :: proc(t: ^testing.T) {
|
||||||
// Value if/loop misuse: an `if` value without an `else`; a branch that neither
|
// Value if/loop/block misuse: an `if` value without an `else`; a branch that neither
|
||||||
// yields nor exits on every path (milestone 20.6); a value loop whose body lacks a
|
// yields nor exits on every path (20.6); a value loop whose body lacks a trailing
|
||||||
// trailing fall-through `yield`; and a `yield :label` with no matching value loop.
|
// fall-through `yield`; a `yield :label` with no matching value loop; a labeled value
|
||||||
|
// block that does not yield on every path, and a `break :label` naming no loop (20.7).
|
||||||
text := `main :: func() i32 {
|
text := `main :: func() i32 {
|
||||||
noelse :: if (true) {
|
noelse :: if (true) {
|
||||||
yield 1
|
yield 1
|
||||||
@@ -2198,7 +2198,14 @@ yield_control_flow_is_diagnosed :: proc(t: ^testing.T) {
|
|||||||
for 0..10 |j| stray: {
|
for 0..10 |j| stray: {
|
||||||
yield :stray j
|
yield :stray j
|
||||||
}
|
}
|
||||||
return noelse + badbranch + noloopyield
|
noblockyield :: blk: {
|
||||||
|
if (true) yield :blk 1
|
||||||
|
k2 :: 5
|
||||||
|
}
|
||||||
|
for 0..10 |m| {
|
||||||
|
break :nope
|
||||||
|
}
|
||||||
|
return noelse + badbranch + noloopyield + noblockyield
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
source_file := source.Source{path="test.bro", text=text}
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
@@ -2217,16 +2224,22 @@ yield_control_flow_is_diagnosed :: proc(t: ^testing.T) {
|
|||||||
branch_no_yield := false
|
branch_no_yield := false
|
||||||
loop_no_yield := false
|
loop_no_yield := false
|
||||||
stray_label := false
|
stray_label := false
|
||||||
|
block_no_yield := false
|
||||||
|
bad_break_label := false
|
||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
no_else = no_else || strings.contains(diagnostic.message, "an 'if' used as a value must have an 'else'")
|
no_else = no_else || strings.contains(diagnostic.message, "an 'if' used as a value must have an 'else'")
|
||||||
branch_no_yield = branch_no_yield || strings.contains(diagnostic.message, "a value branch must end with 'yield' or exit on every path")
|
branch_no_yield = branch_no_yield || strings.contains(diagnostic.message, "a value branch must end with 'yield' or exit on every path")
|
||||||
loop_no_yield = loop_no_yield || strings.contains(diagnostic.message, "a value loop's body must end with a 'yield'")
|
loop_no_yield = loop_no_yield || strings.contains(diagnostic.message, "a value loop's body must end with a 'yield'")
|
||||||
stray_label = stray_label || strings.contains(diagnostic.message, "no enclosing value loop is labeled 'stray'")
|
stray_label = stray_label || strings.contains(diagnostic.message, "no enclosing value loop or block is labeled 'stray'")
|
||||||
|
block_no_yield = block_no_yield || strings.contains(diagnostic.message, "a labeled value block must 'yield' on every path")
|
||||||
|
bad_break_label = bad_break_label || strings.contains(diagnostic.message, "no enclosing loop is labeled 'nope'")
|
||||||
}
|
}
|
||||||
testing.expect(t, no_else)
|
testing.expect(t, no_else)
|
||||||
testing.expect(t, branch_no_yield)
|
testing.expect(t, branch_no_yield)
|
||||||
testing.expect(t, loop_no_yield)
|
testing.expect(t, loop_no_yield)
|
||||||
testing.expect(t, stray_label)
|
testing.expect(t, stray_label)
|
||||||
|
testing.expect(t, block_no_yield)
|
||||||
|
testing.expect(t, bad_break_label)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
|||||||
@@ -174,6 +174,72 @@ loop_none_first :: func() i32 {
|
|||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- labels: value blocks + outer-loop yield/break (milestone 20.7) -----------
|
||||||
|
|
||||||
|
# A labeled value block: `yield :blk` exits the block (past a nested `if`) with a
|
||||||
|
# value. Every path must yield.
|
||||||
|
lblock :: func(sel i32) i32 {
|
||||||
|
r :: blk: {
|
||||||
|
base :: 10
|
||||||
|
if (sel == 0) {
|
||||||
|
yield :blk base
|
||||||
|
} else {
|
||||||
|
yield :blk base * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
# An early `yield :blk` skips the rest of the block; the value is captured before
|
||||||
|
# the block's defer runs.
|
||||||
|
lblock_defer :: func() i32 {
|
||||||
|
r :: blk: {
|
||||||
|
n i32 = 5
|
||||||
|
defer n = 999
|
||||||
|
if (true) yield :blk n
|
||||||
|
yield :blk 0
|
||||||
|
}
|
||||||
|
return r # 5, not 999
|
||||||
|
}
|
||||||
|
|
||||||
|
# A labeled block whose `{T, none}` yields resolve the result to an optional.
|
||||||
|
lblock_optional :: func(present i32) i32 {
|
||||||
|
r :: blk: {
|
||||||
|
if (present == 0) yield :blk none
|
||||||
|
yield :blk 8
|
||||||
|
}
|
||||||
|
if r |v| {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# `yield :outer v` exits an OUTER value loop from inside an inner loop.
|
||||||
|
yield_outer :: func(target i32) i32 {
|
||||||
|
found :: for 0..3 |row| outer: {
|
||||||
|
for 0..3 |col| {
|
||||||
|
if (row * 3 + col == target) yield :outer (row * 10 + col)
|
||||||
|
}
|
||||||
|
yield none
|
||||||
|
}
|
||||||
|
if found |v| {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Plain `break :outer` exits an outer loop from an inner loop.
|
||||||
|
break_outer :: func() i32 {
|
||||||
|
count i32 = 0
|
||||||
|
for 0..3 |a| outer: {
|
||||||
|
for 0..3 |b| {
|
||||||
|
count += 1
|
||||||
|
if (a == 1 and b == 1) break :outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count # 5
|
||||||
|
}
|
||||||
|
|
||||||
main :: func() i32 {
|
main :: func() i32 {
|
||||||
if (basic() != 42) return 101
|
if (basic() != 42) return 101
|
||||||
if (typed() != 100) return 102
|
if (typed() != 100) return 102
|
||||||
@@ -201,5 +267,14 @@ main :: func() i32 {
|
|||||||
if (orelse_value(none) != 7) return 121
|
if (orelse_value(none) != 7) return 121
|
||||||
if (loop_none_first() != 0) return 122
|
if (loop_none_first() != 0) return 122
|
||||||
|
|
||||||
|
if (lblock(0) != 10) return 123
|
||||||
|
if (lblock(1) != 20) return 124
|
||||||
|
if (lblock_defer() != 5) return 125
|
||||||
|
if (lblock_optional(0) != -1) return 126
|
||||||
|
if (lblock_optional(1) != 8) return 127
|
||||||
|
if (yield_outer(4) != 11) return 128
|
||||||
|
if (yield_outer(99) != -1) return 129
|
||||||
|
if (break_outer() != 5) return 130
|
||||||
|
|
||||||
return 42
|
return 42
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user