authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-03-11 13:51:31+01:00
committergravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-03-11 21:04:04+01:00
log28886ca9ec3170c3d97308c6e504cdcc50654b65
tree87eb3c5fd4eac557d37f1a0cb1b0c69592be0671
parentbe9b42d7074077eadf18e8d934c6f22051397a72

Sema: implement switch for packed structs/unions

Since packed containers are now represented by `bitpack` and can't include pointers anymore this has become a very easy change to make. This commit largely just reuses the logic already in place for integers. Also fixes a small bug where captures-by-ref of errors wouldn't cause a compile error for regular switch statements. There was already an astgen error in place for error handling switch statements (`switch_block_err_union`) capturing their error by reference.

8 files changed, 847 insertions(+), 531 deletions(-)

src/Sema.zig+555-530
......@@ -10211,15 +10211,17 @@ fn analyzeSwitchBlock(
1021110211 const operand_ty = sema.typeOf(val);
1021210212 operand_ty.assertHasLayout(zcu);
1021310213 const maybe_operand_opv = try operand_ty.onePossibleValue(pt);
10214 const init_cond: Air.Inst.Ref, const item_ty: Type = switch (operand_ty.zigTypeTag(zcu)) {
10215 .@"union" => tag: {
10214 const init_cond: Air.Inst.Ref, const item_ty: Type = init: {
10215 if (operand_ty.zigTypeTag(zcu) == .@"union" and
10216 operand_ty.containerLayout(zcu) != .@"packed")
10217 {
1021610218 const tag_val = try sema.unionToTag(block, val);
10217 break :tag .{ tag_val, sema.typeOf(tag_val) };
10218 },
10219 else => .{
10219 break :init .{ tag_val, sema.typeOf(tag_val) };
10220 }
10221 break :init .{
1022010222 if (maybe_operand_opv) |operand_opv| .fromValue(operand_opv) else val,
1022110223 operand_ty,
10222 },
10224 };
1022310225 };
1022410226 item_ty.assertHasLayout(zcu);
1022510227
......@@ -10250,7 +10252,8 @@ fn analyzeSwitchBlock(
1025010252
1025110253 const raw_operand_ty = sema.typeOf(raw_operand);
1025210254
10253 const union_originally = operand_ty.zigTypeTag(zcu) == .@"union";
10255 const tagged_union_originally = operand_ty.zigTypeTag(zcu) == .@"union" and
10256 operand_ty.containerLayout(zcu) != .@"packed";
1025410257 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
1025510258
1025610259 if (item_ty.zigTypeTag(zcu) == .@"enum" and
......@@ -10305,7 +10308,7 @@ fn analyzeSwitchBlock(
1030510308 else
1030610309 .{ new_operand, .none };
1030710310
10308 const new_cond_ref = if (union_originally)
10311 const new_cond_ref = if (tagged_union_originally)
1030910312 try sema.unionToTag(child_block, new_val)
1031010313 else
1031110314 new_val;
......@@ -10327,9 +10330,26 @@ fn analyzeSwitchBlock(
1032710330 unreachable;
1032810331 }
1032910332
10330 if (try item_ty.onePossibleValue(pt)) |item_opv| {
10333 const switch_ref: Air.Inst.Ref, const item_has_opv = switch_ref: {
10334 const item_opv = try item_ty.onePossibleValue(pt) orelse {
10335 assert(maybe_operand_opv == null); // `operand_ty` can only be an OPV type if `item_ty` is one too!
10336 const air_ref = try sema.finishSwitchBr(
10337 block,
10338 child_block,
10339 operand,
10340 raw_operand_ty,
10341 operand_is_ref,
10342 merges,
10343 switch_inst,
10344 zir_switch,
10345 validated_switch,
10346 );
10347 break :switch_ref .{ air_ref, false };
10348 };
10349
1033110350 // We simplify conditions with OPV to either a `loop` or a `block` since
1033210351 // we cannot switch on a value which doesn't exist at runtime.
10352
1033310353 assert(operand == .loop); // `simple` should have already been comptime-resolved above!
1033410354
1033510355 var case_block = child_block.makeSubBlock();
......@@ -10374,7 +10394,7 @@ fn analyzeSwitchBlock(
1037410394 unreachable; // malformed validated switch
1037510395 };
1037610396
10377 const analyze_body = sema.wantSwitchProngBodyAnalysis(.fromValue(item_opv), operand_ty, union_originally, err_set, false);
10397 const analyze_body = sema.wantSwitchProngBodyAnalysis(.fromValue(item_opv), operand_ty, tagged_union_originally, err_set, false);
1037810398 if (!analyze_body) return .unreachable_value;
1037910399
1038010400 if (!(err_set and
......@@ -10384,47 +10404,46 @@ fn analyzeSwitchBlock(
1038410404 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
1038510405 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
1038610406 const payload_ref: Air.Inst.Ref = payload_ref: {
10387 const item_val: Value = switch (operand_ty.zigTypeTag(zcu)) {
10388 .@"union" => item_val: {
10389 if (maybe_operand_opv) |operand_opv| {
10390 break :item_val .fromInterned(zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val);
10391 }
10392 assert(union_originally); // operand type must be union, otherwise it would be an OPV type here
10393 assert(zir_switch.any_maybe_runtime_capture); // there's a payload capture
10394 const operand_val, const operand_ref = switch (operand) {
10395 .simple => unreachable,
10396 .loop => |l| load_operand: {
10397 const loaded = try sema.analyzeLoad(block, src, l.operand_alloc, src);
10398 if (l.operand_is_ref) {
10399 const by_val = try sema.analyzeLoad(block, src, loaded, src);
10400 break :load_operand .{ by_val, loaded };
10401 } else {
10402 break :load_operand .{ loaded, .none };
10403 }
10404 },
10405 };
10406 const prong_kind: SwitchProngKind = kind: {
10407 if (is_inline) break :kind .{ .inline_ref = .fromValue(item_opv) };
10408 if (is_special) break :kind .special;
10409 break :kind .{ .item_refs = &.{.fromValue(item_opv)} };
10410 };
10411 break :payload_ref try sema.analyzeSwitchPayloadCapture(
10412 &case_block,
10413 operand,
10414 operand_val,
10415 operand_ref,
10416 operand_ty,
10417 operand_src,
10418 block.src(.{ .switch_capture = .{
10419 .switch_node_offset = src_node_offset,
10420 .case_idx = index,
10421 } }),
10422 capture == .by_ref,
10423 prong_kind,
10424 validated_switch.else_err_ty,
10425 );
10426 },
10427 else => item_opv,
10407 const item_val: Value = item_val: {
10408 if (!tagged_union_originally) {
10409 break :item_val item_opv;
10410 }
10411 if (maybe_operand_opv) |operand_opv| {
10412 break :item_val .fromInterned(zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val);
10413 }
10414 assert(zir_switch.any_maybe_runtime_capture); // there's a payload capture
10415 const operand_val, const operand_ref = switch (operand) {
10416 .simple => unreachable,
10417 .loop => |l| load_operand: {
10418 const loaded = try sema.analyzeLoad(block, src, l.operand_alloc, src);
10419 if (l.operand_is_ref) {
10420 const by_val = try sema.analyzeLoad(block, src, loaded, src);
10421 break :load_operand .{ by_val, loaded };
10422 } else {
10423 break :load_operand .{ loaded, .none };
10424 }
10425 },
10426 };
10427 const prong_kind: SwitchProngKind = kind: {
10428 if (is_inline) break :kind .{ .inline_ref = .fromValue(item_opv) };
10429 if (is_special) break :kind .special;
10430 break :kind .{ .item_refs = &.{.fromValue(item_opv)} };
10431 };
10432 break :payload_ref try sema.analyzeSwitchPayloadCapture(
10433 &case_block,
10434 operand,
10435 operand_val,
10436 operand_ref,
10437 operand_ty,
10438 operand_src,
10439 block.src(.{ .switch_capture = .{
10440 .switch_node_offset = src_node_offset,
10441 .case_idx = index,
10442 } }),
10443 capture == .by_ref,
10444 prong_kind,
10445 validated_switch.else_err_ty,
10446 );
1042810447 };
1042910448 break :payload_ref switch (capture) {
1043010449 .by_val => .fromValue(item_val),
......@@ -10462,40 +10481,100 @@ fn analyzeSwitchBlock(
1046210481 .loop
1046310482 else
1046410483 .block;
10465 const air_loop_ref = try child_block.addInst(.{
10484 const air_ref = try child_block.addInst(.{
1046610485 .tag = air_tag,
1046710486 .data = .{ .ty_pl = .{
1046810487 .ty = .noreturn_type,
1046910488 .payload = payload_index,
1047010489 } },
1047110490 });
10472 try sema.fixupSwitchContinues(
10473 block,
10474 src,
10475 air_loop_ref,
10476 operand,
10477 operand_is_ref,
10478 item_ty,
10479 .opv,
10480 zir_switch.any_maybe_runtime_capture,
10481 merges,
10482 );
10483 return null;
10491 break :switch_ref .{ air_ref, true };
10492 };
10493
10494 const air_tag = sema.air_instructions.items(.tag)[@intFromEnum(switch_ref.toIndex().?)];
10495 switch (air_tag) {
10496 .loop_switch_br, .switch_br => assert(!item_has_opv),
10497 .loop, .block => assert(item_has_opv),
10498 else => unreachable,
10499 }
10500 switch (air_tag) {
10501 .loop_switch_br, .loop => assert(merges.extra_insts.items.len > 0),
10502 .switch_br, .block => assert(merges.extra_insts.items.len == 0),
10503 else => unreachable,
1048410504 }
1048510505
10486 assert(maybe_operand_opv == null); // `operand_ty` can only be an OPV type if `item_ty` is one too!
10506 // We're done with analyzing the switch statement! Now all we have to do is
10507 // replace the placeholder `br` insts inserted by `zirSwitchContinue`s with
10508 // their respective finalized inst pointing back at `switch_ref`.
10509
10510 for (merges.extra_insts.items, merges.extra_src_locs.items) |placeholder_inst, dispatch_src| {
10511 var replacement_block = block.makeSubBlock();
10512 defer replacement_block.instructions.deinit(gpa);
10513
10514 assert(sema.air_instructions.items(.tag)[@intFromEnum(placeholder_inst)] == .br);
10515 const new_operand_maybe_ref = sema.air_instructions.items(.data)[@intFromEnum(placeholder_inst)].br.operand;
10516
10517 if (zir_switch.any_maybe_runtime_capture and !item_has_opv) {
10518 _ = try replacement_block.addBinOp(.store, operand.loop.operand_alloc, new_operand_maybe_ref);
10519 }
10520
10521 const new_operand_val = if (operand_is_ref)
10522 try sema.analyzeLoad(&replacement_block, dispatch_src, new_operand_maybe_ref, dispatch_src)
10523 else
10524 new_operand_maybe_ref;
10525
10526 const new_cond = try sema.coerce(&replacement_block, item_ty, new_operand_val, dispatch_src);
10527
10528 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
10529 item_ty.zigTypeTag(zcu) == .@"enum" and !item_ty.isNonexhaustiveEnum(zcu) and
10530 !item_has_opv and !try sema.isComptimeKnown(new_cond))
10531 {
10532 const ok = try replacement_block.addUnOp(.is_named_enum_value, new_cond);
10533 try sema.addSafetyCheck(&replacement_block, src, ok, .corrupt_switch);
10534 }
10535
10536 if (item_has_opv) {
10537 _ = try replacement_block.addInst(.{
10538 .tag = .repeat,
10539 .data = .{ .repeat = .{
10540 .loop_inst = switch_ref.toIndex().?,
10541 } },
10542 });
10543 } else {
10544 _ = try replacement_block.addInst(.{
10545 .tag = .switch_dispatch,
10546 .data = .{ .br = .{
10547 .block_inst = switch_ref.toIndex().?,
10548 .operand = new_cond,
10549 } },
10550 });
10551 }
10552
10553 if (replacement_block.instructions.items.len == 1) {
10554 // Optimization: we don't need a block!
10555 sema.air_instructions.set(
10556 @intFromEnum(placeholder_inst),
10557 sema.air_instructions.get(@intFromEnum(replacement_block.instructions.items[0])),
10558 );
10559 continue;
10560 }
10561
10562 // Replace placeholder with a block.
10563 // No `br` is needed as the block is a switch dispatch so necessarily `noreturn`.
10564 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len +
10565 replacement_block.instructions.items.len);
10566 sema.air_instructions.set(@intFromEnum(placeholder_inst), .{
10567 .tag = .block,
10568 .data = .{ .ty_pl = .{
10569 .ty = .noreturn_type,
10570 .payload = sema.addExtraAssumeCapacity(Air.Block{
10571 .body_len = @intCast(replacement_block.instructions.items.len),
10572 }),
10573 } },
10574 });
10575 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(replacement_block.instructions.items));
10576 }
1048710577
10488 try sema.finishSwitchBr(
10489 block,
10490 child_block,
10491 operand,
10492 raw_operand_ty,
10493 operand_is_ref,
10494 merges,
10495 switch_inst,
10496 zir_switch,
10497 validated_switch,
10498 );
1049910578 return null;
1050010579}
1050110580
......@@ -10510,7 +10589,7 @@ fn finishSwitchBr(
1051010589 switch_inst: Zir.Inst.Index,
1051110590 zir_switch: *const Zir.UnwrappedSwitchBlock,
1051210591 validated_switch: *const ValidatedSwitchBlock,
10513) CompileError!void {
10592) CompileError!Air.Inst.Ref {
1051410593 const pt = sema.pt;
1051510594 const zcu = pt.zcu;
1051610595 const ip = &zcu.intern_pool;
......@@ -10544,13 +10623,15 @@ fn finishSwitchBr(
1054410623
1054510624 const else_is_named_only = has_else and has_under;
1054610625
10547 const item_ty = switch (operand_ty.zigTypeTag(zcu)) {
10548 .@"union" => operand_ty.unionTagType(zcu).?,
10549 else => operand_ty,
10550 };
10551 const union_originally = operand_ty.zigTypeTag(zcu) == .@"union";
10626 const tagged_union_originally = operand_ty.zigTypeTag(zcu) == .@"union" and
10627 operand_ty.containerLayout(zcu) != .@"packed";
1055210628 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
1055310629
10630 const item_ty = if (tagged_union_originally)
10631 operand_ty.unionTagType(zcu).?
10632 else
10633 operand_ty;
10634
1055410635 const estimated_cases_len: u32 = scalar_cases_len + multi_cases_len +
1055510636 @intFromBool(has_else or has_under);
1055610637
......@@ -10632,7 +10713,7 @@ fn finishSwitchBr(
1063210713 if (item_ref == .none) is_under_prong = true;
1063310714 if (item_info.bodyLen()) |body_len| extra_index += body_len;
1063410715
10635 const analyze_body = sema.wantSwitchProngBodyAnalysis(item_ref, operand_ty, union_originally, err_set, prong_info.is_comptime_unreach);
10716 const analyze_body = sema.wantSwitchProngBodyAnalysis(item_ref, operand_ty, tagged_union_originally, err_set, prong_info.is_comptime_unreach);
1063610717 if (analyze_body) any_analyze_body = true;
1063710718
1063810719 if (prong_info.is_inline) {
......@@ -10840,9 +10921,8 @@ fn finishSwitchBr(
1084010921 const else_prong_src = block.src(.{ .node_offset_switch_else_prong = src_node_offset });
1084110922 const error_names, const min_int = check_enumerable: {
1084210923 switch (item_ty.zigTypeTag(zcu)) {
10843 .@"union" => unreachable,
1084410924 .@"enum" => if (else_is_named_only or
10845 !item_ty.isNonexhaustiveEnum(zcu) or union_originally)
10925 !item_ty.isNonexhaustiveEnum(zcu) or tagged_union_originally)
1084610926 {
1084710927 try branch_hints.ensureUnusedCapacity(gpa, @intCast(validated_switch.seen_enum_fields.len));
1084810928 break :check_enumerable .{ undefined, undefined };
......@@ -10856,6 +10936,11 @@ fn finishSwitchBr(
1085610936 const min_int = try item_ty.minInt(pt, item_ty);
1085710937 break :check_enumerable .{ undefined, min_int };
1085810938 },
10939 .@"union", .@"struct" => {
10940 const backing_int_ty = item_ty.bitpackBackingInt(zcu);
10941 const min_backing_int = try backing_int_ty.minInt(pt, backing_int_ty);
10942 break :check_enumerable .{ undefined, min_backing_int };
10943 },
1085910944 .bool, .void => break :check_enumerable .{ undefined, undefined },
1086010945 else => {},
1086110946 }
......@@ -10871,7 +10956,7 @@ fn finishSwitchBr(
1087110956
1087210957 const item_ref: Air.Inst.Ref = .fromValue(item_val);
1087310958
10874 const analyze_body = sema.wantSwitchProngBodyAnalysis(item_ref, operand_ty, union_originally, err_set, false);
10959 const analyze_body = sema.wantSwitchProngBodyAnalysis(item_ref, operand_ty, tagged_union_originally, err_set, false);
1087510960
1087610961 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
1087710962 emit_bb = true;
......@@ -10918,13 +11003,11 @@ fn finishSwitchBr(
1091811003 if (zcu.backendSupportsFeature(.is_named_enum_value) and
1091911004 (has_else or has_under) and block.wantSafety() and
1092011005 item_ty.zigTypeTag(zcu) == .@"enum" and
10921 (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally))
11006 (!operand_ty.isNonexhaustiveEnum(zcu) or tagged_union_originally))
1092211007 {
1092311008 try sema.zirDbgStmt(&case_block, cond_dbg_node_index);
1092411009 const ok = try case_block.addUnOp(.is_named_enum_value, cond_ref);
10925 if (else_is_named_only) {} else {
10926 try sema.addSafetyCheck(&case_block, src, ok, .corrupt_switch);
10927 }
11010 try sema.addSafetyCheck(&case_block, src, ok, .corrupt_switch);
1092811011 }
1092911012
1093011013 if (else_is_named_only and !else_case.is_inline) {
......@@ -10991,13 +11074,15 @@ fn finishSwitchBr(
1099111074
1099211075 const analyze_catch_all_body = analyze_body: {
1099311076 if (has_under) {
10994 break :analyze_body true; // can't be a union or an error set, never inlined
11077 assert(!tagged_union_originally);
11078 assert(!err_set);
11079 break :analyze_body true; // can never be inline
1099511080 } else if (has_else) {
1099611081 if (else_case.is_inline) break :analyze_body false; // already handled above
1099711082 } else {
1099811083 break :analyze_body false; // we still may want a safety check!
1099911084 }
11000 if (union_originally) {
11085 if (tagged_union_originally) {
1100111086 const union_obj = zcu.typeToUnion(operand_ty).?;
1100211087 for (validated_switch.seen_enum_fields, 0..) |seen_field, field_i| {
1100311088 if (seen_field != null) continue;
......@@ -11072,126 +11157,14 @@ fn finishSwitchBr(
1107211157 .loop_switch_br
1107311158 else
1107411159 .switch_br;
11075 const air_switch_ref = try child_block.addInst(.{
11160 const air_ref = try child_block.addInst(.{
1107611161 .tag = air_tag,
1107711162 .data = .{ .pl_op = .{
1107811163 .operand = cond_ref,
1107911164 .payload = payload_index,
1108011165 } },
1108111166 });
11082 try sema.fixupSwitchContinues(
11083 block,
11084 src,
11085 air_switch_ref,
11086 operand,
11087 operand_is_ref,
11088 item_ty,
11089 .normal,
11090 zir_switch.any_maybe_runtime_capture,
11091 merges,
11092 );
11093}
11094
11095/// This is the counterpart to `zirSwitchContinue`; replaces placeholder `br` insts
11096/// with their respective finalized inst pointing back at `switch_ref`.
11097fn fixupSwitchContinues(
11098 sema: *Sema,
11099 block: *Block,
11100 switch_src: LazySrcLoc,
11101 switch_ref: Air.Inst.Ref,
11102 operand: SwitchOperand,
11103 operand_is_ref: bool,
11104 item_ty: Type,
11105 mode: enum { normal, opv },
11106 any_maybe_runtime_capture: bool,
11107 merges: *const Block.Merges,
11108) CompileError!void {
11109 const pt = sema.pt;
11110 const zcu = pt.zcu;
11111 const gpa = sema.gpa;
11112
11113 const air_tag = sema.air_instructions.items(.tag)[@intFromEnum(switch_ref.toIndex().?)];
11114 switch (air_tag) {
11115 .loop_switch_br, .switch_br => assert(mode == .normal),
11116 .loop, .block => assert(mode == .opv),
11117 else => unreachable,
11118 }
11119 switch (air_tag) {
11120 .loop_switch_br, .loop => assert(merges.extra_insts.items.len > 0),
11121 .switch_br, .block => assert(merges.extra_insts.items.len == 0),
11122 else => unreachable,
11123 }
11124
11125 for (merges.extra_insts.items, merges.extra_src_locs.items) |placeholder_inst, dispatch_src| {
11126 var replacement_block = block.makeSubBlock();
11127 defer replacement_block.instructions.deinit(gpa);
11128
11129 assert(sema.air_instructions.items(.tag)[@intFromEnum(placeholder_inst)] == .br);
11130 const new_operand_maybe_ref = sema.air_instructions.items(.data)[@intFromEnum(placeholder_inst)].br.operand;
11131
11132 if (any_maybe_runtime_capture and mode != .opv) {
11133 _ = try replacement_block.addBinOp(.store, operand.loop.operand_alloc, new_operand_maybe_ref);
11134 }
11135
11136 const new_operand_val = if (operand_is_ref)
11137 try sema.analyzeLoad(&replacement_block, dispatch_src, new_operand_maybe_ref, dispatch_src)
11138 else
11139 new_operand_maybe_ref;
11140
11141 const new_cond = try sema.coerce(&replacement_block, item_ty, new_operand_val, dispatch_src);
11142
11143 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
11144 item_ty.zigTypeTag(zcu) == .@"enum" and !item_ty.isNonexhaustiveEnum(zcu) and
11145 mode == .normal and !try sema.isComptimeKnown(new_cond))
11146 {
11147 const ok = try replacement_block.addUnOp(.is_named_enum_value, new_cond);
11148 try sema.addSafetyCheck(&replacement_block, switch_src, ok, .corrupt_switch);
11149 }
11150
11151 switch (mode) {
11152 .normal => {
11153 _ = try replacement_block.addInst(.{
11154 .tag = .switch_dispatch,
11155 .data = .{ .br = .{
11156 .block_inst = switch_ref.toIndex().?,
11157 .operand = new_cond,
11158 } },
11159 });
11160 },
11161 .opv => {
11162 _ = try replacement_block.addInst(.{
11163 .tag = .repeat,
11164 .data = .{ .repeat = .{
11165 .loop_inst = switch_ref.toIndex().?,
11166 } },
11167 });
11168 },
11169 }
11170
11171 if (replacement_block.instructions.items.len == 1) {
11172 // Optimization: we don't need a block!
11173 sema.air_instructions.set(
11174 @intFromEnum(placeholder_inst),
11175 sema.air_instructions.get(@intFromEnum(replacement_block.instructions.items[0])),
11176 );
11177 continue;
11178 }
11179
11180 // Replace placeholder with a block.
11181 // No `br` is needed as the block is a switch dispatch so necessarily `noreturn`.
11182 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len +
11183 replacement_block.instructions.items.len);
11184 sema.air_instructions.set(@intFromEnum(placeholder_inst), .{
11185 .tag = .block,
11186 .data = .{ .ty_pl = .{
11187 .ty = .noreturn_type,
11188 .payload = sema.addExtraAssumeCapacity(Air.Block{
11189 .body_len = @intCast(replacement_block.instructions.items.len),
11190 }),
11191 } },
11192 });
11193 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(replacement_block.instructions.items));
11194 }
11167 return air_ref;
1119511168}
1119611169
1119711170const ValidatedSwitchBlock = struct {
......@@ -11242,7 +11215,6 @@ const ValidatedSwitchBlock = struct {
1124211215 const zcu = pt.zcu;
1124311216 const ip = &zcu.intern_pool;
1124411217 switch (item_ty.zigTypeTag(zcu)) {
11245 .@"union" => unreachable,
1124611218 .@"enum" => {
1124711219 for (it.seen_enum_fields[it.next_idx..], it.next_idx..) |seen_field, field_i| {
1124811220 if (seen_field != null) continue;
......@@ -11262,26 +11234,35 @@ const ValidatedSwitchBlock = struct {
1126211234 }
1126311235 return null;
1126411236 },
11265 .int => {
11266 var cur = it.next_val orelse return null;
11237 .int, .@"union", .@"struct" => |type_tag| {
11238 var cur_val = it.next_val orelse return null;
11239 const int_ty = switch (type_tag) {
11240 .int => item_ty,
11241 .@"union", .@"struct" => item_ty.bitpackBackingInt(zcu),
11242 else => unreachable,
11243 };
1126711244 while (it.next_idx < it.seen_ranges.len and
11268 cur.eql(it.seen_ranges[it.next_idx].first, item_ty, zcu))
11245 cur_val.eql(it.seen_ranges[it.next_idx].first, int_ty, zcu))
1126911246 {
1127011247 defer it.next_idx += 1;
1127111248 const incr = try arith.incrementDefinedInt(
1127211249 sema,
11273 item_ty,
11250 int_ty,
1127411251 it.seen_ranges[it.next_idx].last,
1127511252 );
1127611253 if (incr.overflow) {
1127711254 it.next_val = null;
1127811255 return null;
1127911256 }
11280 cur = incr.val;
11257 cur_val = incr.val;
1128111258 }
11282 const incr = try arith.incrementDefinedInt(sema, item_ty, cur);
11259 const incr = try arith.incrementDefinedInt(sema, int_ty, cur_val);
1128311260 it.next_val = if (incr.overflow) null else incr.val;
11284 return cur;
11261 return switch (type_tag) {
11262 .int => cur_val,
11263 .@"union", .@"struct" => try pt.bitpackValue(item_ty, cur_val),
11264 else => unreachable,
11265 };
1128511266 },
1128611267 .bool => {
1128711268 if (!it.seen_true) {
......@@ -11366,17 +11347,43 @@ fn validateSwitchBlock(
1136611347 => break :item_ty operand_ty,
1136711348
1136811349 .@"union" => {
11369 const enum_ty = operand_ty.unionTagType(zcu) orelse {
11370 return sema.failWithOwnedErrorMsg(block, msg: {
11371 const msg = try sema.errMsg(operand_src, "switch on union with no attached enum", .{});
11372 errdefer msg.destroy(sema.gpa);
11373 if (operand_ty.srcLocOrNull(zcu)) |union_src| {
11374 try sema.errNote(union_src, msg, "consider 'union(enum)' here", .{});
11350 operand_ty.assertHasLayout(zcu);
11351 const union_obj = ip.loadUnionType(operand_ty.toIntern());
11352 switch (union_obj.tag_usage) {
11353 .tagged => {
11354 break :item_ty .fromInterned(union_obj.enum_tag_type);
11355 },
11356 .none => {
11357 if (union_obj.layout == .@"packed") {
11358 break :item_ty operand_ty;
1137511359 }
11376 break :msg msg;
11377 });
11378 };
11379 break :item_ty enum_ty;
11360 },
11361 .safety => {},
11362 }
11363 return sema.failWithOwnedErrorMsg(block, msg: {
11364 const msg = try sema.errMsg(operand_src, "switch on union with no attached enum", .{});
11365 errdefer msg.destroy(sema.gpa);
11366 if (operand_ty.srcLocOrNull(zcu)) |union_src| {
11367 try sema.errNote(union_src, msg, "consider 'union(enum)' here", .{});
11368 }
11369 break :msg msg;
11370 });
11371 },
11372
11373 .@"struct" => {
11374 operand_ty.assertHasLayout(zcu);
11375 const layout = operand_ty.containerLayout(zcu);
11376 if (layout == .@"packed") {
11377 break :item_ty operand_ty;
11378 }
11379 return sema.failWithOwnedErrorMsg(block, msg: {
11380 const msg = try sema.errMsg(operand_src, "switch on struct with {t} layout", .{layout});
11381 errdefer msg.destroy(sema.gpa);
11382 if (operand_ty.srcLocOrNull(zcu)) |struct_src| {
11383 try sema.errNote(struct_src, msg, "consider 'packed struct' here", .{});
11384 }
11385 break :msg msg;
11386 });
1138011387 },
1138111388
1138211389 .pointer => {
......@@ -11424,7 +11431,6 @@ fn validateSwitchBlock(
1142411431 const else_case = zir_switch.else_case orelse undefined;
1142511432
1142611433 switch (item_ty.zigTypeTag(zcu)) {
11427 .@"union" => unreachable,
1142811434 .@"enum" => {
1142911435 seen_enum_fields = try arena.alloc(?LazySrcLoc, item_ty.enumFieldCount(zcu));
1143011436 @memset(seen_enum_fields, null);
......@@ -11435,7 +11441,7 @@ fn validateSwitchBlock(
1143511441 .error_set => {
1143611442 try seen_errors.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
1143711443 },
11438 .int, .comptime_int => {
11444 .int, .comptime_int, .@"union", .@"struct" => {
1143911445 try range_set.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
1144011446 },
1144111447 .enum_literal, .@"fn", .pointer, .type => {
......@@ -11503,7 +11509,6 @@ fn validateSwitchBlock(
1150311509 }
1150411510
1150511511 switch (item_ty.zigTypeTag(zcu)) {
11506 .@"union" => unreachable,
1150711512 .int, .comptime_int => {},
1150811513 else => if (zir_switch.anyRanges()) {
1150911514 const range_src = block.src(.{ .node_offset_switch_range = src_node_offset });
......@@ -11528,7 +11533,6 @@ fn validateSwitchBlock(
1152811533
1152911534 // Validate for missing special prongs.
1153011535 switch (item_ty.zigTypeTag(zcu)) {
11531 .@"union" => unreachable,
1153211536 .@"enum" => {
1153311537 const all_tags_handled = for (seen_enum_fields) |seen_src| {
1153411538 if (seen_src == null) break false;
......@@ -11661,22 +11665,26 @@ fn validateSwitchBlock(
1166111665 },
1166211666 };
1166311667 },
11664 .int, .comptime_int => |type_tag| {
11668 .int, .comptime_int, .@"union", .@"struct" => |type_tag| {
1166511669 check_range: {
11666 if (type_tag == .int) {
11667 const min_int = try item_ty.minInt(pt, item_ty);
11668 const max_int = try item_ty.maxInt(pt, item_ty);
11669 if (try range_set.spans(arena, min_int, max_int, item_ty, zcu)) {
11670 if (has_else) {
11671 return sema.fail(
11672 block,
11673 else_prong_src,
11674 "unreachable else prong; all cases already handled",
11675 .{},
11676 );
11677 }
11678 break :check_range;
11670 const int_ty = switch (type_tag) {
11671 .comptime_int => break :check_range, // comptime_int has 'infinite' range
11672 .int => item_ty,
11673 .@"union", .@"struct" => item_ty.bitpackBackingInt(zcu),
11674 else => unreachable,
11675 };
11676 const min_int = try int_ty.minInt(pt, int_ty);
11677 const max_int = try int_ty.maxInt(pt, int_ty);
11678 if (try range_set.spans(arena, min_int, max_int, int_ty, zcu)) {
11679 if (has_else) {
11680 return sema.fail(
11681 block,
11682 else_prong_src,
11683 "unreachable else prong; all cases already handled",
11684 .{},
11685 );
1167911686 }
11687 break :check_range;
1168011688 }
1168111689 if (!has_else) {
1168211690 return sema.fail(
......@@ -11759,12 +11767,15 @@ fn resolveSwitchBlock(
1175911767 const switch_node_offset = zir_switch.switch_src_node_offset;
1176011768
1176111769 const operand_ty = sema.typeOf(operand.simple.by_val);
11762 const item_ty = switch (operand_ty.zigTypeTag(zcu)) {
11763 .@"union" => operand_ty.unionTagType(zcu).?,
11764 else => operand_ty,
11765 };
11766 const union_originally = operand_ty.zigTypeTag(zcu) == .@"union";
11767 const err_set = item_ty.zigTypeTag(zcu) == .error_set;
11770
11771 const tagged_union_originally = operand_ty.zigTypeTag(zcu) == .@"union" and
11772 operand_ty.containerLayout(zcu) != .@"packed";
11773 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
11774
11775 const item_ty = if (tagged_union_originally)
11776 operand_ty.unionTagType(zcu).?
11777 else
11778 operand_ty;
1176811779
1176911780 const cond_ref = operand.simple.cond;
1177011781
......@@ -11807,7 +11818,7 @@ fn resolveSwitchBlock(
1180711818 const item_val = sema.resolveValue(item_ref).?;
1180811819 if (cond_val.eql(item_val, item_ty, zcu)) {
1180911820 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, prong_body, cond_ref);
11810 if (union_originally and operand_ty.unionFieldType(item_val, zcu).?.isNoReturn(zcu)) {
11821 if (tagged_union_originally and operand_ty.unionFieldType(item_val, zcu).?.isNoReturn(zcu)) {
1181111822 // This prong should be unreachable!
1181211823 return .unreachable_value;
1181311824 }
......@@ -11908,7 +11919,7 @@ fn resolveSwitchBlock(
1190811919 else
1190911920 .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline };
1191011921 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_ref);
11911 if (union_originally) {
11922 if (tagged_union_originally) {
1191211923 for (validated_switch.seen_enum_fields, 0..) |maybe_seen, field_i| {
1191311924 if (maybe_seen != null) continue;
1191411925 if (!operand_ty.unionFieldTypeByIndex(field_i, zcu).isNoReturn(zcu)) break;
......@@ -12049,12 +12060,12 @@ fn wantSwitchProngBodyAnalysis(
1204912060 sema: *Sema,
1205012061 item_ref: Air.Inst.Ref,
1205112062 operand_ty: Type,
12052 union_originally: bool,
12063 tagged_union_originally: bool,
1205312064 err_set: bool,
1205412065 prong_is_comptime_unreach: bool,
1205512066) bool {
1205612067 const zcu = sema.pt.zcu;
12057 if (union_originally) {
12068 if (tagged_union_originally) {
1205812069 const item_val = sema.resolveValue(item_ref).?;
1205912070 const field_ty = operand_ty.unionFieldType(item_val, zcu).?;
1206012071 if (field_ty.isNoReturn(zcu)) return false;
......@@ -12106,8 +12117,10 @@ fn analyzeSwitchProng(
1210612117 // No need to load the operand for this prong!
1210712118 break :need_load false;
1210812119 }
12109 if (capture != .none and operand_ty.zigTypeTag(zcu) == .@"union") {
12110 // Non-OPV union payload captures are always runtime-known.
12120 if (capture != .none and operand_ty.zigTypeTag(zcu) == .@"union" and
12121 operand_ty.containerLayout(zcu) != .@"packed")
12122 {
12123 // Non-OPV tagged union payload captures are always runtime-known.
1211112124 break :need_load true;
1211212125 }
1211312126 if (kind == .inline_ref) {
......@@ -12202,6 +12215,9 @@ fn analyzeSwitchTagCapture(
1220212215 operand_ty.fmt(pt),
1220312216 });
1220412217 }
12218 if (operand_ty.containerLayout(zcu) == .@"packed") {
12219 return sema.fail(case_block, tag_capture_src, "cannot capture tag of packed union", .{});
12220 }
1220512221 switch (kind) {
1220612222 .has_ranges => unreachable,
1220712223 .inline_ref => |ref| return ref,
......@@ -12215,9 +12231,9 @@ fn analyzeSwitchPayloadCapture(
1221512231 sema: *Sema,
1221612232 case_block: *Block,
1221712233 operand: SwitchOperand,
12218 /// Always has to be not-`none` if this is a union payload capture.
12219 /// For non-union captures, this may be `none` if this is an inline capture
12220 /// or if `kind.item_refs.len == 1` and capture is by val.
12234 /// Always has to be not-`none` if this is a tagged union payload capture.
12235 /// For non-tagged-union captures, this may be `none` if this is an inline
12236 /// capture or if `kind.item_refs.len == 1` and capture is by val.
1222112237 operand_val: Air.Inst.Ref,
1222212238 /// May be `none` if `capture_by_ref` is `false` or if `operand_val` is also `none`.
1222312239 operand_ptr: Air.Inst.Ref,
......@@ -12234,9 +12250,22 @@ fn analyzeSwitchPayloadCapture(
1223412250
1223512251 const switch_node_offset = operand_src.offset.node_offset_switch_operand;
1223612252
12253 const tagged_union_originally = operand_ty.zigTypeTag(zcu) == .@"union" and
12254 operand_ty.containerLayout(zcu) != .@"packed";
12255 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
12256
12257 if (err_set and capture_by_ref) {
12258 return sema.fail(
12259 case_block,
12260 capture_src,
12261 "error set cannot be captured by reference",
12262 .{},
12263 );
12264 }
12265
1223712266 if (kind == .inline_ref) {
1223812267 const item_val = sema.resolveValue(kind.inline_ref).?;
12239 if (operand_ty.zigTypeTag(zcu) == .@"union") {
12268 if (tagged_union_originally) {
1224012269 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);
1224112270 const union_obj = zcu.typeToUnion(operand_ty).?;
1224212271 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
......@@ -12267,52 +12296,90 @@ fn analyzeSwitchPayloadCapture(
1226712296 }
1226812297
1226912298 if (kind == .special) {
12270 if (capture_by_ref) return operand_ptr;
12271 return switch (operand_ty.zigTypeTag(zcu)) {
12272 .error_set => e: {
12273 if (else_err_ty) |err_ty| {
12274 break :e sema.bitCast(case_block, err_ty, operand_val, operand_src, null);
12275 } else {
12276 try sema.analyzeUnreachable(case_block, operand_src, false);
12277 break :e .unreachable_value;
12278 }
12279 },
12280 else => operand_val,
12281 };
12299 if (err_set) {
12300 if (else_err_ty) |err_ty| {
12301 return sema.bitCast(case_block, err_ty, operand_val, operand_src, null);
12302 } else {
12303 try sema.analyzeUnreachable(case_block, operand_src, false);
12304 return .unreachable_value;
12305 }
12306 }
12307 if (capture_by_ref) {
12308 return operand_ptr;
12309 }
12310 return operand_val;
1228212311 }
1228312312
12284 switch (operand_ty.zigTypeTag(zcu)) {
12285 .@"union" => {
12286 const case_vals = kind.item_refs;
12313 if (tagged_union_originally) {
12314 const case_vals = kind.item_refs;
1228712315
12288 const union_obj = zcu.typeToUnion(operand_ty).?;
12289 const first_item_val = sema.resolveValue(case_vals[0]).?;
12316 const union_obj = zcu.typeToUnion(operand_ty).?;
12317 const first_item_val = sema.resolveValue(case_vals[0]).?;
1229012318
12291 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;
12292 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);
12319 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;
12320 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);
1229312321
12294 const field_indices = try sema.arena.alloc(u32, case_vals.len);
12295 for (case_vals, field_indices) |item, *field_idx| {
12296 const item_val = sema.resolveValue(item).?;
12297 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
12298 }
12322 const field_indices = try sema.arena.alloc(u32, case_vals.len);
12323 for (case_vals, field_indices) |item, *field_idx| {
12324 const item_val = sema.resolveValue(item).?;
12325 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
12326 }
12327
12328 // Fast path: if all the operands are the same type already, we don't need to hit
12329 // PTR! This will also allow us to emit simpler code.
12330 const same_types = for (field_indices[1..]) |field_idx| {
12331 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12332 if (!field_ty.eql(first_field_ty, zcu)) break false;
12333 } else true;
1229912334
12300 // Fast path: if all the operands are the same type already, we don't need to hit
12301 // PTR! This will also allow us to emit simpler code.
12302 const same_types = for (field_indices[1..]) |field_idx| {
12335 const capture_ty: Type = capture_ty: {
12336 if (same_types) break :capture_ty first_field_ty;
12337 // We need values to run PTR on, so make a bunch of undef constants.
12338 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
12339 for (dummy_captures, field_indices) |*dummy, field_idx| {
1230312340 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12304 if (!field_ty.eql(first_field_ty, zcu)) break false;
12305 } else true;
12341 dummy.* = try pt.undefRef(field_ty);
12342 }
1230612343
12307 const capture_ty: Type = capture_ty: {
12308 if (same_types) break :capture_ty first_field_ty;
12344 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
12345 for (case_srcs, 0..) |*case_src, item_i| {
12346 case_src.* = .{
12347 .base_node_inst = capture_src.base_node_inst,
12348 .offset = .{ .switch_case_item = .{
12349 .switch_node_offset = switch_node_offset,
12350 .case_idx = capture_src.offset.switch_capture.case_idx,
12351 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
12352 } },
12353 };
12354 }
12355
12356 break :capture_ty sema.resolvePeerTypes(
12357 case_block,
12358 capture_src,
12359 dummy_captures,
12360 .{ .override = case_srcs },
12361 ) catch |err| switch (err) {
12362 error.AnalysisFail => {
12363 const msg = sema.err orelse return error.AnalysisFail;
12364 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12365 return error.AnalysisFail;
12366 },
12367 else => |e| return e,
12368 };
12369 };
12370
12371 // By-reference captures have some further restrictions which make them easier to emit
12372 if (capture_by_ref) {
12373 const operand_ptr_ty = sema.typeOf(operand_ptr);
12374 const capture_ptr_ty = resolve: {
12375 // By-ref captures of hetereogeneous types are only allowed if all field
12376 // pointer types are peer resolvable to each other.
1230912377 // We need values to run PTR on, so make a bunch of undef constants.
1231012378 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
12311 for (dummy_captures, field_indices) |*dummy, field_idx| {
12312 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12313 dummy.* = try pt.undefRef(field_ty);
12379 for (field_indices, dummy_captures) |field_index, *dummy| {
12380 const field_ptr_ty = try operand_ptr_ty.fieldPtrType(field_index, pt);
12381 dummy.* = try pt.undefRef(field_ptr_ty);
1231412382 }
12315
1231612383 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
1231712384 for (case_srcs, 0..) |*case_src, item_i| {
1231812385 case_src.* = .{
......@@ -12325,7 +12392,7 @@ fn analyzeSwitchPayloadCapture(
1232512392 };
1232612393 }
1232712394
12328 break :capture_ty sema.resolvePeerTypes(
12395 break :resolve sema.resolvePeerTypes(
1232912396 case_block,
1233012397 capture_src,
1233112398 dummy_captures,
......@@ -12333,6 +12400,7 @@ fn analyzeSwitchPayloadCapture(
1233312400 ) catch |err| switch (err) {
1233412401 error.AnalysisFail => {
1233512402 const msg = sema.err orelse return error.AnalysisFail;
12403 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
1233612404 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
1233712405 return error.AnalysisFail;
1233812406 },
......@@ -12340,272 +12408,222 @@ fn analyzeSwitchPayloadCapture(
1234012408 };
1234112409 };
1234212410
12343 // By-reference captures have some further restrictions which make them easier to emit
12344 if (capture_by_ref) {
12345 const operand_ptr_ty = sema.typeOf(operand_ptr);
12346 const capture_ptr_ty = resolve: {
12347 // By-ref captures of hetereogeneous types are only allowed if all field
12348 // pointer types are peer resolvable to each other.
12349 // We need values to run PTR on, so make a bunch of undef constants.
12350 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
12351 for (field_indices, dummy_captures) |field_index, *dummy| {
12352 const field_ptr_ty = try operand_ptr_ty.fieldPtrType(field_index, pt);
12353 dummy.* = try pt.undefRef(field_ptr_ty);
12354 }
12355 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
12356 for (case_srcs, 0..) |*case_src, item_i| {
12357 case_src.* = .{
12358 .base_node_inst = capture_src.base_node_inst,
12359 .offset = .{ .switch_case_item = .{
12360 .switch_node_offset = switch_node_offset,
12361 .case_idx = capture_src.offset.switch_capture.case_idx,
12362 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
12363 } },
12364 };
12365 }
12366
12367 break :resolve sema.resolvePeerTypes(
12368 case_block,
12369 capture_src,
12370 dummy_captures,
12371 .{ .override = case_srcs },
12372 ) catch |err| switch (err) {
12373 error.AnalysisFail => {
12374 const msg = sema.err orelse return error.AnalysisFail;
12375 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
12376 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12377 return error.AnalysisFail;
12378 },
12379 else => |e| return e,
12380 };
12381 };
12382
12383 if (try sema.resolveDefinedValue(case_block, operand_src, operand_ptr)) |op_ptr_val| {
12384 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
12385 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
12386 return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
12387 }
12388
12389 try sema.requireRuntimeBlock(case_block, operand_src, null);
12390 return case_block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);
12411 if (try sema.resolveDefinedValue(case_block, operand_src, operand_ptr)) |op_ptr_val| {
12412 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
12413 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
12414 return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
1239112415 }
1239212416
12393 if (try capture_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
12417 try sema.requireRuntimeBlock(case_block, operand_src, null);
12418 return case_block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);
12419 }
1239412420
12395 if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |operand_val_val| {
12396 if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);
12397 const union_val = ip.indexToKey(operand_val_val.toIntern()).un;
12398 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
12399 const uncoerced: Air.Inst.Ref = .fromIntern(union_val.val);
12400 return sema.coerce(case_block, capture_ty, uncoerced, operand_src);
12401 }
12421 if (try capture_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
1240212422
12403 try sema.requireRuntimeBlock(case_block, operand_src, null);
12423 if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |operand_val_val| {
12424 if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);
12425 const union_val = ip.indexToKey(operand_val_val.toIntern()).un;
12426 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
12427 const uncoerced: Air.Inst.Ref = .fromIntern(union_val.val);
12428 return sema.coerce(case_block, capture_ty, uncoerced, operand_src);
12429 }
1240412430
12405 if (same_types) {
12406 return case_block.addStructFieldVal(operand_val, first_field_index, capture_ty);
12407 }
12431 try sema.requireRuntimeBlock(case_block, operand_src, null);
1240812432
12409 // We may have to emit a switch block which coerces the operand to the capture type.
12410 // If we can, try to avoid that using in-memory coercions.
12411 const first_non_imc = in_mem: {
12412 for (field_indices, 0..) |field_idx, i| {
12413 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12414 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12415 break :in_mem i;
12416 }
12433 if (same_types) {
12434 return case_block.addStructFieldVal(operand_val, first_field_index, capture_ty);
12435 }
12436
12437 // We may have to emit a switch block which coerces the operand to the capture type.
12438 // If we can, try to avoid that using in-memory coercions.
12439 const first_non_imc = in_mem: {
12440 for (field_indices, 0..) |field_idx, i| {
12441 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12442 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12443 break :in_mem i;
1241712444 }
12418 // All fields are in-memory coercible to the resolved type!
12419 // Just take the first field and bitcast the result.
12420 const uncoerced = try case_block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
12421 return case_block.addBitCast(capture_ty, uncoerced);
12422 };
12445 }
12446 // All fields are in-memory coercible to the resolved type!
12447 // Just take the first field and bitcast the result.
12448 const uncoerced = try case_block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
12449 return case_block.addBitCast(capture_ty, uncoerced);
12450 };
1242312451
12424 // By-val capture with heterogeneous types which are not all in-memory coercible to
12425 // the resolved capture type. We finally have to fall back to the ugly method.
12452 // By-val capture with heterogeneous types which are not all in-memory coercible to
12453 // the resolved capture type. We finally have to fall back to the ugly method.
1242612454
12427 // However, let's first track which operands are in-memory coercible. There may well
12428 // be several, and we can squash all of these cases into the same switch prong using
12429 // a simple bitcast. We'll make this the 'else' prong.
12455 // However, let's first track which operands are in-memory coercible. There may well
12456 // be several, and we can squash all of these cases into the same switch prong using
12457 // a simple bitcast. We'll make this the 'else' prong.
1243012458
12431 var in_mem_coercible: std.DynamicBitSet = try .initFull(sema.arena, field_indices.len);
12432 in_mem_coercible.unset(first_non_imc);
12433 {
12434 const next = first_non_imc + 1;
12435 for (field_indices[next..], next..) |field_idx, i| {
12436 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12437 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12438 in_mem_coercible.unset(i);
12439 }
12459 var in_mem_coercible: std.DynamicBitSet = try .initFull(sema.arena, field_indices.len);
12460 in_mem_coercible.unset(first_non_imc);
12461 {
12462 const next = first_non_imc + 1;
12463 for (field_indices[next..], next..) |field_idx, i| {
12464 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12465 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12466 in_mem_coercible.unset(i);
1244012467 }
1244112468 }
12469 }
1244212470
12443 const capture_block_inst = try case_block.addInstAsIndex(.{
12444 .tag = .block,
12445 .data = .{
12446 .ty_pl = .{
12447 .ty = .fromType(capture_ty),
12448 .payload = undefined, // updated below
12449 },
12471 const capture_block_inst = try case_block.addInstAsIndex(.{
12472 .tag = .block,
12473 .data = .{
12474 .ty_pl = .{
12475 .ty = .fromType(capture_ty),
12476 .payload = undefined, // updated below
1245012477 },
12451 });
12478 },
12479 });
1245212480
12453 const prong_count = field_indices.len - in_mem_coercible.count();
12481 const prong_count = field_indices.len - in_mem_coercible.count();
1245412482
12455 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
12456 var cases_extra = try std.array_list.Managed(u32).initCapacity(sema.gpa, estimated_extra);
12457 defer cases_extra.deinit();
12483 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
12484 var cases_extra = try std.array_list.Managed(u32).initCapacity(sema.gpa, estimated_extra);
12485 defer cases_extra.deinit();
1245812486
12459 {
12460 // All branch hints are `.none`, so just add zero elems.
12461 comptime assert(@intFromEnum(std.builtin.BranchHint.none) == 0);
12462 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
12463 try cases_extra.appendNTimes(0, need_elems);
12464 }
12487 {
12488 // All branch hints are `.none`, so just add zero elems.
12489 comptime assert(@intFromEnum(std.builtin.BranchHint.none) == 0);
12490 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
12491 try cases_extra.appendNTimes(0, need_elems);
12492 }
1246512493
12466 {
12467 // Non-bitcast cases
12468 var it = in_mem_coercible.iterator(.{ .kind = .unset });
12469 while (it.next()) |idx| {
12470 var coerce_block = case_block.makeSubBlock();
12471 defer coerce_block.instructions.deinit(sema.gpa);
12494 {
12495 // Non-bitcast cases
12496 var it = in_mem_coercible.iterator(.{ .kind = .unset });
12497 while (it.next()) |idx| {
12498 var coerce_block = case_block.makeSubBlock();
12499 defer coerce_block.instructions.deinit(sema.gpa);
1247212500
12473 const case_src: LazySrcLoc = .{
12474 .base_node_inst = capture_src.base_node_inst,
12475 .offset = .{ .switch_case_item = .{
12476 .switch_node_offset = switch_node_offset,
12477 .case_idx = capture_src.offset.switch_capture.case_idx,
12478 .item_idx = .{ .kind = .single, .value = @intCast(idx) },
12479 } },
12480 };
12501 const case_src: LazySrcLoc = .{
12502 .base_node_inst = capture_src.base_node_inst,
12503 .offset = .{ .switch_case_item = .{
12504 .switch_node_offset = switch_node_offset,
12505 .case_idx = capture_src.offset.switch_capture.case_idx,
12506 .item_idx = .{ .kind = .single, .value = @intCast(idx) },
12507 } },
12508 };
1248112509
12482 const field_idx = field_indices[idx];
12483 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12484 const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);
12485 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
12486 _ = try coerce_block.addBr(capture_block_inst, coerced);
12510 const field_idx = field_indices[idx];
12511 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12512 const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);
12513 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
12514 _ = try coerce_block.addBr(capture_block_inst, coerced);
1248712515
12488 try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12489 1 + // `item`, no ranges
12490 coerce_block.instructions.items.len);
12491 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12492 .items_len = 1,
12493 .ranges_len = 0,
12494 .body_len = @intCast(coerce_block.instructions.items.len),
12495 }));
12496 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item
12497 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
12498 }
12516 try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12517 1 + // `item`, no ranges
12518 coerce_block.instructions.items.len);
12519 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12520 .items_len = 1,
12521 .ranges_len = 0,
12522 .body_len = @intCast(coerce_block.instructions.items.len),
12523 }));
12524 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item
12525 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
1249912526 }
12500 const else_body_len = len: {
12501 // 'else' prong uses a bitcast
12502 var coerce_block = case_block.makeSubBlock();
12503 defer coerce_block.instructions.deinit(sema.gpa);
12527 }
12528 const else_body_len = len: {
12529 // 'else' prong uses a bitcast
12530 var coerce_block = case_block.makeSubBlock();
12531 defer coerce_block.instructions.deinit(sema.gpa);
1250412532
12505 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
12506 const first_imc_field_idx = field_indices[first_imc_item_idx];
12507 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
12508 const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);
12509 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
12510 _ = try coerce_block.addBr(capture_block_inst, coerced);
12533 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
12534 const first_imc_field_idx = field_indices[first_imc_item_idx];
12535 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
12536 const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);
12537 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
12538 _ = try coerce_block.addBr(capture_block_inst, coerced);
1251112539
12512 try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));
12513 break :len coerce_block.instructions.items.len;
12514 };
12540 try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));
12541 break :len coerce_block.instructions.items.len;
12542 };
1251512543
12516 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +
12517 cases_extra.items.len +
12518 @typeInfo(Air.Block).@"struct".fields.len +
12519 1);
12520
12521 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
12522 try sema.air_instructions.append(sema.gpa, .{
12523 .tag = .switch_br,
12524 .data = .{
12525 .pl_op = .{
12526 .operand = undefined, // set by switch below
12527 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
12528 .cases_len = @intCast(prong_count),
12529 .else_body_len = @intCast(else_body_len),
12530 }),
12531 },
12532 },
12533 });
12534 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
12535
12536 // Set up block body
12537 switch (operand) {
12538 .simple => |s| {
12539 const air_datas = sema.air_instructions.items(.data);
12540 air_datas[switch_br_inst].pl_op.operand = s.cond;
12541 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12542 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
12543 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12544 },
12545 .loop => {
12546 // The block must first extract the tag from the loaded union.
12547 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
12548 try sema.air_instructions.append(sema.gpa, .{
12549 .tag = .get_union_tag,
12550 .data = .{ .ty_op = .{
12551 .ty = .fromIntern(union_obj.enum_tag_type),
12552 .operand = operand_val,
12553 } },
12554 });
12555 const air_datas = sema.air_instructions.items(.data);
12556 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
12557 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12558 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 2 });
12559 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
12560 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12544 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +
12545 cases_extra.items.len +
12546 @typeInfo(Air.Block).@"struct".fields.len +
12547 1);
12548
12549 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
12550 try sema.air_instructions.append(sema.gpa, .{
12551 .tag = .switch_br,
12552 .data = .{
12553 .pl_op = .{
12554 .operand = undefined, // set by switch below
12555 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
12556 .cases_len = @intCast(prong_count),
12557 .else_body_len = @intCast(else_body_len),
12558 }),
1256112559 },
12562 }
12560 },
12561 });
12562 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
1256312563
12564 return capture_block_inst.toRef();
12565 },
12566 .error_set => {
12567 if (capture_by_ref) {
12568 return sema.fail(
12569 case_block,
12570 capture_src,
12571 "error set cannot be captured by reference",
12572 .{},
12573 );
12574 }
12564 // Set up block body
12565 switch (operand) {
12566 .simple => |s| {
12567 const air_datas = sema.air_instructions.items(.data);
12568 air_datas[switch_br_inst].pl_op.operand = s.cond;
12569 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12570 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
12571 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12572 },
12573 .loop => {
12574 // The block must first extract the tag from the loaded union.
12575 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
12576 try sema.air_instructions.append(sema.gpa, .{
12577 .tag = .get_union_tag,
12578 .data = .{ .ty_op = .{
12579 .ty = .fromIntern(union_obj.enum_tag_type),
12580 .operand = operand_val,
12581 } },
12582 });
12583 const air_datas = sema.air_instructions.items(.data);
12584 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
12585 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12586 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 2 });
12587 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
12588 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12589 },
12590 }
1257512591
12576 const case_vals = kind.item_refs;
12577 if (case_vals.len == 1) {
12578 const item_val = sema.resolveValue(case_vals[0]).?;
12579 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
12580 return sema.bitCast(case_block, item_ty, .fromValue(item_val), operand_src, null);
12581 }
12592 return capture_block_inst.toRef();
12593 }
1258212594
12583 var names: InferredErrorSet.NameMap = .{};
12584 try names.ensureUnusedCapacity(sema.arena, case_vals.len);
12585 for (case_vals) |err| {
12586 const err_val = sema.resolveValue(err).?;
12587 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});
12588 }
12589 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());
12590 return sema.bitCast(case_block, error_ty, operand_val, operand_src, null);
12591 },
12592 else => {
12593 // In this case the capture value is just the passed-through value of the
12594 // switch condition. It is comptime-known if there is only one item.
12595 if (capture_by_ref) {
12596 return operand_ptr;
12597 }
12598 switch (kind) {
12599 .inline_ref, .special => unreachable,
12600 .item_refs => |case_vals| {
12601 // If there's only a single item, the capture is comptime-known!
12602 if (case_vals.len == 1) return case_vals[0];
12603 },
12604 .has_ranges => {},
12605 }
12606 return operand_val;
12595 if (err_set) {
12596 const case_vals = kind.item_refs;
12597 if (case_vals.len == 1) {
12598 const item_val = sema.resolveValue(case_vals[0]).?;
12599 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
12600 return sema.bitCast(case_block, item_ty, .fromValue(item_val), operand_src, null);
12601 }
12602
12603 var names: InferredErrorSet.NameMap = .{};
12604 try names.ensureUnusedCapacity(sema.arena, case_vals.len);
12605 for (case_vals) |err| {
12606 const err_val = sema.resolveValue(err).?;
12607 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});
12608 }
12609 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());
12610 return sema.bitCast(case_block, error_ty, operand_val, operand_src, null);
12611 }
12612
12613 // In this case the capture value is just the passed-through value of the
12614 // switch condition. It is comptime-known if there is only one item.
12615 if (capture_by_ref) {
12616 return operand_ptr;
12617 }
12618 switch (kind) {
12619 .inline_ref, .special => unreachable,
12620 .item_refs => |case_vals| {
12621 // If there's only a single item, the capture is comptime-known!
12622 if (case_vals.len == 1) return case_vals[0];
1260712623 },
12624 .has_ranges => {},
1260812625 }
12626 return operand_val;
1260912627}
1261012628
1261112629const ResolvedSwitchItem = struct {
......@@ -12714,7 +12732,6 @@ fn validateSwitchItemOrRange(
1271412732 const zcu = pt.zcu;
1271512733 const ip = &zcu.intern_pool;
1271612734 const maybe_prev_src: ?LazySrcLoc = maybe_prev_src: switch (item_ty.zigTypeTag(zcu)) {
12717 .@"union" => unreachable,
1271812735 .@"enum" => {
1271912736 const int = ip.indexToKey(item_val.toIntern()).enum_tag.int;
1272012737 if (ip.loadEnumType(item_ty.toIntern()).tagValueIndex(ip, int)) |field_index| {
......@@ -12755,6 +12772,14 @@ fn validateSwitchItemOrRange(
1275512772 }, item_ty, zcu);
1275612773 }
1275712774 },
12775 .@"union", .@"struct" => {
12776 const backing_int_val = ip.indexToKey(item_val.toIntern()).bitpack.backing_int_val;
12777 break :maybe_prev_src range_set.addAssumeCapacity(.{
12778 .first = .fromInterned(backing_int_val),
12779 .last = .fromInterned(backing_int_val),
12780 .src = item_src,
12781 }, item_ty.bitpackBackingInt(zcu), zcu);
12782 },
1275812783 .enum_literal, .@"fn", .pointer, .type => {
1275912784 break :maybe_prev_src if (seen_sparse_values.fetchPutAssumeCapacity(item_val.toIntern(), item_src)) |prev|
1276012785 prev.value
src/codegen/llvm.zig+1-1
......@@ -6252,7 +6252,7 @@ pub const FuncGen = struct {
62526252 const cond_ty = self.typeOf(switch_br.operand);
62536253 switch (cond_ty.zigTypeTag(zcu)) {
62546254 .bool, .pointer => break :jmp_table null,
6255 .@"enum", .int, .error_set => {},
6255 .@"enum", .int, .error_set, .@"struct", .@"union" => {},
62566256 else => unreachable,
62576257 }
62586258
test/behavior/switch.zig+130
......@@ -1327,3 +1327,133 @@ test "single range switch prong capture" {
13271327 try S.doTheTest(2);
13281328 try comptime S.doTheTest(2);
13291329}
1330
1331test "switch on packed struct" {
1332 const P = packed struct {
1333 a: u1,
1334 b: u1,
1335
1336 fn doTheTest(p: @This()) !void {
1337 switch (p) {
1338 .{ .a = 0, .b = 1 } => {},
1339 else => return error.TestFailed,
1340 }
1341
1342 switch (p) {
1343 .{ .a = 0, .b = 1 } => {},
1344 .{ .a = 0, .b = 0 },
1345 .{ .a = 1, .b = 0 },
1346 .{ .a = 1, .b = 1 },
1347 => return error.TestFailed,
1348 }
1349
1350 switch (p) {
1351 inline else => |val| {
1352 if (val != @This(){ .a = 0, .b = 1 }) return error.TestFailed;
1353 },
1354 }
1355 }
1356 };
1357 try P.doTheTest(.{ .a = 0, .b = 1 });
1358 try comptime P.doTheTest(.{ .a = 0, .b = 1 });
1359}
1360
1361test "switch on packed union" {
1362 const P = packed union(u2) {
1363 a: u2,
1364 b: i2,
1365 c: packed struct(u2) { x: u1, y: i1 },
1366
1367 fn doTheTest(p: @This()) !void {
1368 switch (p) {
1369 .{ .a = 1 } => {},
1370 else => return error.TestFailed,
1371 }
1372
1373 switch (p) {
1374 .{ .a = 1 } => {},
1375 .{ .a = 0 },
1376 .{ .a = 2 },
1377 .{ .a = 3 },
1378 => return error.TestFailed,
1379 }
1380
1381 switch (p) {
1382 .{ .a = 1 } => {},
1383 .{ .a = 0 },
1384 .{ .b = -2 },
1385 .{ .b = -1 },
1386 => return error.TestFailed,
1387 }
1388
1389 switch (p) {
1390 .{ .c = .{ .x = 1, .y = 0 } } => {},
1391 .{ .b = 0 },
1392 .{ .a = 2 },
1393 .{ .c = .{ .x = 1, .y = -1 } },
1394 => return error.TestFailed,
1395 }
1396
1397 switch (p) {
1398 inline else => |val| {
1399 if (val != @This(){ .c = .{ .x = 1, .y = 0 } }) return error.TestFailed;
1400 },
1401 }
1402 }
1403 };
1404 try P.doTheTest(.{ .a = 1 });
1405 try comptime P.doTheTest(.{ .a = 1 });
1406}
1407
1408test "switch on nested packed containers" {
1409 const P = packed struct {
1410 iu: u17,
1411 is: i31,
1412 b: bool,
1413 e: enum(u5) { a = 5, b = 3, c = 12 },
1414 un: packed union {
1415 a: i9,
1416 b: u9,
1417 c: packed struct(u9) { a: i5, b: u4 },
1418 },
1419 p: packed struct(u9) { a: u3, b: u6 },
1420
1421 fn doTheTest(p: @This()) !void {
1422 switch (p) {
1423 .{
1424 .iu = 72,
1425 .is = 124,
1426 .b = false,
1427 .e = .c,
1428 .un = .{ .b = 13 },
1429 .p = .{ .a = 0, .b = 12 },
1430 } => return error.TestFailed,
1431 .{
1432 .iu = 129,
1433 .is = -162784612,
1434 .b = true,
1435 .e = .a,
1436 .un = .{ .c = .{ .a = -3, .b = 9 } },
1437 .p = .{ .a = 2, .b = 17 },
1438 } => {},
1439 else => return error.TestFailed,
1440 }
1441 }
1442 };
1443 try P.doTheTest(.{
1444 .iu = 129,
1445 .is = -162784612,
1446 .b = true,
1447 .e = .a,
1448 .un = .{ .c = .{ .a = -3, .b = 9 } },
1449 .p = .{ .a = 2, .b = 17 },
1450 });
1451 try comptime P.doTheTest(.{
1452 .iu = 129,
1453 .is = -162784612,
1454 .b = true,
1455 .e = .a,
1456 .un = .{ .c = .{ .a = -3, .b = 9 } },
1457 .p = .{ .a = 2, .b = 17 },
1458 });
1459}
test/behavior/switch_loop.zig+55
......@@ -509,3 +509,58 @@ test "switch loop for error handling" {
509509 try S.doTheTest();
510510 try comptime S.doTheTest();
511511}
512
513test "switch loop with packed structs" {
514 const P = packed struct {
515 a: u7,
516 b: u20,
517
518 fn doTheTest(p: @This()) !void {
519 const result = s: switch (p) {
520 .{ .a = 5, .b = 10 } => |x| x,
521 else => |x| continue :s .{ .a = x.a, .b = x.b + 1 },
522 };
523 try expect(result == @This(){ .a = 5, .b = 10 });
524 }
525 };
526 try P.doTheTest(.{ .a = 5, .b = 0 });
527 try comptime P.doTheTest(.{ .a = 5, .b = 0 });
528}
529
530test "switch loop with packed unions" {
531 const P = packed union {
532 a: u7,
533 b: i7,
534
535 fn doTheTest(p: @This()) !void {
536 const result = s: switch (p) {
537 .{ .a = 10 } => |x| x,
538 else => |x| continue :s .{ .b = @intCast(x.a + 1) },
539 };
540 try expect(result == @This(){ .b = 10 });
541 }
542 };
543 try P.doTheTest(.{ .a = 5 });
544 try comptime P.doTheTest(.{ .a = 5 });
545}
546
547test "switch loop with packed unions with OPV" {
548 const P = packed union {
549 a: u0,
550 b: i0,
551
552 fn doTheTest(p: @This()) !void {
553 var looped = false;
554 s: switch (p) {
555 .{ .b = 0 } => |x| {
556 comptime assert(x.a == 0);
557 if (looped) break :s;
558 looped = true;
559 continue :s .{ .a = 0 };
560 },
561 }
562 }
563 };
564 try P.doTheTest(.{ .a = 0 });
565 try comptime P.doTheTest(.{ .a = 0 });
566}
test/cases/compile_errors/switch_capture_error_by_ref.zig created+25
......@@ -0,0 +1,25 @@
1export fn entry1() void {
2 switch (@as(anyerror, error.MyError)) {
3 error.MyError, error.MyOtherError => |*err| _ = err,
4 else => {},
5 }
6}
7
8export fn entry2() void {
9 switch (@as(anyerror, error.MyError)) {
10 inline error.MyError, error.MyOtherError => |*err| _ = err,
11 else => {},
12 }
13}
14
15export fn entry3() void {
16 switch (@as(anyerror, error.MyError)) {
17 else => |*err| _ = err,
18 }
19}
20
21// error
22//
23// :3:47: error: error set cannot be captured by reference
24// :10:54: error: error set cannot be captured by reference
25// :17:18: error: error set cannot be captured by reference
test/cases/compile_errors/switch_capture_packed_union_tag.zig created+15
......@@ -0,0 +1,15 @@
1const P = packed union(u8) {
2 a: u8,
3 b: i8,
4};
5
6export fn foo(p: P) void {
7 switch (p) {
8 .{ .a = 123 } => |_, tag| _ = tag,
9 else => {},
10 }
11}
12
13// error
14//
15// :8:30: error: cannot capture tag of packed union
test/cases/compile_errors/switch_on_non_packed_struct.zig created+25
......@@ -0,0 +1,25 @@
1const Auto = struct {
2 a: u8,
3};
4export fn entry1(a: u8) void {
5 const s: Auto = .{ .a = a };
6 switch (s) {
7 else => {},
8 }
9}
10
11const Extern = extern struct {
12 a: u8,
13};
14export fn entry2(s: Extern) void {
15 switch (s) {
16 else => {},
17 }
18}
19
20// error
21//
22// :6:13: error: switch on struct with auto layout
23// :1:14: note: consider 'packed struct' here
24// :15:13: error: switch on struct with extern layout
25// :11:23: note: consider 'packed struct' here
test/cases/compile_errors/switch_packed_exhaustion.zig created+41
......@@ -0,0 +1,41 @@
1const S = packed struct(u2) {
2 a: u2,
3};
4export fn entry1(x: u8) void {
5 const s: S = .{ .a = @intCast(x) };
6 switch (s) {
7 .{ .a = 0b00 }, .{ .a = 0b01 }, .{ .a = 0b10 }, .{ .a = 0b11 } => {},
8 else => {},
9 }
10}
11export fn entry2(x: u8) void {
12 const s: S = .{ .a = @intCast(x) };
13 switch (s) {
14 .{ .a = 0b00 }, .{ .a = 0b01 }, .{ .a = 0b11 } => {},
15 }
16}
17
18const U = packed union(u2) {
19 a: u2,
20 b: i2,
21};
22export fn entry3(x: u8) void {
23 const u: U = .{ .a = @intCast(x) };
24 switch (u) {
25 .{ .a = 0b00 }, .{ .a = 0b01 }, .{ .a = 0b10 }, .{ .a = 0b11 } => {},
26 else => {},
27 }
28}
29export fn entry4(x: u8) void {
30 const u: U = .{ .a = @intCast(x) };
31 switch (u) {
32 .{ .a = 0b00 }, .{ .a = 0b01 }, .{ .a = 0b11 } => {},
33 }
34}
35
36// error
37//
38// :8:14: error: unreachable else prong; all cases already handled
39// :13:5: error: switch must handle all possibilities
40// :26:14: error: unreachable else prong; all cases already handled
41// :31:5: error: switch must handle all possibilities