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
21622162
21632163 // Look for the label in the scope.
21642164 var scope = parent_scope;
2165 while (true) {
2166 switch (scope.tag) {
2167 .gen_zir => {
2168 const block_gz = scope.cast(GenZir).?;
2165 find_scope: switch (scope.tag) {
2166 .gen_zir => {
2167 const gen_zir = scope.cast(GenZir).?;
21692168
2170 if (block_gz.cur_defer_node.unwrap()) |cur_defer_node| {
2171 // We are breaking out of a `defer` block.
2172 return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{
2173 try astgen.errNoteNode(
2174 cur_defer_node,
2175 "defer expression here",
2176 .{},
2177 ),
2178 });
2179 }
2169 if (gen_zir.cur_defer_node.unwrap()) |cur_defer_node| {
2170 // We are breaking out of a `defer` block.
2171 return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{
2172 try astgen.errNoteNode(
2173 cur_defer_node,
2174 "defer expression here",
2175 .{},
2176 ),
2177 });
2178 }
21802179
2181 const block_inst = blk: {
2182 if (opt_break_label.unwrap()) |break_label| {
2183 if (block_gz.label) |*label| {
2184 if (try astgen.tokenIdentEql(label.token, break_label)) {
2185 label.used = true;
2186 break :blk label.block_inst;
2187 }
2188 }
2189 } else if (block_gz.break_block.unwrap()) |i| {
2190 break :blk i;
2180 if (opt_break_label.unwrap()) |break_label| labeled: {
2181 if (gen_zir.label) |*label| {
2182 if (try astgen.tokenIdentEql(label.token, break_label)) {
2183 label.used = true;
2184 break :labeled;
21912185 }
2192 // If not the target, start over with the parent
2193 scope = block_gz.parent;
2194 continue;
2195 };
2196 // If we made it here, this block is the target of the break expr
2197
2198 const break_tag: Zir.Inst.Tag = if (block_gz.is_inline)
2199 .break_inline
2200 else
2201 .@"break";
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);
2186 }
2187 // gz without or with different label, continue to parent scopes.
2188 scope = gen_zir.parent;
2189 continue :find_scope scope.tag;
2190 } else if (!gen_zir.allow_unlabeled_control_flow) {
2191 // This `break` is unlabeled and the gz we've found doesn't allow
2192 // unlabeled control flow. Continue to parent scopes.
2193 scope = gen_zir.parent;
2194 continue :find_scope scope.tag;
2195 }
22112196
2212 _ = try parent_gz.addBreak(break_tag, block_inst, .void_value);
2213 return Zir.Inst.Ref.unreachable_value;
2214 };
2197 const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)
2198 .break_inline
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
22182206 try genDefers(parent_gz, scope, parent_scope, .normal_only);
22192207
22202208 // As our last action before the break, "pop" the error trace if needed
2221 if (!block_gz.is_comptime)
2222 try restoreErrRetIndex(parent_gz, .{ .block = block_inst }, block_gz.break_result_info, rhs, operand);
2223
2224 switch (block_gz.break_result_info.rl) {
2209 if (!gen_zir.is_comptime) {
2210 try restoreErrRetIndex(parent_gz, .{ .block = gen_zir.break_target }, gen_zir.break_result_info, rhs, operand);
2211 }
2212 switch (gen_zir.break_result_info.rl) {
22252213 .ptr => {
22262214 // In this case we don't have any mechanism to intercept it;
22272215 // 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);
22292217 },
22302218 .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);
22322220 },
22332221 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);
22352223 },
22362224 }
2237 return Zir.Inst.Ref.unreachable_value;
2238 },
2239 .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
2240 .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
2241 .namespace => break,
2242 .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
2243 .top => unreachable,
2244 }
2245 }
2246 if (opt_break_label.unwrap()) |break_label| {
2247 const label_name = try astgen.identifierTokenString(break_label);
2248 return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});
2249 } else {
2250 return astgen.failNode(node, "break expression outside loop", .{});
2225 return .unreachable_value;
2226 } else {
2227 _ = try rvalue(parent_gz, gen_zir.break_result_info, .void_value, node);
2228
2229 try genDefers(parent_gz, scope, parent_scope, .normal_only);
2230
2231 // As our last action before the break, "pop" the error trace if needed
2232 if (!gen_zir.is_comptime)
2233 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = gen_zir.break_target }, .always, node);
2234
2235 _ = try parent_gz.addBreak(break_tag, gen_zir.break_target, .void_value);
2236 return .unreachable_value;
2237 }
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,
22512260 }
22522261}
22532262
......@@ -2262,100 +2271,116 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
22622271
22632272 // Look for the label in the scope.
22642273 var scope = parent_scope;
2265 while (true) {
2266 switch (scope.tag) {
2267 .gen_zir => {
2268 const gen_zir = scope.cast(GenZir).?;
2274 find_scope: switch (scope.tag) {
2275 .gen_zir => {
2276 const gen_zir = scope.cast(GenZir).?;
22692277
2270 if (gen_zir.cur_defer_node.unwrap()) |cur_defer_node| {
2271 return astgen.failNodeNotes(node, "cannot continue out of defer expression", .{}, &.{
2272 try astgen.errNoteNode(
2273 cur_defer_node,
2274 "defer expression here",
2275 .{},
2276 ),
2277 });
2278 }
2279 const continue_block = gen_zir.continue_block.unwrap() orelse {
2280 scope = gen_zir.parent;
2281 continue;
2282 };
2283 if (opt_break_label.unwrap()) |break_label| blk: {
2284 if (gen_zir.label) |*label| {
2285 if (try astgen.tokenIdentEql(label.token, break_label)) {
2286 const maybe_switch_tag = astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)];
2287 if (opt_rhs != .none) switch (maybe_switch_tag) {
2288 .switch_block, .switch_block_ref => {},
2289 else => return astgen.failNode(node, "cannot continue loop with operand", .{}),
2290 } else switch (maybe_switch_tag) {
2291 .switch_block, .switch_block_ref => return astgen.failNode(node, "cannot continue switch without operand", .{}),
2292 else => {},
2293 }
2294
2295 label.used = true;
2296 label.used_for_continue = true;
2297 break :blk;
2278 if (gen_zir.cur_defer_node.unwrap()) |cur_defer_node| {
2279 return astgen.failNodeNotes(node, "cannot continue out of defer expression", .{}, &.{
2280 try astgen.errNoteNode(
2281 cur_defer_node,
2282 "defer expression here",
2283 .{},
2284 ),
2285 });
2286 }
2287
2288 if (opt_break_label.unwrap()) |break_label| labeled: {
2289 if (gen_zir.label) |*label| {
2290 if (try astgen.tokenIdentEql(label.token, break_label)) {
2291 switch (gen_zir.continue_target) {
2292 .none => {
2293 return astgen.failNode(node, "continue cannot target labeled block", .{});
2294 },
2295 .@"break" => if (opt_rhs != .none) {
2296 return astgen.failNode(node, "cannot continue loop with operand", .{});
2297 },
2298 .switch_continue => if (opt_rhs == .none) {
2299 return astgen.failNode(node, "cannot continue switch without operand", .{});
2300 },
22982301 }
2299 }
2300 // found continue but either it has a different label, or no label
2301 scope = gen_zir.parent;
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 => {},
2302 label.used = true;
2303 label.used_for_continue = true;
2304 break :labeled;
23122305 }
23132306 }
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| {
2316 // We need to figure out the result info to use.
2317 // The type should match
2318 const operand = try reachableExpr(parent_gz, parent_scope, gen_zir.continue_result_info, rhs, node);
2319
2328 switch (gen_zir.continue_target) {
2329 .none => unreachable, // should have failed or continued to parent scopes by now
2330 .@"break" => |block| {
23202331 try genDefers(parent_gz, scope, parent_scope, .normal_only);
23212332
2322 // As our last action before the continue, "pop" the error trace if needed
2323 if (!gen_zir.is_comptime)
2324 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = continue_block }, .always, node);
2325
2326 _ = try parent_gz.addBreakWithSrcNode(.switch_continue, continue_block, operand, rhs);
2327 return Zir.Inst.Ref.unreachable_value;
2328 }
2329
2330 try genDefers(parent_gz, scope, parent_scope, .normal_only);
2333 const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)
2334 .break_inline
2335 else
2336 .@"break";
2337 if (break_tag == .break_inline) {
2338 _ = try parent_gz.addUnNode(.check_comptime_control_flow, block.toRef(), node);
2339 }
23312340
2332 const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)
2333 .break_inline
2334 else
2335 .@"break";
2336 if (break_tag == .break_inline) {
2337 _ = try parent_gz.addUnNode(.check_comptime_control_flow, continue_block.toRef(), node);
2338 }
2341 // As our last action before the continue, "pop" the error trace if needed
2342 if (!gen_zir.is_comptime) {
2343 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = block }, .always, node);
2344 }
2345 _ = try parent_gz.addBreak(break_tag, block, .void_value);
2346 return .unreachable_value;
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 needed
2341 if (!gen_zir.is_comptime)
2342 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = continue_block }, .always, node);
2352 try genDefers(parent_gz, scope, parent_scope, .normal_only);
23432353
2344 _ = try parent_gz.addBreak(break_tag, continue_block, .void_value);
2345 return Zir.Inst.Ref.unreachable_value;
2346 },
2347 .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
2348 .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
2349 .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
2350 .namespace => break,
2351 .top => unreachable,
2352 }
2353 }
2354 if (opt_break_label.unwrap()) |break_label| {
2355 const label_name = try astgen.identifierTokenString(break_label);
2356 return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});
2357 } else {
2358 return astgen.failNode(node, "continue expression outside loop", .{});
2354 // As our last action before the continue, "pop" the error trace if needed
2355 if (!gen_zir.is_comptime) {
2356 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = switch_block }, .always, node);
2357 }
2358 _ = try parent_gz.addBreakWithSrcNode(.switch_continue, switch_block, operand, rhs);
2359 return .unreachable_value;
2360 },
2361 }
2362 },
2363 .local_val => {
2364 scope = scope.cast(Scope.LocalVal).?.parent;
2365 continue :find_scope scope.tag;
2366 },
2367 .local_ptr => {
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,
23592384 }
23602385}
23612386
......@@ -2509,10 +2534,9 @@ fn labeledBlockExpr(
25092534 try gz.instructions.append(astgen.gpa, block_inst);
25102535 var block_scope = gz.makeSubBlock(parent_scope);
25112536 block_scope.is_inline = force_comptime;
2512 block_scope.label = GenZir.Label{
2513 .token = label_token,
2514 .block_inst = block_inst,
2515 };
2537 block_scope.label = .{ .token = label_token };
2538 block_scope.break_target = block_inst;
2539 block_scope.continue_target = .none;
25162540 block_scope.setBreakResultInfo(block_ri);
25172541 if (force_comptime) block_scope.is_comptime = true;
25182542 defer block_scope.unstack();
......@@ -6574,7 +6598,6 @@ fn whileExpr(
65746598
65756599 var loop_scope = parent_gz.makeSubBlock(scope);
65766600 loop_scope.is_inline = is_inline;
6577 loop_scope.setBreakResultInfo(block_ri);
65786601 defer loop_scope.unstack();
65796602
65806603 var cond_scope = parent_gz.makeSubBlock(&loop_scope.base);
......@@ -6707,14 +6730,13 @@ fn whileExpr(
67076730 _ = try loop_scope.addNode(repeat_tag, node);
67086731
67096732 try loop_scope.setBlockBody(loop_block);
6710 loop_scope.break_block = loop_block.toOptional();
6711 loop_scope.continue_block = continue_block.toOptional();
67126733 if (while_full.label_token) |label_token| {
6713 loop_scope.label = .{
6714 .token = label_token,
6715 .block_inst = loop_block,
6716 };
6734 loop_scope.label = .{ .token = label_token };
67176735 }
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
67196741 // done adding instructions to loop_scope, can now stack then_scope
67206742 then_scope.instructions_top = then_scope.instructions.items.len;
......@@ -6787,10 +6809,12 @@ fn whileExpr(
67876809 break :s &else_scope.base;
67886810 }
67896811 };
6790 // Remove the continue block and break block so that `continue` and `break`
6791 // control flow apply to outer loops; not this one.
6792 loop_scope.continue_block = .none;
6793 loop_scope.break_block = .none;
6812 // Remove label and forbid unlabeled control flow to this scope so that
6813 // `continue` and `break` control flow apply to outer loops; not this one.
6814 loop_scope.label = null;
6815 loop_scope.allow_unlabeled_control_flow = false;
6816 loop_scope.continue_target = undefined;
6817 loop_scope.break_target = undefined;
67946818 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint);
67956819 if (is_statement) {
67966820 _ = try addEnsureResult(&else_scope, else_result, else_node);
......@@ -6979,14 +7003,12 @@ fn forExpr(
69797003 const cond_block = try loop_scope.makeBlockInst(block_tag, node);
69807004 try cond_scope.setBlockBody(cond_block);
69817005
6982 loop_scope.break_block = loop_block.toOptional();
6983 loop_scope.continue_block = cond_block.toOptional();
69847006 if (for_full.label_token) |label_token| {
6985 loop_scope.label = .{
6986 .token = label_token,
6987 .block_inst = loop_block,
6988 };
7007 loop_scope.label = .{ .token = label_token };
69897008 }
7009 loop_scope.allow_unlabeled_control_flow = true;
7010 loop_scope.break_target = loop_block;
7011 loop_scope.continue_target = .{ .@"break" = cond_block };
69907012
69917013 const then_node = for_full.ast.then_expr;
69927014 var then_scope = parent_gz.makeSubBlock(&cond_scope.base);
......@@ -7077,10 +7099,12 @@ fn forExpr(
70777099
70787100 if (for_full.ast.else_expr.unwrap()) |else_node| {
70797101 const sub_scope = &else_scope.base;
7080 // Remove the continue block and break block so that `continue` and `break`
7081 // control flow apply to outer loops; not this one.
7082 loop_scope.continue_block = .none;
7083 loop_scope.break_block = .none;
7102 // Remove label and forbid unlabeled control flow to this scope so that
7103 // `continue` and `break` control flow apply to outer loops; not this one.
7104 loop_scope.label = null;
7105 loop_scope.allow_unlabeled_control_flow = false;
7106 loop_scope.continue_target = undefined;
7107 loop_scope.break_target = undefined;
70847108 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint);
70857109 if (is_statement) {
70867110 _ = try addEnsureResult(&else_scope, else_result, else_node);
......@@ -7818,7 +7842,9 @@ fn switchExpr(
78187842 const switch_block = try parent_gz.makeBlockInst(switch_tag, node);
78197843
78207844 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 };
78227848 block_scope.continue_result_info = .{
78237849 .rl = if (any_payload_is_ref)
78247850 .{ .ref_coerced_ty = raw_operand_ty_ref }
......@@ -7826,12 +7852,7 @@ fn switchExpr(
78267852 .{ .coerced_ty = raw_operand_ty_ref },
78277853 };
78287854
7829 block_scope.label = .{
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`
7855 // `break_result_info` already set by `setBreakResultInfo` above.
78357856 }
78367857
78377858 // We re-use this same scope for all cases, including the special prong, if any.
......@@ -11916,8 +11937,8 @@ const GenZir = struct {
1191611937 /// whenever we know Sema will analyze the current block with `is_comptime`,
1191711938 /// for instance when we're within a `struct_decl` or a `block_comptime`.
1191811939 is_comptime: bool,
11919 /// Whether we're in an expression within a `@TypeOf` operand. In this case, closure of runtime
11920 /// variables is permitted where it is usually not.
11940 /// Whether we're in an expression within a `@TypeOf` operand. In this case,
11941 /// closure of runtime variables is permitted where it is usually not.
1192111942 is_typeof: bool = false,
1192211943 /// This is set to true for a `GenZir` of a `block_inline`, indicating that
1192311944 /// exits from this block should use `break_inline` rather than `break`.
......@@ -11938,10 +11959,27 @@ const GenZir = struct {
1193811959 /// if use is strictly nested. This saves prior size of list for unstacking.
1193911960 instructions_top: usize,
1194011961 label: ?Label = null,
11941 break_block: Zir.Inst.OptionalIndex = .none,
11942 continue_block: Zir.Inst.OptionalIndex = .none,
11962 /// If `true`, unlabeled `break` and `continue` exprs can target this `GenZir`.
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,
1194311979 /// Only valid when setBreakResultInfo is called.
1194411980 break_result_info: AstGen.ResultInfo = undefined,
11981 /// If `continue_target` is *not* `switch_continue`, this is unused and may
11982 /// be `undefined`.
1194511983 continue_result_info: AstGen.ResultInfo = undefined,
1194611984
1194711985 suspend_node: Ast.Node.OptionalIndex = .none,
......@@ -12008,7 +12046,6 @@ const GenZir = struct {
1200812046
1200912047 const Label = struct {
1201012048 token: Ast.TokenIndex,
12011 block_inst: Zir.Inst.Index,
1201212049 used: bool = false,
1201312050 used_for_continue: bool = false,
1201412051 };
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