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(...@@ -10211,15 +10211,17 @@ fn analyzeSwitchBlock(
10211 const operand_ty = sema.typeOf(val);10211 const operand_ty = sema.typeOf(val);
10212 operand_ty.assertHasLayout(zcu);10212 operand_ty.assertHasLayout(zcu);
10213 const maybe_operand_opv = try operand_ty.onePossibleValue(pt);10213 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)) {10214 const init_cond: Air.Inst.Ref, const item_ty: Type = init: {
10215 .@"union" => tag: {10215 if (operand_ty.zigTypeTag(zcu) == .@"union" and
10216 operand_ty.containerLayout(zcu) != .@"packed")
10217 {
10216 const tag_val = try sema.unionToTag(block, val);10218 const tag_val = try sema.unionToTag(block, val);
10217 break :tag .{ tag_val, sema.typeOf(tag_val) };10219 break :init .{ tag_val, sema.typeOf(tag_val) };
10218 },10220 }
10219 else => .{10221 break :init .{
10220 if (maybe_operand_opv) |operand_opv| .fromValue(operand_opv) else val,10222 if (maybe_operand_opv) |operand_opv| .fromValue(operand_opv) else val,
10221 operand_ty,10223 operand_ty,
10222 },10224 };
10223 };10225 };
10224 item_ty.assertHasLayout(zcu);10226 item_ty.assertHasLayout(zcu);
1022510227
...@@ -10250,7 +10252,8 @@ fn analyzeSwitchBlock(...@@ -10250,7 +10252,8 @@ fn analyzeSwitchBlock(
1025010252
10251 const raw_operand_ty = sema.typeOf(raw_operand);10253 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";
10254 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;10257 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
1025510258
10256 if (item_ty.zigTypeTag(zcu) == .@"enum" and10259 if (item_ty.zigTypeTag(zcu) == .@"enum" and
...@@ -10305,7 +10308,7 @@ fn analyzeSwitchBlock(...@@ -10305,7 +10308,7 @@ fn analyzeSwitchBlock(
10305 else10308 else
10306 .{ new_operand, .none };10309 .{ new_operand, .none };
1030710310
10308 const new_cond_ref = if (union_originally)10311 const new_cond_ref = if (tagged_union_originally)
10309 try sema.unionToTag(child_block, new_val)10312 try sema.unionToTag(child_block, new_val)
10310 else10313 else
10311 new_val;10314 new_val;
...@@ -10327,9 +10330,26 @@ fn analyzeSwitchBlock(...@@ -10327,9 +10330,26 @@ fn analyzeSwitchBlock(
10327 unreachable;10330 unreachable;
10328 }10331 }
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
10331 // We simplify conditions with OPV to either a `loop` or a `block` since10350 // We simplify conditions with OPV to either a `loop` or a `block` since
10332 // we cannot switch on a value which doesn't exist at runtime.10351 // we cannot switch on a value which doesn't exist at runtime.
10352
10333 assert(operand == .loop); // `simple` should have already been comptime-resolved above!10353 assert(operand == .loop); // `simple` should have already been comptime-resolved above!
1033410354
10335 var case_block = child_block.makeSubBlock();10355 var case_block = child_block.makeSubBlock();
...@@ -10374,7 +10394,7 @@ fn analyzeSwitchBlock(...@@ -10374,7 +10394,7 @@ fn analyzeSwitchBlock(
10374 unreachable; // malformed validated switch10394 unreachable; // malformed validated switch
10375 };10395 };
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);
10378 if (!analyze_body) return .unreachable_value;10398 if (!analyze_body) return .unreachable_value;
1037910399
10380 if (!(err_set and10400 if (!(err_set and
...@@ -10384,47 +10404,46 @@ fn analyzeSwitchBlock(...@@ -10384,47 +10404,46 @@ fn analyzeSwitchBlock(
10384 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {10404 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
10385 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;10405 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
10386 const payload_ref: Air.Inst.Ref = payload_ref: {10406 const payload_ref: Air.Inst.Ref = payload_ref: {
10387 const item_val: Value = switch (operand_ty.zigTypeTag(zcu)) {10407 const item_val: Value = item_val: {
10388 .@"union" => item_val: {10408 if (!tagged_union_originally) {
10389 if (maybe_operand_opv) |operand_opv| {10409 break :item_val item_opv;
10390 break :item_val .fromInterned(zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val);10410 }
10391 }10411 if (maybe_operand_opv) |operand_opv| {
10392 assert(union_originally); // operand type must be union, otherwise it would be an OPV type here10412 break :item_val .fromInterned(zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val);
10393 assert(zir_switch.any_maybe_runtime_capture); // there's a payload capture10413 }
10394 const operand_val, const operand_ref = switch (operand) {10414 assert(zir_switch.any_maybe_runtime_capture); // there's a payload capture
10395 .simple => unreachable,10415 const operand_val, const operand_ref = switch (operand) {
10396 .loop => |l| load_operand: {10416 .simple => unreachable,
10397 const loaded = try sema.analyzeLoad(block, src, l.operand_alloc, src);10417 .loop => |l| load_operand: {
10398 if (l.operand_is_ref) {10418 const loaded = try sema.analyzeLoad(block, src, l.operand_alloc, src);
10399 const by_val = try sema.analyzeLoad(block, src, loaded, src);10419 if (l.operand_is_ref) {
10400 break :load_operand .{ by_val, loaded };10420 const by_val = try sema.analyzeLoad(block, src, loaded, src);
10401 } else {10421 break :load_operand .{ by_val, loaded };
10402 break :load_operand .{ loaded, .none };10422 } else {
10403 }10423 break :load_operand .{ loaded, .none };
10404 },10424 }
10405 };10425 },
10406 const prong_kind: SwitchProngKind = kind: {10426 };
10407 if (is_inline) break :kind .{ .inline_ref = .fromValue(item_opv) };10427 const prong_kind: SwitchProngKind = kind: {
10408 if (is_special) break :kind .special;10428 if (is_inline) break :kind .{ .inline_ref = .fromValue(item_opv) };
10409 break :kind .{ .item_refs = &.{.fromValue(item_opv)} };10429 if (is_special) break :kind .special;
10410 };10430 break :kind .{ .item_refs = &.{.fromValue(item_opv)} };
10411 break :payload_ref try sema.analyzeSwitchPayloadCapture(10431 };
10412 &case_block,10432 break :payload_ref try sema.analyzeSwitchPayloadCapture(
10413 operand,10433 &case_block,
10414 operand_val,10434 operand,
10415 operand_ref,10435 operand_val,
10416 operand_ty,10436 operand_ref,
10417 operand_src,10437 operand_ty,
10418 block.src(.{ .switch_capture = .{10438 operand_src,
10419 .switch_node_offset = src_node_offset,10439 block.src(.{ .switch_capture = .{
10420 .case_idx = index,10440 .switch_node_offset = src_node_offset,
10421 } }),10441 .case_idx = index,
10422 capture == .by_ref,10442 } }),
10423 prong_kind,10443 capture == .by_ref,
10424 validated_switch.else_err_ty,10444 prong_kind,
10425 );10445 validated_switch.else_err_ty,
10426 },10446 );
10427 else => item_opv,
10428 };10447 };
10429 break :payload_ref switch (capture) {10448 break :payload_ref switch (capture) {
10430 .by_val => .fromValue(item_val),10449 .by_val => .fromValue(item_val),
...@@ -10462,40 +10481,100 @@ fn analyzeSwitchBlock(...@@ -10462,40 +10481,100 @@ fn analyzeSwitchBlock(
10462 .loop10481 .loop
10463 else10482 else
10464 .block;10483 .block;
10465 const air_loop_ref = try child_block.addInst(.{10484 const air_ref = try child_block.addInst(.{
10466 .tag = air_tag,10485 .tag = air_tag,
10467 .data = .{ .ty_pl = .{10486 .data = .{ .ty_pl = .{
10468 .ty = .noreturn_type,10487 .ty = .noreturn_type,
10469 .payload = payload_index,10488 .payload = payload_index,
10470 } },10489 } },
10471 });10490 });
10472 try sema.fixupSwitchContinues(10491 break :switch_ref .{ air_ref, true };
10473 block,10492 };
10474 src,10493
10475 air_loop_ref,10494 const air_tag = sema.air_instructions.items(.tag)[@intFromEnum(switch_ref.toIndex().?)];
10476 operand,10495 switch (air_tag) {
10477 operand_is_ref,10496 .loop_switch_br, .switch_br => assert(!item_has_opv),
10478 item_ty,10497 .loop, .block => assert(item_has_opv),
10479 .opv,10498 else => unreachable,
10480 zir_switch.any_maybe_runtime_capture,10499 }
10481 merges,10500 switch (air_tag) {
10482 );10501 .loop_switch_br, .loop => assert(merges.extra_insts.items.len > 0),
10483 return null;10502 .switch_br, .block => assert(merges.extra_insts.items.len == 0),
10503 else => unreachable,
10484 }10504 }
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 );
10499 return null;10578 return null;
10500}10579}
1050110580
...@@ -10510,7 +10589,7 @@ fn finishSwitchBr(...@@ -10510,7 +10589,7 @@ fn finishSwitchBr(
10510 switch_inst: Zir.Inst.Index,10589 switch_inst: Zir.Inst.Index,
10511 zir_switch: *const Zir.UnwrappedSwitchBlock,10590 zir_switch: *const Zir.UnwrappedSwitchBlock,
10512 validated_switch: *const ValidatedSwitchBlock,10591 validated_switch: *const ValidatedSwitchBlock,
10513) CompileError!void {10592) CompileError!Air.Inst.Ref {
10514 const pt = sema.pt;10593 const pt = sema.pt;
10515 const zcu = pt.zcu;10594 const zcu = pt.zcu;
10516 const ip = &zcu.intern_pool;10595 const ip = &zcu.intern_pool;
...@@ -10544,13 +10623,15 @@ fn finishSwitchBr(...@@ -10544,13 +10623,15 @@ fn finishSwitchBr(
1054410623
10545 const else_is_named_only = has_else and has_under;10624 const else_is_named_only = has_else and has_under;
1054610625
10547 const item_ty = switch (operand_ty.zigTypeTag(zcu)) {10626 const tagged_union_originally = operand_ty.zigTypeTag(zcu) == .@"union" and
10548 .@"union" => operand_ty.unionTagType(zcu).?,10627 operand_ty.containerLayout(zcu) != .@"packed";
10549 else => operand_ty,
10550 };
10551 const union_originally = operand_ty.zigTypeTag(zcu) == .@"union";
10552 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;10628 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
10554 const estimated_cases_len: u32 = scalar_cases_len + multi_cases_len +10635 const estimated_cases_len: u32 = scalar_cases_len + multi_cases_len +
10555 @intFromBool(has_else or has_under);10636 @intFromBool(has_else or has_under);
1055610637
...@@ -10632,7 +10713,7 @@ fn finishSwitchBr(...@@ -10632,7 +10713,7 @@ fn finishSwitchBr(
10632 if (item_ref == .none) is_under_prong = true;10713 if (item_ref == .none) is_under_prong = true;
10633 if (item_info.bodyLen()) |body_len| extra_index += body_len;10714 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);
10636 if (analyze_body) any_analyze_body = true;10717 if (analyze_body) any_analyze_body = true;
1063710718
10638 if (prong_info.is_inline) {10719 if (prong_info.is_inline) {
...@@ -10840,9 +10921,8 @@ fn finishSwitchBr(...@@ -10840,9 +10921,8 @@ fn finishSwitchBr(
10840 const else_prong_src = block.src(.{ .node_offset_switch_else_prong = src_node_offset });10921 const else_prong_src = block.src(.{ .node_offset_switch_else_prong = src_node_offset });
10841 const error_names, const min_int = check_enumerable: {10922 const error_names, const min_int = check_enumerable: {
10842 switch (item_ty.zigTypeTag(zcu)) {10923 switch (item_ty.zigTypeTag(zcu)) {
10843 .@"union" => unreachable,
10844 .@"enum" => if (else_is_named_only or10924 .@"enum" => if (else_is_named_only or
10845 !item_ty.isNonexhaustiveEnum(zcu) or union_originally)10925 !item_ty.isNonexhaustiveEnum(zcu) or tagged_union_originally)
10846 {10926 {
10847 try branch_hints.ensureUnusedCapacity(gpa, @intCast(validated_switch.seen_enum_fields.len));10927 try branch_hints.ensureUnusedCapacity(gpa, @intCast(validated_switch.seen_enum_fields.len));
10848 break :check_enumerable .{ undefined, undefined };10928 break :check_enumerable .{ undefined, undefined };
...@@ -10856,6 +10936,11 @@ fn finishSwitchBr(...@@ -10856,6 +10936,11 @@ fn finishSwitchBr(
10856 const min_int = try item_ty.minInt(pt, item_ty);10936 const min_int = try item_ty.minInt(pt, item_ty);
10857 break :check_enumerable .{ undefined, min_int };10937 break :check_enumerable .{ undefined, min_int };
10858 },10938 },
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 },
10859 .bool, .void => break :check_enumerable .{ undefined, undefined },10944 .bool, .void => break :check_enumerable .{ undefined, undefined },
10860 else => {},10945 else => {},
10861 }10946 }
...@@ -10871,7 +10956,7 @@ fn finishSwitchBr(...@@ -10871,7 +10956,7 @@ fn finishSwitchBr(
1087110956
10872 const item_ref: Air.Inst.Ref = .fromValue(item_val);10957 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
10876 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);10961 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
10877 emit_bb = true;10962 emit_bb = true;
...@@ -10918,13 +11003,11 @@ fn finishSwitchBr(...@@ -10918,13 +11003,11 @@ fn finishSwitchBr(
10918 if (zcu.backendSupportsFeature(.is_named_enum_value) and11003 if (zcu.backendSupportsFeature(.is_named_enum_value) and
10919 (has_else or has_under) and block.wantSafety() and11004 (has_else or has_under) and block.wantSafety() and
10920 item_ty.zigTypeTag(zcu) == .@"enum" and11005 item_ty.zigTypeTag(zcu) == .@"enum" and
10921 (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally))11006 (!operand_ty.isNonexhaustiveEnum(zcu) or tagged_union_originally))
10922 {11007 {
10923 try sema.zirDbgStmt(&case_block, cond_dbg_node_index);11008 try sema.zirDbgStmt(&case_block, cond_dbg_node_index);
10924 const ok = try case_block.addUnOp(.is_named_enum_value, cond_ref);11009 const ok = try case_block.addUnOp(.is_named_enum_value, cond_ref);
10925 if (else_is_named_only) {} else {11010 try sema.addSafetyCheck(&case_block, src, ok, .corrupt_switch);
10926 try sema.addSafetyCheck(&case_block, src, ok, .corrupt_switch);
10927 }
10928 }11011 }
1092911012
10930 if (else_is_named_only and !else_case.is_inline) {11013 if (else_is_named_only and !else_case.is_inline) {
...@@ -10991,13 +11074,15 @@ fn finishSwitchBr(...@@ -10991,13 +11074,15 @@ fn finishSwitchBr(
1099111074
10992 const analyze_catch_all_body = analyze_body: {11075 const analyze_catch_all_body = analyze_body: {
10993 if (has_under) {11076 if (has_under) {
10994 break :analyze_body true; // can't be a union or an error set, never inlined11077 assert(!tagged_union_originally);
11078 assert(!err_set);
11079 break :analyze_body true; // can never be inline
10995 } else if (has_else) {11080 } else if (has_else) {
10996 if (else_case.is_inline) break :analyze_body false; // already handled above11081 if (else_case.is_inline) break :analyze_body false; // already handled above
10997 } else {11082 } else {
10998 break :analyze_body false; // we still may want a safety check!11083 break :analyze_body false; // we still may want a safety check!
10999 }11084 }
11000 if (union_originally) {11085 if (tagged_union_originally) {
11001 const union_obj = zcu.typeToUnion(operand_ty).?;11086 const union_obj = zcu.typeToUnion(operand_ty).?;
11002 for (validated_switch.seen_enum_fields, 0..) |seen_field, field_i| {11087 for (validated_switch.seen_enum_fields, 0..) |seen_field, field_i| {
11003 if (seen_field != null) continue;11088 if (seen_field != null) continue;
...@@ -11072,126 +11157,14 @@ fn finishSwitchBr(...@@ -11072,126 +11157,14 @@ fn finishSwitchBr(
11072 .loop_switch_br11157 .loop_switch_br
11073 else11158 else
11074 .switch_br;11159 .switch_br;
11075 const air_switch_ref = try child_block.addInst(.{11160 const air_ref = try child_block.addInst(.{
11076 .tag = air_tag,11161 .tag = air_tag,
11077 .data = .{ .pl_op = .{11162 .data = .{ .pl_op = .{
11078 .operand = cond_ref,11163 .operand = cond_ref,
11079 .payload = payload_index,11164 .payload = payload_index,
11080 } },11165 } },
11081 });11166 });
11082 try sema.fixupSwitchContinues(11167 return air_ref;
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 }
11195}11168}
1119611169
11197const ValidatedSwitchBlock = struct {11170const ValidatedSwitchBlock = struct {
...@@ -11242,7 +11215,6 @@ const ValidatedSwitchBlock = struct {...@@ -11242,7 +11215,6 @@ const ValidatedSwitchBlock = struct {
11242 const zcu = pt.zcu;11215 const zcu = pt.zcu;
11243 const ip = &zcu.intern_pool;11216 const ip = &zcu.intern_pool;
11244 switch (item_ty.zigTypeTag(zcu)) {11217 switch (item_ty.zigTypeTag(zcu)) {
11245 .@"union" => unreachable,
11246 .@"enum" => {11218 .@"enum" => {
11247 for (it.seen_enum_fields[it.next_idx..], it.next_idx..) |seen_field, field_i| {11219 for (it.seen_enum_fields[it.next_idx..], it.next_idx..) |seen_field, field_i| {
11248 if (seen_field != null) continue;11220 if (seen_field != null) continue;
...@@ -11262,26 +11234,35 @@ const ValidatedSwitchBlock = struct {...@@ -11262,26 +11234,35 @@ const ValidatedSwitchBlock = struct {
11262 }11234 }
11263 return null;11235 return null;
11264 },11236 },
11265 .int => {11237 .int, .@"union", .@"struct" => |type_tag| {
11266 var cur = it.next_val orelse return null;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 };
11267 while (it.next_idx < it.seen_ranges.len and11244 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))
11269 {11246 {
11270 defer it.next_idx += 1;11247 defer it.next_idx += 1;
11271 const incr = try arith.incrementDefinedInt(11248 const incr = try arith.incrementDefinedInt(
11272 sema,11249 sema,
11273 item_ty,11250 int_ty,
11274 it.seen_ranges[it.next_idx].last,11251 it.seen_ranges[it.next_idx].last,
11275 );11252 );
11276 if (incr.overflow) {11253 if (incr.overflow) {
11277 it.next_val = null;11254 it.next_val = null;
11278 return null;11255 return null;
11279 }11256 }
11280 cur = incr.val;11257 cur_val = incr.val;
11281 }11258 }
11282 const incr = try arith.incrementDefinedInt(sema, item_ty, cur);11259 const incr = try arith.incrementDefinedInt(sema, int_ty, cur_val);
11283 it.next_val = if (incr.overflow) null else incr.val;11260 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 };
11285 },11266 },
11286 .bool => {11267 .bool => {
11287 if (!it.seen_true) {11268 if (!it.seen_true) {
...@@ -11366,17 +11347,43 @@ fn validateSwitchBlock(...@@ -11366,17 +11347,43 @@ fn validateSwitchBlock(
11366 => break :item_ty operand_ty,11347 => break :item_ty operand_ty,
1136711348
11368 .@"union" => {11349 .@"union" => {
11369 const enum_ty = operand_ty.unionTagType(zcu) orelse {11350 operand_ty.assertHasLayout(zcu);
11370 return sema.failWithOwnedErrorMsg(block, msg: {11351 const union_obj = ip.loadUnionType(operand_ty.toIntern());
11371 const msg = try sema.errMsg(operand_src, "switch on union with no attached enum", .{});11352 switch (union_obj.tag_usage) {
11372 errdefer msg.destroy(sema.gpa);11353 .tagged => {
11373 if (operand_ty.srcLocOrNull(zcu)) |union_src| {11354 break :item_ty .fromInterned(union_obj.enum_tag_type);
11374 try sema.errNote(union_src, msg, "consider 'union(enum)' here", .{});11355 },
11356 .none => {
11357 if (union_obj.layout == .@"packed") {
11358 break :item_ty operand_ty;
11375 }11359 }
11376 break :msg msg;11360 },
11377 });11361 .safety => {},
11378 };11362 }
11379 break :item_ty enum_ty;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 });
11380 },11387 },
1138111388
11382 .pointer => {11389 .pointer => {
...@@ -11424,7 +11431,6 @@ fn validateSwitchBlock(...@@ -11424,7 +11431,6 @@ fn validateSwitchBlock(
11424 const else_case = zir_switch.else_case orelse undefined;11431 const else_case = zir_switch.else_case orelse undefined;
1142511432
11426 switch (item_ty.zigTypeTag(zcu)) {11433 switch (item_ty.zigTypeTag(zcu)) {
11427 .@"union" => unreachable,
11428 .@"enum" => {11434 .@"enum" => {
11429 seen_enum_fields = try arena.alloc(?LazySrcLoc, item_ty.enumFieldCount(zcu));11435 seen_enum_fields = try arena.alloc(?LazySrcLoc, item_ty.enumFieldCount(zcu));
11430 @memset(seen_enum_fields, null);11436 @memset(seen_enum_fields, null);
...@@ -11435,7 +11441,7 @@ fn validateSwitchBlock(...@@ -11435,7 +11441,7 @@ fn validateSwitchBlock(
11435 .error_set => {11441 .error_set => {
11436 try seen_errors.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());11442 try seen_errors.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
11437 },11443 },
11438 .int, .comptime_int => {11444 .int, .comptime_int, .@"union", .@"struct" => {
11439 try range_set.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());11445 try range_set.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
11440 },11446 },
11441 .enum_literal, .@"fn", .pointer, .type => {11447 .enum_literal, .@"fn", .pointer, .type => {
...@@ -11503,7 +11509,6 @@ fn validateSwitchBlock(...@@ -11503,7 +11509,6 @@ fn validateSwitchBlock(
11503 }11509 }
1150411510
11505 switch (item_ty.zigTypeTag(zcu)) {11511 switch (item_ty.zigTypeTag(zcu)) {
11506 .@"union" => unreachable,
11507 .int, .comptime_int => {},11512 .int, .comptime_int => {},
11508 else => if (zir_switch.anyRanges()) {11513 else => if (zir_switch.anyRanges()) {
11509 const range_src = block.src(.{ .node_offset_switch_range = src_node_offset });11514 const range_src = block.src(.{ .node_offset_switch_range = src_node_offset });
...@@ -11528,7 +11533,6 @@ fn validateSwitchBlock(...@@ -11528,7 +11533,6 @@ fn validateSwitchBlock(
1152811533
11529 // Validate for missing special prongs.11534 // Validate for missing special prongs.
11530 switch (item_ty.zigTypeTag(zcu)) {11535 switch (item_ty.zigTypeTag(zcu)) {
11531 .@"union" => unreachable,
11532 .@"enum" => {11536 .@"enum" => {
11533 const all_tags_handled = for (seen_enum_fields) |seen_src| {11537 const all_tags_handled = for (seen_enum_fields) |seen_src| {
11534 if (seen_src == null) break false;11538 if (seen_src == null) break false;
...@@ -11661,22 +11665,26 @@ fn validateSwitchBlock(...@@ -11661,22 +11665,26 @@ fn validateSwitchBlock(
11661 },11665 },
11662 };11666 };
11663 },11667 },
11664 .int, .comptime_int => |type_tag| {11668 .int, .comptime_int, .@"union", .@"struct" => |type_tag| {
11665 check_range: {11669 check_range: {
11666 if (type_tag == .int) {11670 const int_ty = switch (type_tag) {
11667 const min_int = try item_ty.minInt(pt, item_ty);11671 .comptime_int => break :check_range, // comptime_int has 'infinite' range
11668 const max_int = try item_ty.maxInt(pt, item_ty);11672 .int => item_ty,
11669 if (try range_set.spans(arena, min_int, max_int, item_ty, zcu)) {11673 .@"union", .@"struct" => item_ty.bitpackBackingInt(zcu),
11670 if (has_else) {11674 else => unreachable,
11671 return sema.fail(11675 };
11672 block,11676 const min_int = try int_ty.minInt(pt, int_ty);
11673 else_prong_src,11677 const max_int = try int_ty.maxInt(pt, int_ty);
11674 "unreachable else prong; all cases already handled",11678 if (try range_set.spans(arena, min_int, max_int, int_ty, zcu)) {
11675 .{},11679 if (has_else) {
11676 );11680 return sema.fail(
11677 }11681 block,
11678 break :check_range;11682 else_prong_src,
11683 "unreachable else prong; all cases already handled",
11684 .{},
11685 );
11679 }11686 }
11687 break :check_range;
11680 }11688 }
11681 if (!has_else) {11689 if (!has_else) {
11682 return sema.fail(11690 return sema.fail(
...@@ -11759,12 +11767,15 @@ fn resolveSwitchBlock(...@@ -11759,12 +11767,15 @@ fn resolveSwitchBlock(
11759 const switch_node_offset = zir_switch.switch_src_node_offset;11767 const switch_node_offset = zir_switch.switch_src_node_offset;
1176011768
11761 const operand_ty = sema.typeOf(operand.simple.by_val);11769 const operand_ty = sema.typeOf(operand.simple.by_val);
11762 const item_ty = switch (operand_ty.zigTypeTag(zcu)) {11770
11763 .@"union" => operand_ty.unionTagType(zcu).?,11771 const tagged_union_originally = operand_ty.zigTypeTag(zcu) == .@"union" and
11764 else => operand_ty,11772 operand_ty.containerLayout(zcu) != .@"packed";
11765 };11773 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
11766 const union_originally = operand_ty.zigTypeTag(zcu) == .@"union";11774
11767 const err_set = item_ty.zigTypeTag(zcu) == .error_set;11775 const item_ty = if (tagged_union_originally)
11776 operand_ty.unionTagType(zcu).?
11777 else
11778 operand_ty;
1176811779
11769 const cond_ref = operand.simple.cond;11780 const cond_ref = operand.simple.cond;
1177011781
...@@ -11807,7 +11818,7 @@ fn resolveSwitchBlock(...@@ -11807,7 +11818,7 @@ fn resolveSwitchBlock(
11807 const item_val = sema.resolveValue(item_ref).?;11818 const item_val = sema.resolveValue(item_ref).?;
11808 if (cond_val.eql(item_val, item_ty, zcu)) {11819 if (cond_val.eql(item_val, item_ty, zcu)) {
11809 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, prong_body, cond_ref);11820 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)) {
11811 // This prong should be unreachable!11822 // This prong should be unreachable!
11812 return .unreachable_value;11823 return .unreachable_value;
11813 }11824 }
...@@ -11908,7 +11919,7 @@ fn resolveSwitchBlock(...@@ -11908,7 +11919,7 @@ fn resolveSwitchBlock(
11908 else11919 else
11909 .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline };11920 .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline };
11910 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_ref);11921 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_ref);
11911 if (union_originally) {11922 if (tagged_union_originally) {
11912 for (validated_switch.seen_enum_fields, 0..) |maybe_seen, field_i| {11923 for (validated_switch.seen_enum_fields, 0..) |maybe_seen, field_i| {
11913 if (maybe_seen != null) continue;11924 if (maybe_seen != null) continue;
11914 if (!operand_ty.unionFieldTypeByIndex(field_i, zcu).isNoReturn(zcu)) break;11925 if (!operand_ty.unionFieldTypeByIndex(field_i, zcu).isNoReturn(zcu)) break;
...@@ -12049,12 +12060,12 @@ fn wantSwitchProngBodyAnalysis(...@@ -12049,12 +12060,12 @@ fn wantSwitchProngBodyAnalysis(
12049 sema: *Sema,12060 sema: *Sema,
12050 item_ref: Air.Inst.Ref,12061 item_ref: Air.Inst.Ref,
12051 operand_ty: Type,12062 operand_ty: Type,
12052 union_originally: bool,12063 tagged_union_originally: bool,
12053 err_set: bool,12064 err_set: bool,
12054 prong_is_comptime_unreach: bool,12065 prong_is_comptime_unreach: bool,
12055) bool {12066) bool {
12056 const zcu = sema.pt.zcu;12067 const zcu = sema.pt.zcu;
12057 if (union_originally) {12068 if (tagged_union_originally) {
12058 const item_val = sema.resolveValue(item_ref).?;12069 const item_val = sema.resolveValue(item_ref).?;
12059 const field_ty = operand_ty.unionFieldType(item_val, zcu).?;12070 const field_ty = operand_ty.unionFieldType(item_val, zcu).?;
12060 if (field_ty.isNoReturn(zcu)) return false;12071 if (field_ty.isNoReturn(zcu)) return false;
...@@ -12106,8 +12117,10 @@ fn analyzeSwitchProng(...@@ -12106,8 +12117,10 @@ fn analyzeSwitchProng(
12106 // No need to load the operand for this prong!12117 // No need to load the operand for this prong!
12107 break :need_load false;12118 break :need_load false;
12108 }12119 }
12109 if (capture != .none and operand_ty.zigTypeTag(zcu) == .@"union") {12120 if (capture != .none and operand_ty.zigTypeTag(zcu) == .@"union" and
12110 // Non-OPV union payload captures are always runtime-known.12121 operand_ty.containerLayout(zcu) != .@"packed")
12122 {
12123 // Non-OPV tagged union payload captures are always runtime-known.
12111 break :need_load true;12124 break :need_load true;
12112 }12125 }
12113 if (kind == .inline_ref) {12126 if (kind == .inline_ref) {
...@@ -12202,6 +12215,9 @@ fn analyzeSwitchTagCapture(...@@ -12202,6 +12215,9 @@ fn analyzeSwitchTagCapture(
12202 operand_ty.fmt(pt),12215 operand_ty.fmt(pt),
12203 });12216 });
12204 }12217 }
12218 if (operand_ty.containerLayout(zcu) == .@"packed") {
12219 return sema.fail(case_block, tag_capture_src, "cannot capture tag of packed union", .{});
12220 }
12205 switch (kind) {12221 switch (kind) {
12206 .has_ranges => unreachable,12222 .has_ranges => unreachable,
12207 .inline_ref => |ref| return ref,12223 .inline_ref => |ref| return ref,
...@@ -12215,9 +12231,9 @@ fn analyzeSwitchPayloadCapture(...@@ -12215,9 +12231,9 @@ fn analyzeSwitchPayloadCapture(
12215 sema: *Sema,12231 sema: *Sema,
12216 case_block: *Block,12232 case_block: *Block,
12217 operand: SwitchOperand,12233 operand: SwitchOperand,
12218 /// Always has to be not-`none` if this is a union payload capture.12234 /// Always has to be not-`none` if this is a tagged union payload capture.
12219 /// For non-union captures, this may be `none` if this is an inline capture12235 /// For non-tagged-union captures, this may be `none` if this is an inline
12220 /// or if `kind.item_refs.len == 1` and capture is by val.12236 /// capture or if `kind.item_refs.len == 1` and capture is by val.
12221 operand_val: Air.Inst.Ref,12237 operand_val: Air.Inst.Ref,
12222 /// May be `none` if `capture_by_ref` is `false` or if `operand_val` is also `none`.12238 /// May be `none` if `capture_by_ref` is `false` or if `operand_val` is also `none`.
12223 operand_ptr: Air.Inst.Ref,12239 operand_ptr: Air.Inst.Ref,
...@@ -12234,9 +12250,22 @@ fn analyzeSwitchPayloadCapture(...@@ -12234,9 +12250,22 @@ fn analyzeSwitchPayloadCapture(
1223412250
12235 const switch_node_offset = operand_src.offset.node_offset_switch_operand;12251 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
12237 if (kind == .inline_ref) {12266 if (kind == .inline_ref) {
12238 const item_val = sema.resolveValue(kind.inline_ref).?;12267 const item_val = sema.resolveValue(kind.inline_ref).?;
12239 if (operand_ty.zigTypeTag(zcu) == .@"union") {12268 if (tagged_union_originally) {
12240 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);12269 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);
12241 const union_obj = zcu.typeToUnion(operand_ty).?;12270 const union_obj = zcu.typeToUnion(operand_ty).?;
12242 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);12271 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
...@@ -12267,52 +12296,90 @@ fn analyzeSwitchPayloadCapture(...@@ -12267,52 +12296,90 @@ fn analyzeSwitchPayloadCapture(
12267 }12296 }
1226812297
12269 if (kind == .special) {12298 if (kind == .special) {
12270 if (capture_by_ref) return operand_ptr;12299 if (err_set) {
12271 return switch (operand_ty.zigTypeTag(zcu)) {12300 if (else_err_ty) |err_ty| {
12272 .error_set => e: {12301 return sema.bitCast(case_block, err_ty, operand_val, operand_src, null);
12273 if (else_err_ty) |err_ty| {12302 } else {
12274 break :e sema.bitCast(case_block, err_ty, operand_val, operand_src, null);12303 try sema.analyzeUnreachable(case_block, operand_src, false);
12275 } else {12304 return .unreachable_value;
12276 try sema.analyzeUnreachable(case_block, operand_src, false);12305 }
12277 break :e .unreachable_value;12306 }
12278 }12307 if (capture_by_ref) {
12279 },12308 return operand_ptr;
12280 else => operand_val,12309 }
12281 };12310 return operand_val;
12282 }12311 }
1228312312
12284 switch (operand_ty.zigTypeTag(zcu)) {12313 if (tagged_union_originally) {
12285 .@"union" => {12314 const case_vals = kind.item_refs;
12286 const case_vals = kind.item_refs;
1228712315
12288 const union_obj = zcu.typeToUnion(operand_ty).?;12316 const union_obj = zcu.typeToUnion(operand_ty).?;
12289 const first_item_val = sema.resolveValue(case_vals[0]).?;12317 const first_item_val = sema.resolveValue(case_vals[0]).?;
1229012318
12291 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;12319 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]);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);12322 const field_indices = try sema.arena.alloc(u32, case_vals.len);
12295 for (case_vals, field_indices) |item, *field_idx| {12323 for (case_vals, field_indices) |item, *field_idx| {
12296 const item_val = sema.resolveValue(item).?;12324 const item_val = sema.resolveValue(item).?;
12297 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;12325 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
12298 }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 hit12335 const capture_ty: Type = capture_ty: {
12301 // PTR! This will also allow us to emit simpler code.12336 if (same_types) break :capture_ty first_field_ty;
12302 const same_types = for (field_indices[1..]) |field_idx| {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| {
12303 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12340 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12304 if (!field_ty.eql(first_field_ty, zcu)) break false;12341 dummy.* = try pt.undefRef(field_ty);
12305 } else true;12342 }
1230612343
12307 const capture_ty: Type = capture_ty: {12344 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
12308 if (same_types) break :capture_ty first_field_ty;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.
12309 // We need values to run PTR on, so make a bunch of undef constants.12377 // We need values to run PTR on, so make a bunch of undef constants.
12310 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);12378 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
12311 for (dummy_captures, field_indices) |*dummy, field_idx| {12379 for (field_indices, dummy_captures) |field_index, *dummy| {
12312 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12380 const field_ptr_ty = try operand_ptr_ty.fieldPtrType(field_index, pt);
12313 dummy.* = try pt.undefRef(field_ty);12381 dummy.* = try pt.undefRef(field_ptr_ty);
12314 }12382 }
12315
12316 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);12383 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
12317 for (case_srcs, 0..) |*case_src, item_i| {12384 for (case_srcs, 0..) |*case_src, item_i| {
12318 case_src.* = .{12385 case_src.* = .{
...@@ -12325,7 +12392,7 @@ fn analyzeSwitchPayloadCapture(...@@ -12325,7 +12392,7 @@ fn analyzeSwitchPayloadCapture(
12325 };12392 };
12326 }12393 }
1232712394
12328 break :capture_ty sema.resolvePeerTypes(12395 break :resolve sema.resolvePeerTypes(
12329 case_block,12396 case_block,
12330 capture_src,12397 capture_src,
12331 dummy_captures,12398 dummy_captures,
...@@ -12333,6 +12400,7 @@ fn analyzeSwitchPayloadCapture(...@@ -12333,6 +12400,7 @@ fn analyzeSwitchPayloadCapture(
12333 ) catch |err| switch (err) {12400 ) catch |err| switch (err) {
12334 error.AnalysisFail => {12401 error.AnalysisFail => {
12335 const msg = sema.err orelse return error.AnalysisFail;12402 const msg = sema.err orelse return error.AnalysisFail;
12403 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
12336 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});12404 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12337 return error.AnalysisFail;12405 return error.AnalysisFail;
12338 },12406 },
...@@ -12340,272 +12408,222 @@ fn analyzeSwitchPayloadCapture(...@@ -12340,272 +12408,222 @@ fn analyzeSwitchPayloadCapture(
12340 };12408 };
12341 };12409 };
1234212410
12343 // By-reference captures have some further restrictions which make them easier to emit12411 if (try sema.resolveDefinedValue(case_block, operand_src, operand_ptr)) |op_ptr_val| {
12344 if (capture_by_ref) {12412 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
12345 const operand_ptr_ty = sema.typeOf(operand_ptr);12413 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
12346 const capture_ptr_ty = resolve: {12414 return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
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);
12391 }12415 }
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| {12421 if (try capture_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
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 }
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) {12431 try sema.requireRuntimeBlock(case_block, operand_src, null);
12406 return case_block.addStructFieldVal(operand_val, first_field_index, capture_ty);
12407 }
1240812432
12409 // We may have to emit a switch block which coerces the operand to the capture type.12433 if (same_types) {
12410 // If we can, try to avoid that using in-memory coercions.12434 return case_block.addStructFieldVal(operand_val, first_field_index, capture_ty);
12411 const first_non_imc = in_mem: {12435 }
12412 for (field_indices, 0..) |field_idx, i| {12436
12413 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12437 // We may have to emit a switch block which coerces the operand to the capture type.
12414 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {12438 // If we can, try to avoid that using in-memory coercions.
12415 break :in_mem i;12439 const first_non_imc = in_mem: {
12416 }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;
12417 }12444 }
12418 // All fields are in-memory coercible to the resolved type!12445 }
12419 // Just take the first field and bitcast the result.12446 // All fields are in-memory coercible to the resolved type!
12420 const uncoerced = try case_block.addStructFieldVal(operand_val, first_field_index, first_field_ty);12447 // Just take the first field and bitcast the result.
12421 return case_block.addBitCast(capture_ty, uncoerced);12448 const uncoerced = try case_block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
12422 };12449 return case_block.addBitCast(capture_ty, uncoerced);
12450 };
1242312451
12424 // By-val capture with heterogeneous types which are not all in-memory coercible to12452 // 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.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 well12455 // 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 using12456 // 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.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);12459 var in_mem_coercible: std.DynamicBitSet = try .initFull(sema.arena, field_indices.len);
12432 in_mem_coercible.unset(first_non_imc);12460 in_mem_coercible.unset(first_non_imc);
12433 {12461 {
12434 const next = first_non_imc + 1;12462 const next = first_non_imc + 1;
12435 for (field_indices[next..], next..) |field_idx, i| {12463 for (field_indices[next..], next..) |field_idx, i| {
12436 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12464 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)) {12465 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12438 in_mem_coercible.unset(i);12466 in_mem_coercible.unset(i);
12439 }
12440 }12467 }
12441 }12468 }
12469 }
1244212470
12443 const capture_block_inst = try case_block.addInstAsIndex(.{12471 const capture_block_inst = try case_block.addInstAsIndex(.{
12444 .tag = .block,12472 .tag = .block,
12445 .data = .{12473 .data = .{
12446 .ty_pl = .{12474 .ty_pl = .{
12447 .ty = .fromType(capture_ty),12475 .ty = .fromType(capture_ty),
12448 .payload = undefined, // updated below12476 .payload = undefined, // updated below
12449 },
12450 },12477 },
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 hints12483 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);12484 var cases_extra = try std.array_list.Managed(u32).initCapacity(sema.gpa, estimated_extra);
12457 defer cases_extra.deinit();12485 defer cases_extra.deinit();
1245812486
12459 {12487 {
12460 // All branch hints are `.none`, so just add zero elems.12488 // All branch hints are `.none`, so just add zero elems.
12461 comptime assert(@intFromEnum(std.builtin.BranchHint.none) == 0);12489 comptime assert(@intFromEnum(std.builtin.BranchHint.none) == 0);
12462 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;12490 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
12463 try cases_extra.appendNTimes(0, need_elems);12491 try cases_extra.appendNTimes(0, need_elems);
12464 }12492 }
1246512493
12466 {12494 {
12467 // Non-bitcast cases12495 // Non-bitcast cases
12468 var it = in_mem_coercible.iterator(.{ .kind = .unset });12496 var it = in_mem_coercible.iterator(.{ .kind = .unset });
12469 while (it.next()) |idx| {12497 while (it.next()) |idx| {
12470 var coerce_block = case_block.makeSubBlock();12498 var coerce_block = case_block.makeSubBlock();
12471 defer coerce_block.instructions.deinit(sema.gpa);12499 defer coerce_block.instructions.deinit(sema.gpa);
1247212500
12473 const case_src: LazySrcLoc = .{12501 const case_src: LazySrcLoc = .{
12474 .base_node_inst = capture_src.base_node_inst,12502 .base_node_inst = capture_src.base_node_inst,
12475 .offset = .{ .switch_case_item = .{12503 .offset = .{ .switch_case_item = .{
12476 .switch_node_offset = switch_node_offset,12504 .switch_node_offset = switch_node_offset,
12477 .case_idx = capture_src.offset.switch_capture.case_idx,12505 .case_idx = capture_src.offset.switch_capture.case_idx,
12478 .item_idx = .{ .kind = .single, .value = @intCast(idx) },12506 .item_idx = .{ .kind = .single, .value = @intCast(idx) },
12479 } },12507 } },
12480 };12508 };
1248112509
12482 const field_idx = field_indices[idx];12510 const field_idx = field_indices[idx];
12483 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12511 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);12512 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);12513 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
12486 _ = try coerce_block.addBr(capture_block_inst, coerced);12514 _ = try coerce_block.addBr(capture_block_inst, coerced);
1248712515
12488 try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".fields.len +12516 try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12489 1 + // `item`, no ranges12517 1 + // `item`, no ranges
12490 coerce_block.instructions.items.len);12518 coerce_block.instructions.items.len);
12491 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{12519 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12492 .items_len = 1,12520 .items_len = 1,
12493 .ranges_len = 0,12521 .ranges_len = 0,
12494 .body_len = @intCast(coerce_block.instructions.items.len),12522 .body_len = @intCast(coerce_block.instructions.items.len),
12495 }));12523 }));
12496 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item12524 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item
12497 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body12525 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
12498 }
12499 }12526 }
12500 const else_body_len = len: {12527 }
12501 // 'else' prong uses a bitcast12528 const else_body_len = len: {
12502 var coerce_block = case_block.makeSubBlock();12529 // 'else' prong uses a bitcast
12503 defer coerce_block.instructions.deinit(sema.gpa);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().?;12533 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
12506 const first_imc_field_idx = field_indices[first_imc_item_idx];12534 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]);12535 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);12536 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);12537 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
12510 _ = try coerce_block.addBr(capture_block_inst, coerced);12538 _ = try coerce_block.addBr(capture_block_inst, coerced);
1251112539
12512 try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));12540 try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));
12513 break :len coerce_block.instructions.items.len;12541 break :len coerce_block.instructions.items.len;
12514 };12542 };
1251512543
12516 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +12544 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +
12517 cases_extra.items.len +12545 cases_extra.items.len +
12518 @typeInfo(Air.Block).@"struct".fields.len +12546 @typeInfo(Air.Block).@"struct".fields.len +
12519 1);12547 1);
1252012548
12521 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);12549 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
12522 try sema.air_instructions.append(sema.gpa, .{12550 try sema.air_instructions.append(sema.gpa, .{
12523 .tag = .switch_br,12551 .tag = .switch_br,
12524 .data = .{12552 .data = .{
12525 .pl_op = .{12553 .pl_op = .{
12526 .operand = undefined, // set by switch below12554 .operand = undefined, // set by switch below
12527 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{12555 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
12528 .cases_len = @intCast(prong_count),12556 .cases_len = @intCast(prong_count),
12529 .else_body_len = @intCast(else_body_len),12557 .else_body_len = @intCast(else_body_len),
12530 }),12558 }),
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);
12561 },12559 },
12562 }12560 },
12561 });
12562 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
1256312563
12564 return capture_block_inst.toRef();12564 // Set up block body
12565 },12565 switch (operand) {
12566 .error_set => {12566 .simple => |s| {
12567 if (capture_by_ref) {12567 const air_datas = sema.air_instructions.items(.data);
12568 return sema.fail(12568 air_datas[switch_br_inst].pl_op.operand = s.cond;
12569 case_block,12569 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12570 capture_src,12570 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
12571 "error set cannot be captured by reference",12571 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12572 .{},12572 },
12573 );12573 .loop => {
12574 }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;12592 return capture_block_inst.toRef();
12577 if (case_vals.len == 1) {12593 }
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 }
1258212594
12583 var names: InferredErrorSet.NameMap = .{};12595 if (err_set) {
12584 try names.ensureUnusedCapacity(sema.arena, case_vals.len);12596 const case_vals = kind.item_refs;
12585 for (case_vals) |err| {12597 if (case_vals.len == 1) {
12586 const err_val = sema.resolveValue(err).?;12598 const item_val = sema.resolveValue(case_vals[0]).?;
12587 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});12599 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
12588 }12600 return sema.bitCast(case_block, item_ty, .fromValue(item_val), operand_src, null);
12589 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());12601 }
12590 return sema.bitCast(case_block, error_ty, operand_val, operand_src, null);12602
12591 },12603 var names: InferredErrorSet.NameMap = .{};
12592 else => {12604 try names.ensureUnusedCapacity(sema.arena, case_vals.len);
12593 // In this case the capture value is just the passed-through value of the12605 for (case_vals) |err| {
12594 // switch condition. It is comptime-known if there is only one item.12606 const err_val = sema.resolveValue(err).?;
12595 if (capture_by_ref) {12607 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});
12596 return operand_ptr;12608 }
12597 }12609 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());
12598 switch (kind) {12610 return sema.bitCast(case_block, error_ty, operand_val, operand_src, null);
12599 .inline_ref, .special => unreachable,12611 }
12600 .item_refs => |case_vals| {12612
12601 // If there's only a single item, the capture is comptime-known!12613 // In this case the capture value is just the passed-through value of the
12602 if (case_vals.len == 1) return case_vals[0];12614 // switch condition. It is comptime-known if there is only one item.
12603 },12615 if (capture_by_ref) {
12604 .has_ranges => {},12616 return operand_ptr;
12605 }12617 }
12606 return operand_val;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];
12607 },12623 },
12624 .has_ranges => {},
12608 }12625 }
12626 return operand_val;
12609}12627}
1261012628
12611const ResolvedSwitchItem = struct {12629const ResolvedSwitchItem = struct {
...@@ -12714,7 +12732,6 @@ fn validateSwitchItemOrRange(...@@ -12714,7 +12732,6 @@ fn validateSwitchItemOrRange(
12714 const zcu = pt.zcu;12732 const zcu = pt.zcu;
12715 const ip = &zcu.intern_pool;12733 const ip = &zcu.intern_pool;
12716 const maybe_prev_src: ?LazySrcLoc = maybe_prev_src: switch (item_ty.zigTypeTag(zcu)) {12734 const maybe_prev_src: ?LazySrcLoc = maybe_prev_src: switch (item_ty.zigTypeTag(zcu)) {
12717 .@"union" => unreachable,
12718 .@"enum" => {12735 .@"enum" => {
12719 const int = ip.indexToKey(item_val.toIntern()).enum_tag.int;12736 const int = ip.indexToKey(item_val.toIntern()).enum_tag.int;
12720 if (ip.loadEnumType(item_ty.toIntern()).tagValueIndex(ip, int)) |field_index| {12737 if (ip.loadEnumType(item_ty.toIntern()).tagValueIndex(ip, int)) |field_index| {
...@@ -12755,6 +12772,14 @@ fn validateSwitchItemOrRange(...@@ -12755,6 +12772,14 @@ fn validateSwitchItemOrRange(
12755 }, item_ty, zcu);12772 }, item_ty, zcu);
12756 }12773 }
12757 },12774 },
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 },
12758 .enum_literal, .@"fn", .pointer, .type => {12783 .enum_literal, .@"fn", .pointer, .type => {
12759 break :maybe_prev_src if (seen_sparse_values.fetchPutAssumeCapacity(item_val.toIntern(), item_src)) |prev|12784 break :maybe_prev_src if (seen_sparse_values.fetchPutAssumeCapacity(item_val.toIntern(), item_src)) |prev|
12760 prev.value12785 prev.value
src/codegen/llvm.zig+1-1
...@@ -6252,7 +6252,7 @@ pub const FuncGen = struct {...@@ -6252,7 +6252,7 @@ pub const FuncGen = struct {
6252 const cond_ty = self.typeOf(switch_br.operand);6252 const cond_ty = self.typeOf(switch_br.operand);
6253 switch (cond_ty.zigTypeTag(zcu)) {6253 switch (cond_ty.zigTypeTag(zcu)) {
6254 .bool, .pointer => break :jmp_table null,6254 .bool, .pointer => break :jmp_table null,
6255 .@"enum", .int, .error_set => {},6255 .@"enum", .int, .error_set, .@"struct", .@"union" => {},
6256 else => unreachable,6256 else => unreachable,
6257 }6257 }
62586258
test/behavior/switch.zig+130
...@@ -1327,3 +1327,133 @@ test "single range switch prong capture" {...@@ -1327,3 +1327,133 @@ test "single range switch prong capture" {
1327 try S.doTheTest(2);1327 try S.doTheTest(2);
1328 try comptime S.doTheTest(2);1328 try comptime S.doTheTest(2);
1329}1329}
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" {...@@ -509,3 +509,58 @@ test "switch loop for error handling" {
509 try S.doTheTest();509 try S.doTheTest();
510 try comptime S.doTheTest();510 try comptime S.doTheTest();
511}511}
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