zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit bce7e7a52ba4dd9b46184925547b40ecedee63b6 (tree)
parent 39ca03e5156219c23b3b64d9fa682947ea3b37fa
Author: Justus Klausecker <justus@klausecker.de>
Date:   Fri,  9 Jan 2026 08:04:26 +0100

AstGen: Re-allow labeled `break` from loop `else` block targeting its label

This fixes a regression from a couple of commits ago; breaking from the
`else` block of a loop to the loop's tag should be allowed when explicitly
targeting the label by name.

Diffstat:
Mlib/std/zig/AstGen.zig | 20+++++++++-----------
Mtest/behavior/for.zig | 17+++++++++++++++++
Mtest/behavior/while.zig | 17+++++++++++++++++
Atest/cases/compile_errors/continue_loop_from_else_block.zig | 17+++++++++++++++++
Mtest/cases/compile_errors/labeled_block_continue.zig | 2+-
5 files changed, 61 insertions(+), 12 deletions(-)

diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig @@ -2276,7 +2276,7 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) if (try astgen.tokenIdentEql(label.token, break_label)) { switch (gen_zir.continue_target) { .none => { - return astgen.failNode(node, "continue cannot target labeled block", .{}); + return astgen.failNode(node, "continue outside of loop or labeled switch expression", .{}); }, .@"break" => if (opt_rhs != .none) { return astgen.failNode(node, "cannot continue loop with operand", .{}); @@ -6803,12 +6803,11 @@ fn whileExpr( break :s &else_scope.base; } }; - // Remove label and forbid unlabeled control flow to this scope so that - // `continue` and `break` control flow apply to outer loops; not this one. - loop_scope.label = null; + // Disallow unlabeled control flow to this scope so that bare `continue` + // and `break` control flow apply to outer loops; not this one. + // Also disallow `continue` targeting the loop label. loop_scope.allow_unlabeled_control_flow = false; - loop_scope.continue_target = undefined; - loop_scope.break_target = undefined; + loop_scope.continue_target = .none; const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint); if (is_statement) { _ = try addEnsureResult(&else_scope, else_result, else_node); @@ -7093,12 +7092,11 @@ fn forExpr( if (for_full.ast.else_expr.unwrap()) |else_node| { const sub_scope = &else_scope.base; - // Remove label and forbid unlabeled control flow to this scope so that - // `continue` and `break` control flow apply to outer loops; not this one. - loop_scope.label = null; + // Disallow unlabeled control flow to this scope so that bare `continue` + // and `break` control flow apply to outer loops; not this one. + // Also disallow `continue` targeting the loop label. loop_scope.allow_unlabeled_control_flow = false; - loop_scope.continue_target = undefined; - loop_scope.break_target = undefined; + loop_scope.continue_target = .none; const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint); if (is_statement) { _ = try addEnsureResult(&else_scope, else_result, else_node); diff --git a/test/behavior/for.zig b/test/behavior/for.zig @@ -524,3 +524,20 @@ test "for loop 0 length range" { comptime unreachable; } } + +test "labeled break from else prong" { + const S = struct { + fn doTheTest(x: u32) !void { + var y: u32 = 0; + const ok = label: while (y < x) : (y += 1) { + if (y == 10) break :label false; + } else { + break :label true; + }; + try expect(ok); + } + }; + + try S.doTheTest(5); + try comptime S.doTheTest(5); +} diff --git a/test/behavior/while.zig b/test/behavior/while.zig @@ -399,3 +399,20 @@ test "breaking from a loop in an if statement" { } else 2; _ = opt; } + +test "labeled break from else prong" { + const S = struct { + fn doTheTest(x: u32) !void { + var y: u32 = 0; + const ok = label: while (y < x) : (y += 1) { + if (y == 10) break :label false; + } else { + break :label true; + }; + try expect(ok); + } + }; + + try S.doTheTest(5); + try comptime S.doTheTest(5); +} diff --git a/test/cases/compile_errors/continue_loop_from_else_block.zig b/test/cases/compile_errors/continue_loop_from_else_block.zig @@ -0,0 +1,17 @@ +export fn entry1() void { + var x: u32 = 0; + result: while (x < 5) : (x += 1) {} else { + continue :result; + } +} + +export fn entry2() void { + result: for (0..5) |_| {} else { + continue :result; + } +} + +// error +// +// :4:9: error: continue outside of loop or labeled switch expression +// :10:9: error: continue outside of loop or labeled switch expression diff --git a/test/cases/compile_errors/labeled_block_continue.zig b/test/cases/compile_errors/labeled_block_continue.zig @@ -7,4 +7,4 @@ export fn foo() void { // error // -// :3:9: error: continue cannot target labeled block +// :3:9: error: continue outside of loop or labeled switch expression