mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-03-08 01:04:43 +01:00
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.
This commit is contained in:
parent
aa2b178029
commit
e0108dec54
2 changed files with 235 additions and 188 deletions
|
|
@ -2162,92 +2162,101 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
|
|||
|
||||
// Look for the label in the scope.
|
||||
var scope = parent_scope;
|
||||
while (true) {
|
||||
switch (scope.tag) {
|
||||
.gen_zir => {
|
||||
const block_gz = scope.cast(GenZir).?;
|
||||
find_scope: switch (scope.tag) {
|
||||
.gen_zir => {
|
||||
const gen_zir = scope.cast(GenZir).?;
|
||||
|
||||
if (block_gz.cur_defer_node.unwrap()) |cur_defer_node| {
|
||||
// We are breaking out of a `defer` block.
|
||||
return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{
|
||||
try astgen.errNoteNode(
|
||||
cur_defer_node,
|
||||
"defer expression here",
|
||||
.{},
|
||||
),
|
||||
});
|
||||
}
|
||||
if (gen_zir.cur_defer_node.unwrap()) |cur_defer_node| {
|
||||
// We are breaking out of a `defer` block.
|
||||
return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{
|
||||
try astgen.errNoteNode(
|
||||
cur_defer_node,
|
||||
"defer expression here",
|
||||
.{},
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
const block_inst = blk: {
|
||||
if (opt_break_label.unwrap()) |break_label| {
|
||||
if (block_gz.label) |*label| {
|
||||
if (try astgen.tokenIdentEql(label.token, break_label)) {
|
||||
label.used = true;
|
||||
break :blk label.block_inst;
|
||||
}
|
||||
}
|
||||
} else if (block_gz.break_block.unwrap()) |i| {
|
||||
break :blk i;
|
||||
if (opt_break_label.unwrap()) |break_label| labeled: {
|
||||
if (gen_zir.label) |*label| {
|
||||
if (try astgen.tokenIdentEql(label.token, break_label)) {
|
||||
label.used = true;
|
||||
break :labeled;
|
||||
}
|
||||
// If not the target, start over with the parent
|
||||
scope = block_gz.parent;
|
||||
continue;
|
||||
};
|
||||
// If we made it here, this block is the target of the break expr
|
||||
}
|
||||
// gz without or with different label, continue to parent scopes.
|
||||
scope = gen_zir.parent;
|
||||
continue :find_scope scope.tag;
|
||||
} else if (!gen_zir.allow_unlabeled_control_flow) {
|
||||
// This `break` is unlabeled and the gz we've found doesn't allow
|
||||
// unlabeled control flow. Continue to parent scopes.
|
||||
scope = gen_zir.parent;
|
||||
continue :find_scope scope.tag;
|
||||
}
|
||||
|
||||
const break_tag: Zir.Inst.Tag = if (block_gz.is_inline)
|
||||
.break_inline
|
||||
else
|
||||
.@"break";
|
||||
const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)
|
||||
.break_inline
|
||||
else
|
||||
.@"break";
|
||||
|
||||
const rhs = opt_rhs.unwrap() orelse {
|
||||
_ = try rvalue(parent_gz, block_gz.break_result_info, .void_value, node);
|
||||
|
||||
try genDefers(parent_gz, scope, parent_scope, .normal_only);
|
||||
|
||||
// As our last action before the break, "pop" the error trace if needed
|
||||
if (!block_gz.is_comptime)
|
||||
_ = try parent_gz.addRestoreErrRetIndex(.{ .block = block_inst }, .always, node);
|
||||
|
||||
_ = try parent_gz.addBreak(break_tag, block_inst, .void_value);
|
||||
return Zir.Inst.Ref.unreachable_value;
|
||||
};
|
||||
|
||||
const operand = try reachableExpr(parent_gz, parent_scope, block_gz.break_result_info, rhs, node);
|
||||
if (opt_rhs.unwrap()) |rhs| {
|
||||
// We have a `break` operand.
|
||||
const operand = try reachableExpr(parent_gz, parent_scope, gen_zir.break_result_info, rhs, node);
|
||||
|
||||
try genDefers(parent_gz, scope, parent_scope, .normal_only);
|
||||
|
||||
// As our last action before the break, "pop" the error trace if needed
|
||||
if (!block_gz.is_comptime)
|
||||
try restoreErrRetIndex(parent_gz, .{ .block = block_inst }, block_gz.break_result_info, rhs, operand);
|
||||
|
||||
switch (block_gz.break_result_info.rl) {
|
||||
if (!gen_zir.is_comptime) {
|
||||
try restoreErrRetIndex(parent_gz, .{ .block = gen_zir.break_target }, gen_zir.break_result_info, rhs, operand);
|
||||
}
|
||||
switch (gen_zir.break_result_info.rl) {
|
||||
.ptr => {
|
||||
// In this case we don't have any mechanism to intercept it;
|
||||
// we assume the result location is written, and we break with void.
|
||||
_ = try parent_gz.addBreak(break_tag, block_inst, .void_value);
|
||||
_ = try parent_gz.addBreak(break_tag, gen_zir.break_target, .void_value);
|
||||
},
|
||||
.discard => {
|
||||
_ = try parent_gz.addBreak(break_tag, block_inst, .void_value);
|
||||
_ = try parent_gz.addBreak(break_tag, gen_zir.break_target, .void_value);
|
||||
},
|
||||
else => {
|
||||
_ = try parent_gz.addBreakWithSrcNode(break_tag, block_inst, operand, rhs);
|
||||
_ = try parent_gz.addBreakWithSrcNode(break_tag, gen_zir.break_target, operand, rhs);
|
||||
},
|
||||
}
|
||||
return Zir.Inst.Ref.unreachable_value;
|
||||
},
|
||||
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
|
||||
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
|
||||
.namespace => break,
|
||||
.defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
|
||||
.top => unreachable,
|
||||
}
|
||||
}
|
||||
if (opt_break_label.unwrap()) |break_label| {
|
||||
const label_name = try astgen.identifierTokenString(break_label);
|
||||
return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});
|
||||
} else {
|
||||
return astgen.failNode(node, "break expression outside loop", .{});
|
||||
return .unreachable_value;
|
||||
} else {
|
||||
_ = try rvalue(parent_gz, gen_zir.break_result_info, .void_value, node);
|
||||
|
||||
try genDefers(parent_gz, scope, parent_scope, .normal_only);
|
||||
|
||||
// As our last action before the break, "pop" the error trace if needed
|
||||
if (!gen_zir.is_comptime)
|
||||
_ = try parent_gz.addRestoreErrRetIndex(.{ .block = gen_zir.break_target }, .always, node);
|
||||
|
||||
_ = try parent_gz.addBreak(break_tag, gen_zir.break_target, .void_value);
|
||||
return .unreachable_value;
|
||||
}
|
||||
},
|
||||
.local_val => {
|
||||
scope = scope.cast(Scope.LocalVal).?.parent;
|
||||
continue :find_scope scope.tag;
|
||||
},
|
||||
.local_ptr => {
|
||||
scope = scope.cast(Scope.LocalPtr).?.parent;
|
||||
continue :find_scope scope.tag;
|
||||
},
|
||||
.defer_normal, .defer_error => {
|
||||
scope = scope.cast(Scope.Defer).?.parent;
|
||||
continue :find_scope scope.tag;
|
||||
},
|
||||
.namespace => {
|
||||
if (opt_break_label.unwrap()) |break_label| {
|
||||
const label_name = try astgen.identifierTokenString(break_label);
|
||||
return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});
|
||||
} else {
|
||||
return astgen.failNode(node, "break expression outside loop", .{});
|
||||
}
|
||||
},
|
||||
.top => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2262,100 +2271,116 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
|
|||
|
||||
// Look for the label in the scope.
|
||||
var scope = parent_scope;
|
||||
while (true) {
|
||||
switch (scope.tag) {
|
||||
.gen_zir => {
|
||||
const gen_zir = scope.cast(GenZir).?;
|
||||
find_scope: switch (scope.tag) {
|
||||
.gen_zir => {
|
||||
const gen_zir = scope.cast(GenZir).?;
|
||||
|
||||
if (gen_zir.cur_defer_node.unwrap()) |cur_defer_node| {
|
||||
return astgen.failNodeNotes(node, "cannot continue out of defer expression", .{}, &.{
|
||||
try astgen.errNoteNode(
|
||||
cur_defer_node,
|
||||
"defer expression here",
|
||||
.{},
|
||||
),
|
||||
});
|
||||
}
|
||||
const continue_block = gen_zir.continue_block.unwrap() orelse {
|
||||
scope = gen_zir.parent;
|
||||
continue;
|
||||
};
|
||||
if (opt_break_label.unwrap()) |break_label| blk: {
|
||||
if (gen_zir.label) |*label| {
|
||||
if (try astgen.tokenIdentEql(label.token, break_label)) {
|
||||
const maybe_switch_tag = astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)];
|
||||
if (opt_rhs != .none) switch (maybe_switch_tag) {
|
||||
.switch_block, .switch_block_ref => {},
|
||||
else => return astgen.failNode(node, "cannot continue loop with operand", .{}),
|
||||
} else switch (maybe_switch_tag) {
|
||||
.switch_block, .switch_block_ref => return astgen.failNode(node, "cannot continue switch without operand", .{}),
|
||||
else => {},
|
||||
}
|
||||
if (gen_zir.cur_defer_node.unwrap()) |cur_defer_node| {
|
||||
return astgen.failNodeNotes(node, "cannot continue out of defer expression", .{}, &.{
|
||||
try astgen.errNoteNode(
|
||||
cur_defer_node,
|
||||
"defer expression here",
|
||||
.{},
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
label.used = true;
|
||||
label.used_for_continue = true;
|
||||
break :blk;
|
||||
if (opt_break_label.unwrap()) |break_label| labeled: {
|
||||
if (gen_zir.label) |*label| {
|
||||
if (try astgen.tokenIdentEql(label.token, break_label)) {
|
||||
switch (gen_zir.continue_target) {
|
||||
.none => {
|
||||
return astgen.failNode(node, "continue cannot target labeled block", .{});
|
||||
},
|
||||
.@"break" => if (opt_rhs != .none) {
|
||||
return astgen.failNode(node, "cannot continue loop with operand", .{});
|
||||
},
|
||||
.switch_continue => if (opt_rhs == .none) {
|
||||
return astgen.failNode(node, "cannot continue switch without operand", .{});
|
||||
},
|
||||
}
|
||||
}
|
||||
// found continue but either it has a different label, or no label
|
||||
scope = gen_zir.parent;
|
||||
continue;
|
||||
} else if (gen_zir.label) |label| {
|
||||
// This `continue` is unlabeled. If the gz we've found corresponds to a labeled
|
||||
// `switch`, ignore it and continue to parent scopes.
|
||||
switch (astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)]) {
|
||||
.switch_block, .switch_block_ref => {
|
||||
scope = gen_zir.parent;
|
||||
continue;
|
||||
},
|
||||
else => {},
|
||||
label.used = true;
|
||||
label.used_for_continue = true;
|
||||
break :labeled;
|
||||
}
|
||||
}
|
||||
// gz without or with different label, continue to parent scopes.
|
||||
scope = gen_zir.parent;
|
||||
continue :find_scope scope.tag;
|
||||
} else if (gen_zir.allow_unlabeled_control_flow) {
|
||||
// This `continue` is unlabeled. If the gz we've found doesn't
|
||||
// provide a `continue` target or corresponds to a labeled
|
||||
// `switch`, ignore it and continue to parent scopes.
|
||||
switch (gen_zir.continue_target) {
|
||||
.none, .switch_continue => {
|
||||
scope = gen_zir.parent;
|
||||
continue :find_scope scope.tag;
|
||||
},
|
||||
.@"break" => {},
|
||||
}
|
||||
} else {
|
||||
// We don't have a break label and the gz we found doesn't allow
|
||||
// unlabeled control flow, so we continue to its parent scopes.
|
||||
scope = gen_zir.parent;
|
||||
continue :find_scope scope.tag;
|
||||
}
|
||||
|
||||
if (opt_rhs.unwrap()) |rhs| {
|
||||
// We need to figure out the result info to use.
|
||||
// The type should match
|
||||
switch (gen_zir.continue_target) {
|
||||
.none => unreachable, // should have failed or continued to parent scopes by now
|
||||
.@"break" => |block| {
|
||||
try genDefers(parent_gz, scope, parent_scope, .normal_only);
|
||||
|
||||
const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)
|
||||
.break_inline
|
||||
else
|
||||
.@"break";
|
||||
if (break_tag == .break_inline) {
|
||||
_ = try parent_gz.addUnNode(.check_comptime_control_flow, block.toRef(), node);
|
||||
}
|
||||
|
||||
// As our last action before the continue, "pop" the error trace if needed
|
||||
if (!gen_zir.is_comptime) {
|
||||
_ = try parent_gz.addRestoreErrRetIndex(.{ .block = block }, .always, node);
|
||||
}
|
||||
_ = try parent_gz.addBreak(break_tag, block, .void_value);
|
||||
return .unreachable_value;
|
||||
},
|
||||
.switch_continue => |switch_block| {
|
||||
const rhs = opt_rhs.unwrap().?; // checked above
|
||||
const operand = try reachableExpr(parent_gz, parent_scope, gen_zir.continue_result_info, rhs, node);
|
||||
|
||||
try genDefers(parent_gz, scope, parent_scope, .normal_only);
|
||||
|
||||
// As our last action before the continue, "pop" the error trace if needed
|
||||
if (!gen_zir.is_comptime)
|
||||
_ = try parent_gz.addRestoreErrRetIndex(.{ .block = continue_block }, .always, node);
|
||||
|
||||
_ = try parent_gz.addBreakWithSrcNode(.switch_continue, continue_block, operand, rhs);
|
||||
return Zir.Inst.Ref.unreachable_value;
|
||||
}
|
||||
|
||||
try genDefers(parent_gz, scope, parent_scope, .normal_only);
|
||||
|
||||
const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)
|
||||
.break_inline
|
||||
else
|
||||
.@"break";
|
||||
if (break_tag == .break_inline) {
|
||||
_ = try parent_gz.addUnNode(.check_comptime_control_flow, continue_block.toRef(), node);
|
||||
}
|
||||
|
||||
// As our last action before the continue, "pop" the error trace if needed
|
||||
if (!gen_zir.is_comptime)
|
||||
_ = try parent_gz.addRestoreErrRetIndex(.{ .block = continue_block }, .always, node);
|
||||
|
||||
_ = try parent_gz.addBreak(break_tag, continue_block, .void_value);
|
||||
return Zir.Inst.Ref.unreachable_value;
|
||||
},
|
||||
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
|
||||
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
|
||||
.defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
|
||||
.namespace => break,
|
||||
.top => unreachable,
|
||||
}
|
||||
}
|
||||
if (opt_break_label.unwrap()) |break_label| {
|
||||
const label_name = try astgen.identifierTokenString(break_label);
|
||||
return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});
|
||||
} else {
|
||||
return astgen.failNode(node, "continue expression outside loop", .{});
|
||||
if (!gen_zir.is_comptime) {
|
||||
_ = try parent_gz.addRestoreErrRetIndex(.{ .block = switch_block }, .always, node);
|
||||
}
|
||||
_ = try parent_gz.addBreakWithSrcNode(.switch_continue, switch_block, operand, rhs);
|
||||
return .unreachable_value;
|
||||
},
|
||||
}
|
||||
},
|
||||
.local_val => {
|
||||
scope = scope.cast(Scope.LocalVal).?.parent;
|
||||
continue :find_scope scope.tag;
|
||||
},
|
||||
.local_ptr => {
|
||||
scope = scope.cast(Scope.LocalPtr).?.parent;
|
||||
continue :find_scope scope.tag;
|
||||
},
|
||||
.defer_normal, .defer_error => {
|
||||
scope = scope.cast(Scope.Defer).?.parent;
|
||||
continue :find_scope scope.tag;
|
||||
},
|
||||
.namespace => {
|
||||
if (opt_break_label.unwrap()) |break_label| {
|
||||
const label_name = try astgen.identifierTokenString(break_label);
|
||||
return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});
|
||||
} else {
|
||||
return astgen.failNode(node, "continue expression outside loop", .{});
|
||||
}
|
||||
},
|
||||
.top => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2509,10 +2534,9 @@ fn labeledBlockExpr(
|
|||
try gz.instructions.append(astgen.gpa, block_inst);
|
||||
var block_scope = gz.makeSubBlock(parent_scope);
|
||||
block_scope.is_inline = force_comptime;
|
||||
block_scope.label = GenZir.Label{
|
||||
.token = label_token,
|
||||
.block_inst = block_inst,
|
||||
};
|
||||
block_scope.label = .{ .token = label_token };
|
||||
block_scope.break_target = block_inst;
|
||||
block_scope.continue_target = .none;
|
||||
block_scope.setBreakResultInfo(block_ri);
|
||||
if (force_comptime) block_scope.is_comptime = true;
|
||||
defer block_scope.unstack();
|
||||
|
|
@ -6574,7 +6598,6 @@ fn whileExpr(
|
|||
|
||||
var loop_scope = parent_gz.makeSubBlock(scope);
|
||||
loop_scope.is_inline = is_inline;
|
||||
loop_scope.setBreakResultInfo(block_ri);
|
||||
defer loop_scope.unstack();
|
||||
|
||||
var cond_scope = parent_gz.makeSubBlock(&loop_scope.base);
|
||||
|
|
@ -6707,14 +6730,13 @@ fn whileExpr(
|
|||
_ = try loop_scope.addNode(repeat_tag, node);
|
||||
|
||||
try loop_scope.setBlockBody(loop_block);
|
||||
loop_scope.break_block = loop_block.toOptional();
|
||||
loop_scope.continue_block = continue_block.toOptional();
|
||||
if (while_full.label_token) |label_token| {
|
||||
loop_scope.label = .{
|
||||
.token = label_token,
|
||||
.block_inst = loop_block,
|
||||
};
|
||||
loop_scope.label = .{ .token = label_token };
|
||||
}
|
||||
loop_scope.allow_unlabeled_control_flow = true;
|
||||
loop_scope.break_target = loop_block;
|
||||
loop_scope.continue_target = .{ .@"break" = continue_block };
|
||||
loop_scope.setBreakResultInfo(block_ri);
|
||||
|
||||
// done adding instructions to loop_scope, can now stack then_scope
|
||||
then_scope.instructions_top = then_scope.instructions.items.len;
|
||||
|
|
@ -6787,10 +6809,12 @@ fn whileExpr(
|
|||
break :s &else_scope.base;
|
||||
}
|
||||
};
|
||||
// Remove the continue block and break block so that `continue` and `break`
|
||||
// control flow apply to outer loops; not this one.
|
||||
loop_scope.continue_block = .none;
|
||||
loop_scope.break_block = .none;
|
||||
// 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;
|
||||
loop_scope.allow_unlabeled_control_flow = false;
|
||||
loop_scope.continue_target = undefined;
|
||||
loop_scope.break_target = undefined;
|
||||
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);
|
||||
|
|
@ -6979,14 +7003,12 @@ fn forExpr(
|
|||
const cond_block = try loop_scope.makeBlockInst(block_tag, node);
|
||||
try cond_scope.setBlockBody(cond_block);
|
||||
|
||||
loop_scope.break_block = loop_block.toOptional();
|
||||
loop_scope.continue_block = cond_block.toOptional();
|
||||
if (for_full.label_token) |label_token| {
|
||||
loop_scope.label = .{
|
||||
.token = label_token,
|
||||
.block_inst = loop_block,
|
||||
};
|
||||
loop_scope.label = .{ .token = label_token };
|
||||
}
|
||||
loop_scope.allow_unlabeled_control_flow = true;
|
||||
loop_scope.break_target = loop_block;
|
||||
loop_scope.continue_target = .{ .@"break" = cond_block };
|
||||
|
||||
const then_node = for_full.ast.then_expr;
|
||||
var then_scope = parent_gz.makeSubBlock(&cond_scope.base);
|
||||
|
|
@ -7077,10 +7099,12 @@ fn forExpr(
|
|||
|
||||
if (for_full.ast.else_expr.unwrap()) |else_node| {
|
||||
const sub_scope = &else_scope.base;
|
||||
// Remove the continue block and break block so that `continue` and `break`
|
||||
// control flow apply to outer loops; not this one.
|
||||
loop_scope.continue_block = .none;
|
||||
loop_scope.break_block = .none;
|
||||
// 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;
|
||||
loop_scope.allow_unlabeled_control_flow = false;
|
||||
loop_scope.continue_target = undefined;
|
||||
loop_scope.break_target = undefined;
|
||||
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);
|
||||
|
|
@ -7818,7 +7842,9 @@ fn switchExpr(
|
|||
const switch_block = try parent_gz.makeBlockInst(switch_tag, node);
|
||||
|
||||
if (switch_full.label_token) |label_token| {
|
||||
block_scope.continue_block = switch_block.toOptional();
|
||||
block_scope.label = .{ .token = label_token };
|
||||
block_scope.break_target = switch_block;
|
||||
block_scope.continue_target = .{ .switch_continue = switch_block };
|
||||
block_scope.continue_result_info = .{
|
||||
.rl = if (any_payload_is_ref)
|
||||
.{ .ref_coerced_ty = raw_operand_ty_ref }
|
||||
|
|
@ -7826,12 +7852,7 @@ fn switchExpr(
|
|||
.{ .coerced_ty = raw_operand_ty_ref },
|
||||
};
|
||||
|
||||
block_scope.label = .{
|
||||
.token = label_token,
|
||||
.block_inst = switch_block,
|
||||
};
|
||||
// `break` can target this via `label.block_inst`
|
||||
// `break_result_info` already set by `setBreakResultInfo`
|
||||
// `break_result_info` already set by `setBreakResultInfo` above.
|
||||
}
|
||||
|
||||
// We re-use this same scope for all cases, including the special prong, if any.
|
||||
|
|
@ -11916,8 +11937,8 @@ const GenZir = struct {
|
|||
/// whenever we know Sema will analyze the current block with `is_comptime`,
|
||||
/// for instance when we're within a `struct_decl` or a `block_comptime`.
|
||||
is_comptime: bool,
|
||||
/// Whether we're in an expression within a `@TypeOf` operand. In this case, closure of runtime
|
||||
/// variables is permitted where it is usually not.
|
||||
/// Whether we're in an expression within a `@TypeOf` operand. In this case,
|
||||
/// closure of runtime variables is permitted where it is usually not.
|
||||
is_typeof: bool = false,
|
||||
/// This is set to true for a `GenZir` of a `block_inline`, indicating that
|
||||
/// exits from this block should use `break_inline` rather than `break`.
|
||||
|
|
@ -11938,10 +11959,27 @@ const GenZir = struct {
|
|||
/// if use is strictly nested. This saves prior size of list for unstacking.
|
||||
instructions_top: usize,
|
||||
label: ?Label = null,
|
||||
break_block: Zir.Inst.OptionalIndex = .none,
|
||||
continue_block: Zir.Inst.OptionalIndex = .none,
|
||||
/// If `true`, unlabeled `break` and `continue` exprs can target this `GenZir`.
|
||||
allow_unlabeled_control_flow: bool = false,
|
||||
/// If `label` is `null` and `unlabeled_control_flow_target` is `false`,
|
||||
/// this is unused and may be `undefined`.
|
||||
/// Otherwise, this is the target for a `break` instruction when a `break`
|
||||
/// targets this `GenZir`.
|
||||
break_target: Zir.Inst.Index = undefined,
|
||||
/// If `label` is `null` and `unlabeled_control_flow_target` is `false`,
|
||||
/// this is unused and may be `undefined`.
|
||||
continue_target: union(enum) {
|
||||
/// A `continue` cannot target this `GenZir`; emit an error.
|
||||
none,
|
||||
/// Emit a `break` instruction targeting this block.
|
||||
@"break": Zir.Inst.Index,
|
||||
/// Emit a `switch_continue` instruction targeting this `switch_block`.
|
||||
switch_continue: Zir.Inst.Index,
|
||||
} = undefined,
|
||||
/// Only valid when setBreakResultInfo is called.
|
||||
break_result_info: AstGen.ResultInfo = undefined,
|
||||
/// If `continue_target` is *not* `switch_continue`, this is unused and may
|
||||
/// be `undefined`.
|
||||
continue_result_info: AstGen.ResultInfo = undefined,
|
||||
|
||||
suspend_node: Ast.Node.OptionalIndex = .none,
|
||||
|
|
@ -12008,7 +12046,6 @@ const GenZir = struct {
|
|||
|
||||
const Label = struct {
|
||||
token: Ast.TokenIndex,
|
||||
block_inst: Zir.Inst.Index,
|
||||
used: bool = false,
|
||||
used_for_continue: bool = false,
|
||||
};
|
||||
|
|
|
|||
10
test/cases/compile_errors/labeled_block_continue.zig
Normal file
10
test/cases/compile_errors/labeled_block_continue.zig
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export fn foo() void {
|
||||
const result: u32 = b: {
|
||||
continue :b 123;
|
||||
};
|
||||
_ = result;
|
||||
}
|
||||
|
||||
// error
|
||||
//
|
||||
// :3:9: error: continue cannot target labeled block
|
||||
Loading…
Add table
Add a link
Reference in a new issue