authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-01-09 08:04:26+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-11 11:37:17+00:00
logbce7e7a52ba4dd9b46184925547b40ecedee63b6
tree2735a8f54e9f93f498899a2010101b66ab0b97dc
parent39ca03e5156219c23b3b64d9fa682947ea3b37fa
signaturelock-open Commit is signed but in an unrecognized format.

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.

5 files changed, 61 insertions(+), 12 deletions(-)

lib/std/zig/AstGen.zig+9-11
......@@ -2276,7 +2276,7 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
22762276 if (try astgen.tokenIdentEql(label.token, break_label)) {
22772277 switch (gen_zir.continue_target) {
22782278 .none => {
2279 return astgen.failNode(node, "continue cannot target labeled block", .{});
2279 return astgen.failNode(node, "continue outside of loop or labeled switch expression", .{});
22802280 },
22812281 .@"break" => if (opt_rhs != .none) {
22822282 return astgen.failNode(node, "cannot continue loop with operand", .{});
......@@ -6803,12 +6803,11 @@ fn whileExpr(
68036803 break :s &else_scope.base;
68046804 }
68056805 };
6806 // Remove label and forbid unlabeled control flow to this scope so that
6807 // `continue` and `break` control flow apply to outer loops; not this one.
6808 loop_scope.label = null;
6806 // Disallow unlabeled control flow to this scope so that bare `continue`
6807 // and `break` control flow apply to outer loops; not this one.
6808 // Also disallow `continue` targeting the loop label.
68096809 loop_scope.allow_unlabeled_control_flow = false;
6810 loop_scope.continue_target = undefined;
6811 loop_scope.break_target = undefined;
6810 loop_scope.continue_target = .none;
68126811 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint);
68136812 if (is_statement) {
68146813 _ = try addEnsureResult(&else_scope, else_result, else_node);
......@@ -7093,12 +7092,11 @@ fn forExpr(
70937092
70947093 if (for_full.ast.else_expr.unwrap()) |else_node| {
70957094 const sub_scope = &else_scope.base;
7096 // Remove label and forbid unlabeled control flow to this scope so that
7097 // `continue` and `break` control flow apply to outer loops; not this one.
7098 loop_scope.label = null;
7095 // Disallow unlabeled control flow to this scope so that bare `continue`
7096 // and `break` control flow apply to outer loops; not this one.
7097 // Also disallow `continue` targeting the loop label.
70997098 loop_scope.allow_unlabeled_control_flow = false;
7100 loop_scope.continue_target = undefined;
7101 loop_scope.break_target = undefined;
7099 loop_scope.continue_target = .none;
71027100 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint);
71037101 if (is_statement) {
71047102 _ = try addEnsureResult(&else_scope, else_result, else_node);
test/behavior/for.zig+17
......@@ -524,3 +524,20 @@ test "for loop 0 length range" {
524524 comptime unreachable;
525525 }
526526}
527
528test "labeled break from else prong" {
529 const S = struct {
530 fn doTheTest(x: u32) !void {
531 var y: u32 = 0;
532 const ok = label: while (y < x) : (y += 1) {
533 if (y == 10) break :label false;
534 } else {
535 break :label true;
536 };
537 try expect(ok);
538 }
539 };
540
541 try S.doTheTest(5);
542 try comptime S.doTheTest(5);
543}
test/behavior/while.zig+17
......@@ -399,3 +399,20 @@ test "breaking from a loop in an if statement" {
399399 } else 2;
400400 _ = opt;
401401}
402
403test "labeled break from else prong" {
404 const S = struct {
405 fn doTheTest(x: u32) !void {
406 var y: u32 = 0;
407 const ok = label: while (y < x) : (y += 1) {
408 if (y == 10) break :label false;
409 } else {
410 break :label true;
411 };
412 try expect(ok);
413 }
414 };
415
416 try S.doTheTest(5);
417 try comptime S.doTheTest(5);
418}
test/cases/compile_errors/continue_loop_from_else_block.zig created+17
......@@ -0,0 +1,17 @@
1export fn entry1() void {
2 var x: u32 = 0;
3 result: while (x < 5) : (x += 1) {} else {
4 continue :result;
5 }
6}
7
8export fn entry2() void {
9 result: for (0..5) |_| {} else {
10 continue :result;
11 }
12}
13
14// error
15//
16// :4:9: error: continue outside of loop or labeled switch expression
17// :10:9: error: continue outside of loop or labeled switch expression
test/cases/compile_errors/labeled_block_continue.zig+1-1
......@@ -7,4 +7,4 @@ export fn foo() void {
77
88// error
99//
10// :3:9: error: continue cannot target labeled block
10// :3:9: error: continue outside of loop or labeled switch expression