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
24182418 });
24192419}
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
24212447fn failWithExpectedOptionalType(sema: *Sema, block: *Block, src: LazySrcLoc, non_optional_ty: Type) CompileError {
24222448 const pt = sema.pt;
24232449 const msg = msg: {
......@@ -10216,7 +10242,7 @@ fn analyzeSwitchBlock(
1021610242
1021710243 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: {
1022010246 var case_val_idx: usize = 0;
1022110247 var case_it = zir_switch.iterateCases();
1022210248 var extra_index = zir_switch.end;
......@@ -10239,12 +10265,12 @@ fn analyzeSwitchBlock(
1023910265 }
1024010266 continue;
1024110267 }
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 };
1024310269 }
1024410270 if (has_else) {
1024510271 // This *has* to be checked after iterating all regular cases because
1024610272 // 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 };
1024810274 }
1024910275 unreachable; // malformed validated switch
1025010276 };
......@@ -10289,6 +10315,13 @@ fn analyzeSwitchBlock(
1028910315
1029010316 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
1029110317 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 }
1029210325 sema.inst_map.putAssumeCapacity(tag_inst, .fromValue(item_opv));
1029310326 break :inst tag_inst;
1029410327 } else undefined;
......@@ -12157,26 +12190,7 @@ fn analyzeSwitchCaptures(
1215712190 .base_node_inst = capture_src.base_node_inst,
1215812191 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
1215912192 };
12160 if (operand_ty.zigTypeTag(zcu) == .@"union") {
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 });
12193 return sema.failWithInvalidSwitchTagCapture(case_block, tag_capture_src, operand_ty);
1218012194 }
1218112195
1218212196 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 {
88 else => {},
99 }
1010}
11export fn entry2(p: P) void {
12 label: switch (p) {
13 .{ .a = 123 } => |_, tag| _ = tag,
14 else => continue :label .{ .a = 123 },
15 }
16}
1117
1218const E = enum(u8) { a, b };
1319export fn entry3(e: E) void {
......@@ -16,23 +22,57 @@ export fn entry3(e: E) void {
1622 else => {},
1723 }
1824}
25export fn entry4(e: E) void {
26 label: switch (e) {
27 .a => |_, tag| _ = tag,
28 else => continue :label .a,
29 }
30}
1931
2032const Error = error{ MyError, MyOtherError };
21export fn entry2(ok: bool) void {
33export fn entry5(ok: bool) void {
2234 switch (foo(ok)) {
2335 error.MyError => |_, tag| _ = tag,
2436 else => {},
2537 }
2638}
39export fn entry6(ok: bool) void {
40 label: switch (foo(ok)) {
41 error.MyError => |_, tag| _ = tag,
42 else => continue :label error.MyError,
43 }
44}
2745fn foo(ok: bool) Error {
2846 return if (ok) error.MyError else error.MyOtherError;
2947}
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
3163// error
3264//
3365// :7:30: error: cannot capture tag of packed union
3466// :1:18: note: union declared here
3567// :1:18: note: consider using a tagged union
36// :15:19: error: cannot capture tag of non-union type 'tmp.E'
37// :12:11: note: enum declared here
38// :23:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'
68// :13:30: error: cannot capture tag of packed union
69// :1:18: note: union declared here
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'