commit f4f5d06b8019bf6b7b3c205c730d70f291a07fbf (tree)
parent 73b760e03361a86c2121b0e8b6e62de98721ee0d
Author: Justus Klausecker <justus@klausecker.de>
Date: Tue, 5 May 2026 02:06:53 +0200
Sema: fail on switch loop tag capture of OPV type
This:
```zig
label: switch (@as(u0, 0)) {
0 => |_, tag| {
_ = tag;
continue :label 0;
},
}
```
would previously compile even though the operand is not a tagged union.
It is now a compile error.
Diffstat:
2 files changed, 81 insertions(+), 27 deletions(-)
diff --git a/src/Sema.zig b/src/Sema.zig
@@ -2418,6 +2418,32 @@ fn failWithModRemNegative(sema: *Sema, block: *Block, src: LazySrcLoc, lhs_ty: T
});
}
+fn failWithInvalidSwitchTagCapture(sema: *Sema, block: *Block, tag_capture_src: LazySrcLoc, operand_ty: Type) CompileError {
+ const pt = sema.pt;
+ const zcu = pt.zcu;
+
+ if (operand_ty.zigTypeTag(zcu) == .@"union") {
+ assert(operand_ty.containerLayout(zcu) == .@"packed");
+ return sema.failWithOwnedErrorMsg(block, msg: {
+ const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of packed union", .{});
+ errdefer msg.destroy(sema.gpa);
+ try sema.addDeclaredHereNote(msg, operand_ty);
+ if (operand_ty.srcLocOrNull(zcu)) |ty_src| {
+ try sema.errNote(ty_src, msg, "consider using a tagged union", .{});
+ }
+ break :msg msg;
+ });
+ }
+ return sema.failWithOwnedErrorMsg(block, msg: {
+ const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
+ operand_ty.fmt(pt),
+ });
+ errdefer msg.destroy(sema.gpa);
+ try sema.addDeclaredHereNote(msg, operand_ty);
+ break :msg msg;
+ });
+}
+
fn failWithExpectedOptionalType(sema: *Sema, block: *Block, src: LazySrcLoc, non_optional_ty: Type) CompileError {
const pt = sema.pt;
const msg = msg: {
@@ -10216,7 +10242,7 @@ fn analyzeSwitchBlock(
const case_vals = validated_switch.case_vals;
- const body, const capture, const has_tag_capture = find_prong: {
+ const case_idx, const body, const capture, const has_tag_capture = find_prong: {
var case_val_idx: usize = 0;
var case_it = zir_switch.iterateCases();
var extra_index = zir_switch.end;
@@ -10239,12 +10265,12 @@ fn analyzeSwitchBlock(
}
continue;
}
- break :find_prong .{ prong_body, prong_info.capture, prong_info.has_tag_capture };
+ break :find_prong .{ case.index, prong_body, prong_info.capture, prong_info.has_tag_capture };
}
if (has_else) {
// This *has* to be checked after iterating all regular cases because
// we allow simple noreturn else prongs when switching on error sets!
- break :find_prong .{ else_case.body, else_case.capture, else_case.has_tag_capture };
+ break :find_prong .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture };
}
unreachable; // malformed validated switch
};
@@ -10289,6 +10315,13 @@ fn analyzeSwitchBlock(
const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
+ if (!tagged_union_originally) {
+ const tag_capture_src = block.src(.{ .switch_tag_capture = .{
+ .switch_node_offset = src_node_offset,
+ .case_idx = case_idx,
+ } });
+ return sema.failWithInvalidSwitchTagCapture(block, tag_capture_src, operand_ty);
+ }
sema.inst_map.putAssumeCapacity(tag_inst, .fromValue(item_opv));
break :inst tag_inst;
} else undefined;
@@ -12157,26 +12190,7 @@ fn analyzeSwitchCaptures(
.base_node_inst = capture_src.base_node_inst,
.offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
};
- if (operand_ty.zigTypeTag(zcu) == .@"union") {
- assert(operand_ty.containerLayout(zcu) == .@"packed");
- return sema.failWithOwnedErrorMsg(case_block, msg: {
- const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of packed union", .{});
- errdefer msg.destroy(sema.gpa);
- try sema.addDeclaredHereNote(msg, operand_ty);
- if (operand_ty.srcLocOrNull(zcu)) |ty_src| {
- try sema.errNote(ty_src, msg, "consider using a tagged union", .{});
- }
- break :msg msg;
- });
- }
- return sema.failWithOwnedErrorMsg(case_block, msg: {
- const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
- operand_ty.fmt(pt),
- });
- errdefer msg.destroy(sema.gpa);
- try sema.addDeclaredHereNote(msg, operand_ty);
- break :msg msg;
- });
+ return sema.failWithInvalidSwitchTagCapture(case_block, tag_capture_src, operand_ty);
}
return .{ .payload_ref = payload_ref, .tag_ref = .none };
diff --git a/test/cases/compile_errors/switch_invalid_tag_capture.zig b/test/cases/compile_errors/switch_invalid_tag_capture.zig
@@ -8,6 +8,12 @@ export fn entry1(p: P) void {
else => {},
}
}
+export fn entry2(p: P) void {
+ label: switch (p) {
+ .{ .a = 123 } => |_, tag| _ = tag,
+ else => continue :label .{ .a = 123 },
+ }
+}
const E = enum(u8) { a, b };
export fn entry3(e: E) void {
@@ -16,23 +22,57 @@ export fn entry3(e: E) void {
else => {},
}
}
+export fn entry4(e: E) void {
+ label: switch (e) {
+ .a => |_, tag| _ = tag,
+ else => continue :label .a,
+ }
+}
const Error = error{ MyError, MyOtherError };
-export fn entry2(ok: bool) void {
+export fn entry5(ok: bool) void {
switch (foo(ok)) {
error.MyError => |_, tag| _ = tag,
else => {},
}
}
+export fn entry6(ok: bool) void {
+ label: switch (foo(ok)) {
+ error.MyError => |_, tag| _ = tag,
+ else => continue :label error.MyError,
+ }
+}
fn foo(ok: bool) Error {
return if (ok) error.MyError else error.MyOtherError;
}
+export fn entry7() void {
+ switch (@as(u0, 0)) {
+ 0 => |_, tag| _ = tag,
+ }
+}
+export fn entry8() void {
+ label: switch (@as(u0, 0)) {
+ 0 => |_, tag| {
+ _ = tag;
+ continue :label 0;
+ },
+ }
+}
+
// error
//
// :7:30: error: cannot capture tag of packed union
// :1:18: note: union declared here
// :1:18: note: consider using a tagged union
-// :15:19: error: cannot capture tag of non-union type 'tmp.E'
-// :12:11: note: enum declared here
-// :23:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'
+// :13:30: error: cannot capture tag of packed union
+// :1:18: note: union declared here
+// :1:18: note: consider using a tagged union
+// :21:19: error: cannot capture tag of non-union type 'tmp.E'
+// :18:11: note: enum declared here
+// :27:19: error: cannot capture tag of non-union type 'tmp.E'
+// :18:11: note: enum declared here
+// :35:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'
+// :41:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'
+// :51:18: error: cannot capture tag of non-union type 'u0'
+// :56:18: error: cannot capture tag of non-union type 'u0'