From bce7e7a52ba4dd9b46184925547b40ecedee63b6 Mon Sep 17 00:00:00 2001 From: Justus Klausecker Date: Fri, 9 Jan 2026 08:04:26 +0100 Subject: [PATCH] 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. --- lib/std/zig/AstGen.zig | 20 +++++++++---------- test/behavior/for.zig | 17 ++++++++++++++++ test/behavior/while.zig | 17 ++++++++++++++++ .../continue_loop_from_else_block.zig | 17 ++++++++++++++++ .../compile_errors/labeled_block_continue.zig | 2 +- 5 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 test/cases/compile_errors/continue_loop_from_else_block.zig diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 124543b8836223393607f66605ee5d51e04feebe..01a8373e3f7932453e262ae1b5f420f57a4d91d9 100644 --- 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 index a8ed6ec79ecb0f56e9dfedfda51336eae8d9a3f4..0361c2d19d134fc96edb60c24e59fe8d42e02456 100644 --- 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 index 7a177d56905c751c15a1c923181a78a09a55d746..9b5fe1fb84712911e920b3aa016b5aec9618cb7b 100644 --- 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 new file mode 100644 index 0000000000000000000000000000000000000000..84bd6568a869c75b0056eff2d55822120bc5567a --- /dev/null +++ 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 index 80bf2ba979e9904201ead0a2f27969a9404b4a2f..e81e9486b32f87915fcf8116fa9fdb21d4e46a2b 100644 --- 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 -- 2.54.0