authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2025-12-17 16:43:28+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-11 11:37:16+00:00
loge0108dec54deaf044e869bb76aa4a40813913277
tree401f1bc552634defb00782cf3b3e29f1386de0ec
parentaa2b178029fc466f847c0cb663c3b32c7809a357
signaturelock-open Commit is signed but in an unrecognized format.

AstGen: allow labels to provide separate `break` and `continue` targets

Enhances `GenZir` to allow labels to provide separate `break` and `continue` target blocks and adds some more information on continue targets to communicate whether the target is a switch block or cannot be targeted by `continue` at all. The main motivation is enabling this: ``` const result: u32 = operand catch |err| label: switch (err) { else => continue :label error.MyError, error.MyError => break :label 1, }; ``` to be lowered to something like this: ``` %1 = block({ %2 = is_non_err(%operand) %3 = condbr(%2, { %4 = err_union_payload_unsafe(%operand) %5 = break(%1, result) // targets enclosing `block` }, { %6 = err_union_code(%operand) %7 = switch_block(%6, else => { %8 = switch_continue(%7, "error.MyError") // targets `switch_block` }, "error.MyError" => { %9 = break(%1, @one) // targets enclosing `block` }, ) %10 = break(%1, @void_value) }) }) ``` which makes the non-error case and all breaks from switch prongs, but not continues from switch prongs, peers. This is required to avoid the problems described in gh#11957 for labeled switches without having to introduce a fairly complex special case to the `switch_block_err_union` logic. Since this construct is very rare in practice, introducing this additional complexity just to save a few ZIR bytes is likely not worth it, so the simplified lowering described above will be used instead. As a nice bonus, AstGen can now also detect a `continue` trying to target a labeled block and emit an appropriate error message.

2 files changed, 236 insertions(+), 189 deletions(-)

