authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-05-05 02:06:53+02:00
committergravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-05-28 19:35:54+02:00
logf4f5d06b8019bf6b7b3c205c730d70f291a07fbf
tree82803798f889b482c807b9b200f6b71de0d894ae
parent73b760e03361a86c2121b0e8b6e62de98721ee0d

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.

2 files changed, 81 insertions(+), 27 deletions(-)

src/Sema.zig+37-23
...@@ -2418,6 +2418,32 @@ fn failWithModRemNegative(sema: *Sema, block: *Block, src: LazySrcLoc, lhs_ty: T...@@ -2418,6 +2418,32 @@ fn failWithModRemNegative(sema: *Sema, block: *Block, src: LazySrcLoc, lhs_ty: T
2418 });2418 });
2419}2419}
24202420
2421fn failWithInvalidSwitchTagCapture(sema: *Sema, block: *Block, tag_capture_src: LazySrcLoc, operand_ty: Type) CompileError {
2422 const pt = sema.pt;
2423 const zcu = pt.zcu;
2424
2425 if (operand_ty.zigTypeTag(zcu) == .@"union") {
2426 assert(operand_ty.containerLayout(zcu) == .@"packed");
2427 return sema.failWithOwnedErrorMsg(block, msg: {
2428 const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of packed union", .{});
2429 errdefer msg.destroy(sema.gpa);
2430 try sema.addDeclaredHereNote(msg, operand_ty);
2431 if (operand_ty.srcLocOrNull(zcu)) |ty_src| {
2432 try sema.errNote(ty_src, msg, "consider using a tagged union", .{});
2433 }
2434 break :msg msg;
2435 });
2436 }
2437 return sema.failWithOwnedErrorMsg(block, msg: {
2438 const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
2439 operand_ty.fmt(pt),
2440 });
2441 errdefer msg.destroy(sema.gpa);
2442 try sema.addDeclaredHereNote(msg, operand_ty);
2443 break :msg msg;
2444 });
2445}
2446
2421fn failWithExpectedOptionalType(sema: *Sema, block: *Block, src: LazySrcLoc, non_optional_ty: Type) CompileError {2447fn failWithExpectedOptionalType(sema: *Sema, block: *Block, src: LazySrcLoc, non_optional_ty: Type) CompileError {
2422 const pt = sema.pt;2448 const pt = sema.pt;
2423 const msg = msg: {2449 const msg = msg: {
...@@ -10216,7 +10242,7 @@ fn analyzeSwitchBlock(...@@ -10216,7 +10242,7 @@ fn analyzeSwitchBlock(
1021610242
10217 const case_vals = validated_switch.case_vals;10243 const case_vals = validated_switch.case_vals;
1021810244
10219 const body, const capture, const has_tag_capture = find_prong: {10245 const case_idx, const body, const capture, const has_tag_capture = find_prong: {
10220 var case_val_idx: usize = 0;10246 var case_val_idx: usize = 0;
10221 var case_it = zir_switch.iterateCases();10247 var case_it = zir_switch.iterateCases();
10222 var extra_index = zir_switch.end;10248 var extra_index = zir_switch.end;
...@@ -10239,12 +10265,12 @@ fn analyzeSwitchBlock(...@@ -10239,12 +10265,12 @@ fn analyzeSwitchBlock(
10239 }10265 }
10240 continue;10266 continue;
10241 }10267 }
10242 break :find_prong .{ prong_body, prong_info.capture, prong_info.has_tag_capture };10268 break :find_prong .{ case.index, prong_body, prong_info.capture, prong_info.has_tag_capture };
10243 }10269 }
10244 if (has_else) {10270 if (has_else) {
10245 // This *has* to be checked after iterating all regular cases because10271 // This *has* to be checked after iterating all regular cases because
10246 // we allow simple noreturn else prongs when switching on error sets!10272 // we allow simple noreturn else prongs when switching on error sets!
10247 break :find_prong .{ else_case.body, else_case.capture, else_case.has_tag_capture };10273 break :find_prong .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture };
10248 }10274 }
10249 unreachable; // malformed validated switch10275 unreachable; // malformed validated switch
10250 };10276 };
...@@ -10289,6 +10315,13 @@ fn analyzeSwitchBlock(...@@ -10289,6 +10315,13 @@ fn analyzeSwitchBlock(
1028910315
10290 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {10316 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
10291 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;10317 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
10318 if (!tagged_union_originally) {
10319 const tag_capture_src = block.src(.{ .switch_tag_capture = .{
10320 .switch_node_offset = src_node_offset,
10321 .case_idx = case_idx,
10322 } });
10323 return sema.failWithInvalidSwitchTagCapture(block, tag_capture_src, operand_ty);
10324 }
10292 sema.inst_map.putAssumeCapacity(tag_inst, .fromValue(item_opv));10325 sema.inst_map.putAssumeCapacity(tag_inst, .fromValue(item_opv));
10293 break :inst tag_inst;10326 break :inst tag_inst;
10294 } else undefined;10327 } else undefined;
...@@ -12157,26 +12190,7 @@ fn analyzeSwitchCaptures(...@@ -12157,26 +12190,7 @@ fn analyzeSwitchCaptures(
12157 .base_node_inst = capture_src.base_node_inst,12190 .base_node_inst = capture_src.base_node_inst,
12158 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },12191 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
12159 };12192 };
12160 if (operand_ty.zigTypeTag(zcu) == .@"union") {12193 return sema.failWithInvalidSwitchTagCapture(case_block, tag_capture_src, operand_ty);
12161 assert(operand_ty.containerLayout(zcu) == .@"packed");
12162 return sema.failWithOwnedErrorMsg(case_block, msg: {
12163 const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of packed union", .{});
12164 errdefer msg.destroy(sema.gpa);
12165 try sema.addDeclaredHereNote(msg, operand_ty);
12166 if (operand_ty.srcLocOrNull(zcu)) |ty_src| {
12167 try sema.errNote(ty_src, msg, "consider using a tagged union", .{});
12168 }
12169 break :msg msg;
12170 });
12171 }
12172 return sema.failWithOwnedErrorMsg(case_block, msg: {
12173 const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
12174 operand_ty.fmt(pt),
12175 });
12176 errdefer msg.destroy(sema.gpa);
12177 try sema.addDeclaredHereNote(msg, operand_ty);
12178 break :msg msg;
12179 });
12180 }12194 }
1218112195
12182 return .{ .payload_ref = payload_ref, .tag_ref = .none };12196 return .{ .payload_ref = payload_ref, .tag_ref = .none };
test/cases/compile_errors/switch_invalid_tag_capture.zig+44-4
...@@ -8,6 +8,12 @@ export fn entry1(p: P) void {...@@ -8,6 +8,12 @@ export fn entry1(p: P) void {
8 else => {},8 else => {},
9 }9 }
10}10}
11export fn entry2(p: P) void {
12 label: switch (p) {
13 .{ .a = 123 } => |_, tag| _ = tag,
14 else => continue :label .{ .a = 123 },
15 }
16}
1117
12const E = enum(u8) { a, b };18const E = enum(u8) { a, b };
13export fn entry3(e: E) void {19export fn entry3(e: E) void {
...@@ -16,23 +22,57 @@ export fn entry3(e: E) void {...@@ -16,23 +22,57 @@ export fn entry3(e: E) void {
16 else => {},22 else => {},
17 }23 }
18}24}
25export fn entry4(e: E) void {
26 label: switch (e) {
27 .a => |_, tag| _ = tag,
28 else => continue :label .a,
29 }
30}
1931
20const Error = error{ MyError, MyOtherError };32const Error = error{ MyError, MyOtherError };
21export fn entry2(ok: bool) void {33export fn entry5(ok: bool) void {
22 switch (foo(ok)) {34 switch (foo(ok)) {
23 error.MyError => |_, tag| _ = tag,35 error.MyError => |_, tag| _ = tag,
24 else => {},36 else => {},
25 }37 }
26}38}
39export fn entry6(ok: bool) void {
40 label: switch (foo(ok)) {
41 error.MyError => |_, tag| _ = tag,
42 else => continue :label error.MyError,
43 }
44}
27fn foo(ok: bool) Error {45fn foo(ok: bool) Error {
28 return if (ok) error.MyError else error.MyOtherError;46 return if (ok) error.MyError else error.MyOtherError;
29}47}
3048
49export fn entry7() void {
50 switch (@as(u0, 0)) {
51 0 => |_, tag| _ = tag,
52 }
53}
54export fn entry8() void {
55 label: switch (@as(u0, 0)) {
56 0 => |_, tag| {
57 _ = tag;
58 continue :label 0;
59 },
60 }
61}
62
31// error63// error
32//64//
33// :7:30: error: cannot capture tag of packed union65// :7:30: error: cannot capture tag of packed union
34// :1:18: note: union declared here66// :1:18: note: union declared here
35// :1:18: note: consider using a tagged union67// :1:18: note: consider using a tagged union
36// :15:19: error: cannot capture tag of non-union type 'tmp.E'68// :13:30: error: cannot capture tag of packed union
37// :12:11: note: enum declared here69// :1:18: note: union declared here
38// :23:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'70// :1:18: note: consider using a tagged union
71// :21:19: error: cannot capture tag of non-union type 'tmp.E'
72// :18:11: note: enum declared here
73// :27:19: error: cannot capture tag of non-union type 'tmp.E'
74// :18:11: note: enum declared here
75// :35:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'
76// :41:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'
77// :51:18: error: cannot capture tag of non-union type 'u0'
78// :56:18: error: cannot capture tag of non-union type 'u0'