lib/std/zig/AstGen.zig+226-189
...@@ -2162,92 +2162,101 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn...@@ -2162,92 +2162,101 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
21622162
2163 // Look for the label in the scope.2163 // Look for the label in the scope.
2164 var scope = parent_scope;2164 var scope = parent_scope;
2165 while (true) {2165 find_scope: switch (scope.tag) {
2166 switch (scope.tag) {2166 .gen_zir => {
2167 .gen_zir => {2167 const gen_zir = scope.cast(GenZir).?;
2168 const block_gz = scope.cast(GenZir).?;
21692168
2170 if (block_gz.cur_defer_node.unwrap()) |cur_defer_node| {2169 if (gen_zir.cur_defer_node.unwrap()) |cur_defer_node| {
2171 // We are breaking out of a `defer` block.2170 // We are breaking out of a `defer` block.
2172 return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{2171 return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{
2173 try astgen.errNoteNode(2172 try astgen.errNoteNode(
2174 cur_defer_node,2173 cur_defer_node,
2175 "defer expression here",2174 "defer expression here",
2176 .{},2175 .{},
2177 ),2176 ),
2178 });2177 });
2179 }2178 }
21802179
2181 const block_inst = blk: {2180 if (opt_break_label.unwrap()) |break_label| labeled: {
2182 if (opt_break_label.unwrap()) |break_label| {2181 if (gen_zir.label) |*label| {
2183 if (block_gz.label) |*label| {2182 if (try astgen.tokenIdentEql(label.token, break_label)) {
2184 if (try astgen.tokenIdentEql(label.token, break_label)) {2183 label.used = true;
2185 label.used = true;2184 break :labeled;
2186 break :blk label.block_inst;
2187 }
2188 }
2189 } else if (block_gz.break_block.unwrap()) |i| {
2190 break :blk i;
2191 }2185 }
2192 // If not the target, start over with the parent2186 }
2193 scope = block_gz.parent;2187 // gz without or with different label, continue to parent scopes.
2194 continue;2188 scope = gen_zir.parent;
2195 };2189 continue :find_scope scope.tag;
2196 // If we made it here, this block is the target of the break expr2190 } else if (!gen_zir.allow_unlabeled_control_flow) {
21972191 // This `break` is unlabeled and the gz we've found doesn't allow
2198 const break_tag: Zir.Inst.Tag = if (block_gz.is_inline)2192 // unlabeled control flow. Continue to parent scopes.
2199 .break_inline2193 scope = gen_zir.parent;
2200 else2194 continue :find_scope scope.tag;
2201 .@"break";2195 }
2202
2203 const rhs = opt_rhs.unwrap() orelse {
2204 _ = try rvalue(parent_gz, block_gz.break_result_info, .void_value, node);
2205
2206 try genDefers(parent_gz, scope, parent_scope, .normal_only);
2207
2208 // As our last action before the break, "pop" the error trace if needed
2209 if (!block_gz.is_comptime)
2210 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = block_inst }, .always, node);
22112196
2212 _ = try parent_gz.addBreak(break_tag, block_inst, .void_value);2197 const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)
2213 return Zir.Inst.Ref.unreachable_value;2198 .break_inline
2214 };2199 else
2200 .@"break";
22152201
2216 const operand = try reachableExpr(parent_gz, parent_scope, block_gz.break_result_info, rhs, node);2202 if (opt_rhs.unwrap()) |rhs| {
2203 // We have a `break` operand.
2204 const operand = try reachableExpr(parent_gz, parent_scope, gen_zir.break_result_info, rhs, node);
22172205
2218 try genDefers(parent_gz, scope, parent_scope, .normal_only);2206 try genDefers(parent_gz, scope, parent_scope, .normal_only);
22192207
2220 // As our last action before the break, "pop" the error trace if needed2208 // As our last action before the break, "pop" the error trace if needed
2221 if (!block_gz.is_comptime)2209 if (!gen_zir.is_comptime) {
2222 try restoreErrRetIndex(parent_gz, .{ .block = block_inst }, block_gz.break_result_info, rhs, operand);2210 try restoreErrRetIndex(parent_gz, .{ .block = gen_zir.break_target }, gen_zir.break_result_info, rhs, operand);
22232211 }
2224 switch (block_gz.break_result_info.rl) {2212 switch (gen_zir.break_result_info.rl) {
2225 .ptr => {2213 .ptr => {
2226 // In this case we don't have any mechanism to intercept it;2214 // In this case we don't have any mechanism to intercept it;
2227 // we assume the result location is written, and we break with void.2215 // we assume the result location is written, and we break with void.
2228 _ = try parent_gz.addBreak(break_tag, block_inst, .void_value);2216 _ = try parent_gz.addBreak(break_tag, gen_zir.break_target, .void_value);
2229 },2217 },
2230 .discard => {2218 .discard => {
2231 _ = try parent_gz.addBreak(break_tag, block_inst, .void_value);2219 _ = try parent_gz.addBreak(break_tag, gen_zir.break_target, .void_value);
2232 },2220 },
2233 else => {2221 else => {
2234 _ = try parent_gz.addBreakWithSrcNode(break_tag, block_inst, operand, rhs);2222 _ = try parent_gz.addBreakWithSrcNode(break_tag, gen_zir.break_target, operand, rhs);
2235 },2223 },
2236 }2224 }
2237 return Zir.Inst.Ref.unreachable_value;2225 return .unreachable_value;
2238 },2226 } else {
2239 .local_val => scope = scope.cast(Scope.LocalVal).?.parent,2227 _ = try rvalue(parent_gz, gen_zir.break_result_info, .void_value, node);
2240 .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,2228
2241 .namespace => break,2229 try genDefers(parent_gz, scope, parent_scope, .normal_only);
2242 .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,2230
2243 .top => unreachable,2231 // As our last action before the break, "pop" the error trace if needed
2244 }2232 if (!gen_zir.is_comptime)
2245 }2233 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = gen_zir.break_target }, .always, node);
2246 if (opt_break_label.unwrap()) |break_label| {2234
2247 const label_name = try astgen.identifierTokenString(break_label);2235 _ = try parent_gz.addBreak(break_tag, gen_zir.break_target, .void_value);
2248 return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});2236 return .unreachable_value;
2249 } else {2237 }
2250 return astgen.failNode(node, "break expression outside loop", .{});2238 },
2239 .local_val => {
2240 scope = scope.cast(Scope.LocalVal).?.parent;
2241 continue :find_scope scope.tag;
2242 },
2243 .local_ptr => {
2244 scope = scope.cast(Scope.LocalPtr).?.parent;
2245 continue :find_scope scope.tag;
2246 },
2247 .defer_normal, .defer_error => {
2248 scope = scope.cast(Scope.Defer).?.parent;
2249 continue :find_scope scope.tag;
2250 },
2251 .namespace => {
2252 if (opt_break_label.unwrap()) |break_label| {
2253 const label_name = try astgen.identifierTokenString(break_label);
2254 return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});
2255 } else {
2256 return astgen.failNode(node, "break expression outside loop", .{});
2257 }
2258 },
2259 .top => unreachable,
2251 }2260 }
2252}2261}
22532262
...@@ -2262,100 +2271,116 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)...@@ -2262,100 +2271,116 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
22622271
2263 // Look for the label in the scope.2272 // Look for the label in the scope.
2264 var scope = parent_scope;2273 var scope = parent_scope;
2265 while (true) {2274 find_scope: switch (scope.tag) {
2266 switch (scope.tag) {2275 .gen_zir => {
2267 .gen_zir => {2276 const gen_zir = scope.cast(GenZir).?;
2268 const gen_zir = scope.cast(GenZir).?;
22692277
2270 if (gen_zir.cur_defer_node.unwrap()) |cur_defer_node| {2278 if (gen_zir.cur_defer_node.unwrap()) |cur_defer_node| {
2271 return astgen.failNodeNotes(node, "cannot continue out of defer expression", .{}, &.{2279 return astgen.failNodeNotes(node, "cannot continue out of defer expression", .{}, &.{
2272 try astgen.errNoteNode(2280 try astgen.errNoteNode(
2273 cur_defer_node,2281 cur_defer_node,
2274 "defer expression here",2282 "defer expression here",
2275 .{},2283 .{},
2276 ),2284 ),
2277 });2285 });
2278 }2286 }
2279 const continue_block = gen_zir.continue_block.unwrap() orelse {2287
2280 scope = gen_zir.parent;2288 if (opt_break_label.unwrap()) |break_label| labeled: {
2281 continue;2289 if (gen_zir.label) |*label| {
2282 };2290 if (try astgen.tokenIdentEql(label.token, break_label)) {
2283 if (opt_break_label.unwrap()) |break_label| blk: {2291 switch (gen_zir.continue_target) {
2284 if (gen_zir.label) |*label| {2292 .none => {
2285 if (try astgen.tokenIdentEql(label.token, break_label)) {2293 return astgen.failNode(node, "continue cannot target labeled block", .{});
2286 const maybe_switch_tag = astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)];2294 },
2287 if (opt_rhs != .none) switch (maybe_switch_tag) {2295 .@"break" => if (opt_rhs != .none) {
2288 .switch_block, .switch_block_ref => {},2296 return astgen.failNode(node, "cannot continue loop with operand", .{});
2289 else => return astgen.failNode(node, "cannot continue loop with operand", .{}),2297 },
2290 } else switch (maybe_switch_tag) {2298 .switch_continue => if (opt_rhs == .none) {
2291 .switch_block, .switch_block_ref => return astgen.failNode(node, "cannot continue switch without operand", .{}),2299 return astgen.failNode(node, "cannot continue switch without operand", .{});
2292 else => {},2300 },
2293 }
2294
2295 label.used = true;
2296 label.used_for_continue = true;
2297 break :blk;
2298 }2301 }
2299 }2302 label.used = true;
2300 // found continue but either it has a different label, or no label2303 label.used_for_continue = true;
2301 scope = gen_zir.parent;2304 break :labeled;
2302 continue;
2303 } else if (gen_zir.label) |label| {
2304 // This `continue` is unlabeled. If the gz we've found corresponds to a labeled
2305 // `switch`, ignore it and continue to parent scopes.
2306 switch (astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)]) {
2307 .switch_block, .switch_block_ref => {
2308 scope = gen_zir.parent;
2309 continue;
2310 },
2311 else => {},
2312 }2305 }
2313 }2306 }
2307 // gz without or with different label, continue to parent scopes.
2308 scope = gen_zir.parent;
2309 continue :find_scope scope.tag;
2310 } else if (gen_zir.allow_unlabeled_control_flow) {
2311 // This `continue` is unlabeled. If the gz we've found doesn't
2312 // provide a `continue` target or corresponds to a labeled
2313 // `switch`, ignore it and continue to parent scopes.
2314 switch (gen_zir.continue_target) {
2315 .none, .switch_continue => {
2316 scope = gen_zir.parent;
2317 continue :find_scope scope.tag;
2318 },
2319 .@"break" => {},
2320 }
2321 } else {
2322 // We don't have a break label and the gz we found doesn't allow
2323 // unlabeled control flow, so we continue to its parent scopes.
2324 scope = gen_zir.parent;
2325 continue :find_scope scope.tag;
2326 }
23142327
2315 if (opt_rhs.unwrap()) |rhs| {2328 switch (gen_zir.continue_target) {
2316 // We need to figure out the result info to use.2329 .none => unreachable, // should have failed or continued to parent scopes by now
2317 // The type should match2330 .@"break" => |block| {
2318 const operand = try reachableExpr(parent_gz, parent_scope, gen_zir.continue_result_info, rhs, node);
2319
2320 try genDefers(parent_gz, scope, parent_scope, .normal_only);2331 try genDefers(parent_gz, scope, parent_scope, .normal_only);
23212332
2322 // As our last action before the continue, "pop" the error trace if needed2333 const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)
2323 if (!gen_zir.is_comptime)2334 .break_inline
2324 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = continue_block }, .always, node);2335 else
23252336 .@"break";
2326 _ = try parent_gz.addBreakWithSrcNode(.switch_continue, continue_block, operand, rhs);2337 if (break_tag == .break_inline) {
2327 return Zir.Inst.Ref.unreachable_value;2338 _ = try parent_gz.addUnNode(.check_comptime_control_flow, block.toRef(), node);
2328 }2339 }
2329
2330 try genDefers(parent_gz, scope, parent_scope, .normal_only);
23312340
2332 const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)2341 // As our last action before the continue, "pop" the error trace if needed
2333 .break_inline2342 if (!gen_zir.is_comptime) {
2334 else2343 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = block }, .always, node);
2335 .@"break";2344 }
2336 if (break_tag == .break_inline) {2345 _ = try parent_gz.addBreak(break_tag, block, .void_value);
2337 _ = try parent_gz.addUnNode(.check_comptime_control_flow, continue_block.toRef(), node);2346 return .unreachable_value;
2338 }2347 },
2348 .switch_continue => |switch_block| {
2349 const rhs = opt_rhs.unwrap().?; // checked above
2350 const operand = try reachableExpr(parent_gz, parent_scope, gen_zir.continue_result_info, rhs, node);
23392351
2340 // As our last action before the continue, "pop" the error trace if needed2352 try genDefers(parent_gz, scope, parent_scope, .normal_only);
2341 if (!gen_zir.is_comptime)
2342 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = continue_block }, .always, node);
23432353
2344 _ = try parent_gz.addBreak(break_tag, continue_block, .void_value);2354 // As our last action before the continue, "pop" the error trace if needed
2345 return Zir.Inst.Ref.unreachable_value;2355 if (!gen_zir.is_comptime) {
2346 },2356 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = switch_block }, .always, node);
2347 .local_val => scope = scope.cast(Scope.LocalVal).?.parent,2357 }
2348 .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,2358 _ = try parent_gz.addBreakWithSrcNode(.switch_continue, switch_block, operand, rhs);
2349 .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,2359 return .unreachable_value;
2350 .namespace => break,2360 },
2351 .top => unreachable,2361 }
2352 }2362 },
2353 }2363 .local_val => {
2354 if (opt_break_label.unwrap()) |break_label| {2364 scope = scope.cast(Scope.LocalVal).?.parent;
2355 const label_name = try astgen.identifierTokenString(break_label);2365 continue :find_scope scope.tag;
2356 return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});2366 },
2357 } else {2367 .local_ptr => {
2358 return astgen.failNode(node, "continue expression outside loop", .{});2368 scope = scope.cast(Scope.LocalPtr).?.parent;
2369 continue :find_scope scope.tag;
2370 },
2371 .defer_normal, .defer_error => {
2372 scope = scope.cast(Scope.Defer).?.parent;
2373 continue :find_scope scope.tag;
2374 },
2375 .namespace => {
2376 if (opt_break_label.unwrap()) |break_label| {
2377 const label_name = try astgen.identifierTokenString(break_label);
2378 return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});
2379 } else {
2380 return astgen.failNode(node, "continue expression outside loop", .{});
2381 }
2382 },
2383 .top => unreachable,
2359 }2384 }
2360}2385}
23612386
...@@ -2509,10 +2534,9 @@ fn labeledBlockExpr(...@@ -2509,10 +2534,9 @@ fn labeledBlockExpr(
2509 try gz.instructions.append(astgen.gpa, block_inst);2534 try gz.instructions.append(astgen.gpa, block_inst);
2510 var block_scope = gz.makeSubBlock(parent_scope);2535 var block_scope = gz.makeSubBlock(parent_scope);
2511 block_scope.is_inline = force_comptime;2536 block_scope.is_inline = force_comptime;
2512 block_scope.label = GenZir.Label{2537 block_scope.label = .{ .token = label_token };
2513 .token = label_token,2538 block_scope.break_target = block_inst;
2514 .block_inst = block_inst,2539 block_scope.continue_target = .none;
2515 };
2516 block_scope.setBreakResultInfo(block_ri);2540 block_scope.setBreakResultInfo(block_ri);
2517 if (force_comptime) block_scope.is_comptime = true;2541 if (force_comptime) block_scope.is_comptime = true;
2518 defer block_scope.unstack();2542 defer block_scope.unstack();
...@@ -6574,7 +6598,6 @@ fn whileExpr(...@@ -6574,7 +6598,6 @@ fn whileExpr(
65746598
6575 var loop_scope = parent_gz.makeSubBlock(scope);6599 var loop_scope = parent_gz.makeSubBlock(scope);
6576 loop_scope.is_inline = is_inline;6600 loop_scope.is_inline = is_inline;
6577 loop_scope.setBreakResultInfo(block_ri);
6578 defer loop_scope.unstack();6601 defer loop_scope.unstack();
65796602
6580 var cond_scope = parent_gz.makeSubBlock(&loop_scope.base);6603 var cond_scope = parent_gz.makeSubBlock(&loop_scope.base);
...@@ -6707,14 +6730,13 @@ fn whileExpr(...@@ -6707,14 +6730,13 @@ fn whileExpr(
6707 _ = try loop_scope.addNode(repeat_tag, node);6730 _ = try loop_scope.addNode(repeat_tag, node);
67086731
6709 try loop_scope.setBlockBody(loop_block);6732 try loop_scope.setBlockBody(loop_block);
6710 loop_scope.break_block = loop_block.toOptional();
6711 loop_scope.continue_block = continue_block.toOptional();
6712 if (while_full.label_token) |label_token| {6733 if (while_full.label_token) |label_token| {
6713 loop_scope.label = .{6734 loop_scope.label = .{ .token = label_token };
6714 .token = label_token,
6715 .block_inst = loop_block,
6716 };
6717 }6735 }
6736 loop_scope.allow_unlabeled_control_flow = true;
6737 loop_scope.break_target = loop_block;
6738 loop_scope.continue_target = .{ .@"break" = continue_block };
6739 loop_scope.setBreakResultInfo(block_ri);
67186740
6719 // done adding instructions to loop_scope, can now stack then_scope6741 // done adding instructions to loop_scope, can now stack then_scope
6720 then_scope.instructions_top = then_scope.instructions.items.len;6742 then_scope.instructions_top = then_scope.instructions.items.len;
...@@ -6787,10 +6809,12 @@ fn whileExpr(...@@ -6787,10 +6809,12 @@ fn whileExpr(
6787 break :s &else_scope.base;6809 break :s &else_scope.base;
6788 }6810 }
6789 };6811 };
6790 // Remove the continue block and break block so that `continue` and `break`6812 // Remove label and forbid unlabeled control flow to this scope so that
6791 // control flow apply to outer loops; not this one.6813 // `continue` and `break` control flow apply to outer loops; not this one.
6792 loop_scope.continue_block = .none;6814 loop_scope.label = null;
6793 loop_scope.break_block = .none;6815 loop_scope.allow_unlabeled_control_flow = false;
6816 loop_scope.continue_target = undefined;
6817 loop_scope.break_target = undefined;
6794 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint);6818 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint);
6795 if (is_statement) {6819 if (is_statement) {
6796 _ = try addEnsureResult(&else_scope, else_result, else_node);6820 _ = try addEnsureResult(&else_scope, else_result, else_node);
...@@ -6979,14 +7003,12 @@ fn forExpr(...@@ -6979,14 +7003,12 @@ fn forExpr(
6979 const cond_block = try loop_scope.makeBlockInst(block_tag, node);7003 const cond_block = try loop_scope.makeBlockInst(block_tag, node);
6980 try cond_scope.setBlockBody(cond_block);7004 try cond_scope.setBlockBody(cond_block);
69817005
6982 loop_scope.break_block = loop_block.toOptional();
6983 loop_scope.continue_block = cond_block.toOptional();
6984 if (for_full.label_token) |label_token| {7006 if (for_full.label_token) |label_token| {
6985 loop_scope.label = .{7007 loop_scope.label = .{ .token = label_token };
6986 .token = label_token,
6987 .block_inst = loop_block,
6988 };
6989 }7008 }
7009 loop_scope.allow_unlabeled_control_flow = true;
7010 loop_scope.break_target = loop_block;
7011 loop_scope.continue_target = .{ .@"break" = cond_block };
69907012
6991 const then_node = for_full.ast.then_expr;7013 const then_node = for_full.ast.then_expr;
6992 var then_scope = parent_gz.makeSubBlock(&cond_scope.base);7014 var then_scope = parent_gz.makeSubBlock(&cond_scope.base);
...@@ -7077,10 +7099,12 @@ fn forExpr(...@@ -7077,10 +7099,12 @@ fn forExpr(
70777099
7078 if (for_full.ast.else_expr.unwrap()) |else_node| {7100 if (for_full.ast.else_expr.unwrap()) |else_node| {
7079 const sub_scope = &else_scope.base;7101 const sub_scope = &else_scope.base;
7080 // Remove the continue block and break block so that `continue` and `break`7102 // Remove label and forbid unlabeled control flow to this scope so that
7081 // control flow apply to outer loops; not this one.7103 // `continue` and `break` control flow apply to outer loops; not this one.
7082 loop_scope.continue_block = .none;7104 loop_scope.label = null;
7083 loop_scope.break_block = .none;7105 loop_scope.allow_unlabeled_control_flow = false;
7106 loop_scope.continue_target = undefined;
7107 loop_scope.break_target = undefined;
7084 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint);7108 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint);
7085 if (is_statement) {7109 if (is_statement) {
7086 _ = try addEnsureResult(&else_scope, else_result, else_node);7110 _ = try addEnsureResult(&else_scope, else_result, else_node);
...@@ -7818,7 +7842,9 @@ fn switchExpr(...@@ -7818,7 +7842,9 @@ fn switchExpr(
7818 const switch_block = try parent_gz.makeBlockInst(switch_tag, node);7842 const switch_block = try parent_gz.makeBlockInst(switch_tag, node);
78197843
7820 if (switch_full.label_token) |label_token| {7844 if (switch_full.label_token) |label_token| {
7821 block_scope.continue_block = switch_block.toOptional();7845 block_scope.label = .{ .token = label_token };
7846 block_scope.break_target = switch_block;
7847 block_scope.continue_target = .{ .switch_continue = switch_block };
7822 block_scope.continue_result_info = .{7848 block_scope.continue_result_info = .{
7823 .rl = if (any_payload_is_ref)7849 .rl = if (any_payload_is_ref)
7824 .{ .ref_coerced_ty = raw_operand_ty_ref }7850 .{ .ref_coerced_ty = raw_operand_ty_ref }
...@@ -7826,12 +7852,7 @@ fn switchExpr(...@@ -7826,12 +7852,7 @@ fn switchExpr(
7826 .{ .coerced_ty = raw_operand_ty_ref },7852 .{ .coerced_ty = raw_operand_ty_ref },
7827 };7853 };
78287854
7829 block_scope.label = .{7855 // `break_result_info` already set by `setBreakResultInfo` above.
7830 .token = label_token,
7831 .block_inst = switch_block,
7832 };
7833 // `break` can target this via `label.block_inst`
7834 // `break_result_info` already set by `setBreakResultInfo`
7835 }7856 }
78367857
7837 // We re-use this same scope for all cases, including the special prong, if any.7858 // We re-use this same scope for all cases, including the special prong, if any.
...@@ -11916,8 +11937,8 @@ const GenZir = struct {...@@ -11916,8 +11937,8 @@ const GenZir = struct {
11916 /// whenever we know Sema will analyze the current block with `is_comptime`,11937 /// whenever we know Sema will analyze the current block with `is_comptime`,
11917 /// for instance when we're within a `struct_decl` or a `block_comptime`.11938 /// for instance when we're within a `struct_decl` or a `block_comptime`.
11918 is_comptime: bool,11939 is_comptime: bool,
11919 /// Whether we're in an expression within a `@TypeOf` operand. In this case, closure of runtime11940 /// Whether we're in an expression within a `@TypeOf` operand. In this case,
11920 /// variables is permitted where it is usually not.11941 /// closure of runtime variables is permitted where it is usually not.
11921 is_typeof: bool = false,11942 is_typeof: bool = false,
11922 /// This is set to true for a `GenZir` of a `block_inline`, indicating that11943 /// This is set to true for a `GenZir` of a `block_inline`, indicating that
11923 /// exits from this block should use `break_inline` rather than `break`.11944 /// exits from this block should use `break_inline` rather than `break`.
...@@ -11938,10 +11959,27 @@ const GenZir = struct {...@@ -11938,10 +11959,27 @@ const GenZir = struct {
11938 /// if use is strictly nested. This saves prior size of list for unstacking.11959 /// if use is strictly nested. This saves prior size of list for unstacking.
11939 instructions_top: usize,11960 instructions_top: usize,
11940 label: ?Label = null,11961 label: ?Label = null,
11941 break_block: Zir.Inst.OptionalIndex = .none,11962 /// If `true`, unlabeled `break` and `continue` exprs can target this `GenZir`.
11942 continue_block: Zir.Inst.OptionalIndex = .none,11963 allow_unlabeled_control_flow: bool = false,
11964 /// If `label` is `null` and `unlabeled_control_flow_target` is `false`,
11965 /// this is unused and may be `undefined`.
11966 /// Otherwise, this is the target for a `break` instruction when a `break`
11967 /// targets this `GenZir`.
11968 break_target: Zir.Inst.Index = undefined,
11969 /// If `label` is `null` and `unlabeled_control_flow_target` is `false`,
11970 /// this is unused and may be `undefined`.
11971 continue_target: union(enum) {
11972 /// A `continue` cannot target this `GenZir`; emit an error.
11973 none,
11974 /// Emit a `break` instruction targeting this block.
11975 @"break": Zir.Inst.Index,
11976 /// Emit a `switch_continue` instruction targeting this `switch_block`.
11977 switch_continue: Zir.Inst.Index,
11978 } = undefined,
11943 /// Only valid when setBreakResultInfo is called.11979 /// Only valid when setBreakResultInfo is called.
11944 break_result_info: AstGen.ResultInfo = undefined,11980 break_result_info: AstGen.ResultInfo = undefined,
11981 /// If `continue_target` is *not* `switch_continue`, this is unused and may
11982 /// be `undefined`.
11945 continue_result_info: AstGen.ResultInfo = undefined,11983 continue_result_info: AstGen.ResultInfo = undefined,
1194611984
11947 suspend_node: Ast.Node.OptionalIndex = .none,11985 suspend_node: Ast.Node.OptionalIndex = .none,
...@@ -12008,7 +12046,6 @@ const GenZir = struct {...@@ -12008,7 +12046,6 @@ const GenZir = struct {
1200812046
12009 const Label = struct {12047 const Label = struct {
12010 token: Ast.TokenIndex,12048 token: Ast.TokenIndex,
12011 block_inst: Zir.Inst.Index,
12012 used: bool = false,12049 used: bool = false,
12013 used_for_continue: bool = false,12050 used_for_continue: bool = false,
12014 };12051 };
test/cases/compile_errors/labeled_block_continue.zig created+10
...@@ -0,0 +1,10 @@
1export fn foo() void {
2 const result: u32 = b: {
3 continue :b 123;
4 };
5 _ = result;
6}
7
8// error
9//
10// :3:9: error: continue cannot target labeled block