authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-31 09:40:52+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-31 09:40:52+02:00
logd6386772daff1cd3932cc1f177be38b6c90c2e3b
tree313c34a44615fb5bdeee904edbe6a2ffe42ae333
parent1ea73060bbed6a3153e123c224f3eee67f0373d5
parentf4f5d06b8019bf6b7b3c205c730d70f291a07fbf

Merge pull request 'Sema: simplify switch capture logic' (#35207) from justusk/zig:switch-improve-captures into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/35207 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

4 files changed, 665 insertions(+), 509 deletions(-)

src/Sema.zig+554-494
......@@ -2415,6 +2415,32 @@ fn failWithModRemNegative(sema: *Sema, block: *Block, src: LazySrcLoc, lhs_ty: T
24152415 });
24162416}
24172417
2418fn failWithInvalidSwitchTagCapture(sema: *Sema, block: *Block, tag_capture_src: LazySrcLoc, operand_ty: Type) CompileError {
2419 const pt = sema.pt;
2420 const zcu = pt.zcu;
2421
2422 if (operand_ty.zigTypeTag(zcu) == .@"union") {
2423 assert(operand_ty.containerLayout(zcu) == .@"packed");
2424 return sema.failWithOwnedErrorMsg(block, msg: {
2425 const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of packed union", .{});
2426 errdefer msg.destroy(sema.gpa);
2427 try sema.addDeclaredHereNote(msg, operand_ty);
2428 if (operand_ty.srcLocOrNull(zcu)) |ty_src| {
2429 try sema.errNote(ty_src, msg, "consider using a tagged union", .{});
2430 }
2431 break :msg msg;
2432 });
2433 }
2434 return sema.failWithOwnedErrorMsg(block, msg: {
2435 const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
2436 operand_ty.fmt(pt),
2437 });
2438 errdefer msg.destroy(sema.gpa);
2439 try sema.addDeclaredHereNote(msg, operand_ty);
2440 break :msg msg;
2441 });
2442}
2443
24182444fn failWithExpectedOptionalType(sema: *Sema, block: *Block, src: LazySrcLoc, non_optional_ty: Type) CompileError {
24192445 const pt = sema.pt;
24202446 const msg = msg: {
......@@ -10022,7 +10048,7 @@ fn analyzeSwitchBlock(
1002210048
1002310049 const case_vals = validated_switch.case_vals;
1002410050
10025 const index, const body, const capture, const has_tag_capture, const is_inline, const is_special = find_prong: {
10051 const case_idx, const body, const capture, const has_tag_capture = find_prong: {
1002610052 var case_val_idx: usize = 0;
1002710053 var case_it = zir_switch.iterateCases();
1002810054 var extra_index = zir_switch.end;
......@@ -10045,12 +10071,12 @@ fn analyzeSwitchBlock(
1004510071 }
1004610072 continue;
1004710073 }
10048 break :find_prong .{ case.index, prong_body, prong_info.capture, prong_info.has_tag_capture, prong_info.is_inline, false };
10074 break :find_prong .{ case.index, prong_body, prong_info.capture, prong_info.has_tag_capture };
1004910075 }
1005010076 if (has_else) {
1005110077 // This *has* to be checked after iterating all regular cases because
1005210078 // we allow simple noreturn else prongs when switching on error sets!
10053 break :find_prong .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline, true };
10079 break :find_prong .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture };
1005410080 }
1005510081 unreachable; // malformed validated switch
1005610082 };
......@@ -10061,58 +10087,33 @@ fn analyzeSwitchBlock(
1006110087 if (!(err_set and
1006210088 try sema.maybeErrorUnwrap(&case_block, body, cond_ref, operand_src, true)))
1006310089 {
10064 // Set up captures manually to avoid special cases in the main logic.
10065 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
10090 const payload_inst = if (capture != .none) inst: {
1006610091 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
1006710092 const payload_ref: Air.Inst.Ref = payload_ref: {
10068 const item_val: Value = item_val: {
10093 const captured_opv: Value = captured_opv: {
1006910094 if (!tagged_union_originally) {
10070 break :item_val item_opv;
10095 break :captured_opv item_opv;
1007110096 }
1007210097 if (maybe_operand_opv) |operand_opv| {
10073 break :item_val .fromInterned(zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val);
10098 break :captured_opv .fromInterned(zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val);
1007410099 }
1007510100 assert(zir_switch.any_maybe_runtime_capture); // there's a payload capture
10076 const operand_val, const operand_ref = switch (operand) {
10077 .simple => unreachable,
10078 .loop => |l| load_operand: {
10079 const loaded = try sema.analyzeLoad(block, src, l.operand_alloc, src);
10080 if (l.operand_is_ref) {
10081 const by_val = try sema.analyzeLoad(block, src, loaded, src);
10082 break :load_operand .{ by_val, loaded };
10083 } else {
10084 break :load_operand .{ loaded, .none };
10085 }
10086 },
10087 };
10088 const prong_kind: SwitchProngKind = kind: {
10089 if (is_inline) break :kind .{ .inline_ref = .fromValue(item_opv) };
10090 if (is_special) break :kind .special;
10091 break :kind .{ .item_refs = &.{.fromValue(item_opv)} };
10092 };
10093 break :payload_ref try sema.analyzeSwitchPayloadCapture(
10101 const loaded_operand = try sema.analyzeSwitchOperandLoad(&case_block, operand, operand_src, capture == .by_ref);
10102 break :payload_ref try sema.resolveSwitchPayloadCaptureTaggedUnion(
1009410103 &case_block,
10095 operand,
10096 operand_val,
10097 operand_ref,
10098 operand_ty,
10104 loaded_operand,
1009910105 operand_src,
10100 block.src(.{ .switch_capture = .{
10101 .switch_node_offset = src_node_offset,
10102 .case_idx = index,
10103 } }),
10106 operand_ty,
10107 item_opv,
1010410108 capture == .by_ref,
10105 prong_kind,
10106 validated_switch.else_err_ty,
1010710109 );
1010810110 };
1010910111 break :payload_ref switch (capture) {
10110 .by_val => .fromValue(item_val),
10111 .by_ref => try sema.uavRef(item_val),
10112 .by_val => .fromValue(captured_opv),
10113 .by_ref => try sema.uavRef(captured_opv),
1011210114 .none => unreachable,
1011310115 };
1011410116 };
10115 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
1011610117 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
1011710118 break :inst payload_inst;
1011810119 } else undefined;
......@@ -10120,6 +10121,13 @@ fn analyzeSwitchBlock(
1012010121
1012110122 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
1012210123 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
10124 if (!tagged_union_originally) {
10125 const tag_capture_src = block.src(.{ .switch_tag_capture = .{
10126 .switch_node_offset = src_node_offset,
10127 .case_idx = case_idx,
10128 } });
10129 return sema.failWithInvalidSwitchTagCapture(block, tag_capture_src, operand_ty);
10130 }
1012310131 sema.inst_map.putAssumeCapacity(tag_inst, .fromValue(item_opv));
1012410132 break :inst tag_inst;
1012510133 } else undefined;
......@@ -10405,7 +10413,7 @@ fn finishSwitchBr(
1040510413 } }),
1040610414 prong_info.capture,
1040710415 prong_info.has_tag_capture,
10408 .{ .inline_ref = item_ref },
10416 .{ .@"inline" = item_ref },
1040910417 validated_switch.else_err_ty,
1041010418 switch_inst,
1041110419 zir_switch,
......@@ -10495,7 +10503,7 @@ fn finishSwitchBr(
1049510503 } }),
1049610504 prong_info.capture,
1049710505 prong_info.has_tag_capture,
10498 .{ .inline_ref = item_ref },
10506 .{ .@"inline" = item_ref },
1049910507 validated_switch.else_err_ty,
1050010508 switch_inst,
1050110509 zir_switch,
......@@ -10635,7 +10643,7 @@ fn finishSwitchBr(
1063510643 } }),
1063610644 else_case.capture,
1063710645 else_case.has_tag_capture,
10638 .{ .inline_ref = item_ref },
10646 .{ .@"inline" = item_ref },
1063910647 validated_switch.else_err_ty,
1064010648 switch_inst,
1064110649 zir_switch,
......@@ -11075,8 +11083,7 @@ fn validateSwitchBlock(
1107511083 const has_else = zir_switch.else_case != null;
1107611084 const has_under = zir_switch.has_under;
1107711085
11078 var case_vals: std.ArrayList(Air.Inst.Ref) = .empty;
11079 try case_vals.ensureUnusedCapacity(arena, zir_switch.item_infos.len);
11086 var case_vals: std.ArrayList(Air.Inst.Ref) = try .initCapacity(arena, zir_switch.item_infos.len);
1108011087
1108111088 // Duplicate checking variables later also used for `inline else`.
1108211089 var seen_enum_fields: []?LazySrcLoc = &.{};
......@@ -11482,10 +11489,10 @@ fn resolveSwitchBlock(
1148211489 // This prong should be unreachable!
1148311490 return .unreachable_value;
1148411491 }
11485 const prong_kind: SwitchProngKind = kind: {
11486 if (prong_info.is_inline) break :kind .{ .inline_ref = cond_ref };
11487 if (range_refs.len > 0) break :kind .has_ranges;
11488 break :kind .{ .item_refs = item_refs };
11492 const prong_items: SwitchProngItems = prong_items: {
11493 if (prong_info.is_inline) break :prong_items .{ .@"inline" = cond_ref };
11494 if (range_refs.len > 0) break :prong_items .has_ranges;
11495 break :prong_items .{ .item_refs = item_refs };
1148911496 };
1149011497 return sema.resolveSwitchProng(
1149111498 block,
......@@ -11499,7 +11506,7 @@ fn resolveSwitchBlock(
1149911506 } }),
1150011507 prong_info.capture,
1150111508 prong_info.has_tag_capture,
11502 prong_kind,
11509 prong_items,
1150311510 validated_switch.else_err_ty,
1150411511 merges,
1150511512 switch_inst,
......@@ -11513,8 +11520,8 @@ fn resolveSwitchBlock(
1151311520 if ((try sema.compareAll(cond_val, .gte, first_val, item_ty)) and
1151411521 (try sema.compareAll(cond_val, .lte, last_val, item_ty)))
1151511522 {
11516 const prong_kind: SwitchProngKind = if (prong_info.is_inline)
11517 .{ .inline_ref = cond_ref }
11523 const prong_items: SwitchProngItems = if (prong_info.is_inline)
11524 .{ .@"inline" = cond_ref }
1151811525 else
1151911526 .has_ranges;
1152011527 return sema.resolveSwitchProng(
......@@ -11529,7 +11536,7 @@ fn resolveSwitchBlock(
1152911536 } }),
1153011537 prong_info.capture,
1153111538 prong_info.has_tag_capture,
11532 prong_kind,
11539 prong_items,
1153311540 validated_switch.else_err_ty,
1153411541 merges,
1153511542 switch_inst,
......@@ -11548,8 +11555,8 @@ fn resolveSwitchBlock(
1154811555
1154911556 if (else_is_named_only and item_ty.enumTagFieldIndex(cond_val, zcu) != null) {
1155011557 assert(item_ty.isNonexhaustiveEnum(zcu));
11551 const prong_kind: SwitchProngKind = if (else_case.is_inline)
11552 .{ .inline_ref = cond_ref }
11558 const prong_items: SwitchProngItems = if (else_case.is_inline)
11559 .{ .@"inline" = cond_ref }
1155311560 else
1155411561 .special;
1155511562 return sema.resolveSwitchProng(
......@@ -11564,7 +11571,7 @@ fn resolveSwitchBlock(
1156411571 } }),
1156511572 else_case.capture,
1156611573 else_case.has_tag_capture,
11567 prong_kind,
11574 prong_items,
1156811575 validated_switch.else_err_ty,
1156911576 merges,
1157011577 switch_inst,
......@@ -11588,8 +11595,8 @@ fn resolveSwitchBlock(
1158811595 return .unreachable_value;
1158911596 }
1159011597 }
11591 const prong_kind: SwitchProngKind = if (is_inline)
11592 .{ .inline_ref = cond_ref }
11598 const prong_items: SwitchProngItems = if (is_inline)
11599 .{ .@"inline" = cond_ref }
1159311600 else
1159411601 .special;
1159511602 return sema.resolveSwitchProng(
......@@ -11604,7 +11611,7 @@ fn resolveSwitchBlock(
1160411611 } }),
1160511612 capture,
1160611613 has_tag_capture,
11607 prong_kind,
11614 prong_items,
1160811615 validated_switch.else_err_ty,
1160911616 merges,
1161011617 switch_inst,
......@@ -11640,14 +11647,59 @@ const SwitchOperand = union(enum) {
1164011647 },
1164111648};
1164211649
11643const SwitchProngKind = union(enum) {
11644 /// Prefer populating this field over the others, if possible.
11645 inline_ref: Air.Inst.Ref,
11650fn analyzeSwitchOperandLoad(
11651 sema: *Sema,
11652 block: *Block,
11653 operand: SwitchOperand,
11654 operand_src: LazySrcLoc,
11655 by_ref: bool,
11656) CompileError!Air.Inst.Ref {
11657 switch (operand) {
11658 .simple => |s| {
11659 if (by_ref) {
11660 assert(s.by_ref != .none);
11661 return s.by_ref;
11662 } else {
11663 return s.by_val;
11664 }
11665 },
11666 .loop => |l| {
11667 const loaded = try sema.analyzeLoad(block, operand_src, l.operand_alloc, operand_src);
11668 assert(loaded != .none); // there are no captures, so no need to load the switch operand
11669 if (by_ref) {
11670 assert(l.operand_is_ref);
11671 return loaded;
11672 }
11673 return if (l.operand_is_ref)
11674 try sema.analyzeLoad(block, operand_src, loaded, operand_src)
11675 else
11676 loaded;
11677 },
11678 }
11679}
11680
11681const SwitchProngItems = union(enum) {
11682 @"inline": Air.Inst.Ref,
1164611683 item_refs: []const Air.Inst.Ref,
1164711684 has_ranges,
1164811685 special,
1164911686};
1165011687
11688/// A switch capture is comptime-known if it is `inline` and/or it is a by-value
11689/// capture of a prong with a single item.
11690fn resolveSwitchCaptureFromProngItems(
11691 sema: *Sema,
11692 prong_items: SwitchProngItems,
11693 by_ref: bool,
11694) ?Value {
11695 const ref: Air.Inst.Ref = switch (prong_items) {
11696 .@"inline" => |ref| ref,
11697 .item_refs => |refs| if (refs.len == 1 and !by_ref) refs[0] else return null,
11698 .has_ranges, .special => return null,
11699 };
11700 return sema.resolveValue(ref).?;
11701}
11702
1165111703/// Resolve a switch prong which is determined at comptime to have no peers.
1165211704/// Sets up captures as needed. Uses `analyzeBodyRuntimeBreak`.
1165311705fn resolveSwitchProng(
......@@ -11661,7 +11713,7 @@ fn resolveSwitchProng(
1166111713 capture_src: LazySrcLoc,
1166211714 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
1166311715 has_tag_capture: bool,
11664 kind: SwitchProngKind,
11716 prong_items: SwitchProngItems,
1166511717 else_err_ty: ?Type,
1166611718 merges: *Block.Merges,
1166711719 switch_inst: Zir.Inst.Index,
......@@ -11676,36 +11728,28 @@ fn resolveSwitchProng(
1167611728 const parent_hint = sema.branch_hint;
1167711729 defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
1167811730
11679 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
11731 const analyzed_captures = try sema.analyzeSwitchCaptures(
11732 child_block,
11733 operand,
11734 operand_src,
11735 sema.typeOf(operand.simple.by_val),
11736 capture_src,
11737 capture,
11738 has_tag_capture,
11739 prong_items,
11740 else_err_ty,
11741 );
11742
11743 const payload_inst = if (capture != .none) inst: {
1168011744 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
11681 const payload_ref = try sema.analyzeSwitchPayloadCapture(
11682 child_block,
11683 operand,
11684 operand.simple.by_val,
11685 operand.simple.by_ref,
11686 sema.typeOf(operand.simple.by_val),
11687 operand_src,
11688 capture_src,
11689 capture == .by_ref,
11690 kind,
11691 else_err_ty,
11692 );
11693 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
11694 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
11745 sema.inst_map.putAssumeCapacity(payload_inst, analyzed_captures.payload_ref);
1169511746 break :inst payload_inst;
1169611747 } else undefined;
1169711748 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1169811749
1169911750 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
1170011751 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
11701 const tag_ref = try sema.analyzeSwitchTagCapture(
11702 child_block,
11703 operand.simple.by_val,
11704 sema.typeOf(operand.simple.by_val),
11705 capture_src,
11706 kind,
11707 );
11708 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
11752 sema.inst_map.putAssumeCapacity(tag_inst, analyzed_captures.tag_ref);
1170911753 break :inst tag_inst;
1171011754 } else undefined;
1171111755 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
......@@ -11751,7 +11795,7 @@ fn analyzeSwitchProng(
1175111795 capture_src: LazySrcLoc,
1175211796 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
1175311797 has_tag_capture: bool,
11754 kind: SwitchProngKind,
11798 prong_items: SwitchProngItems,
1175511799 else_err_ty: ?Type,
1175611800 switch_inst: Zir.Inst.Index,
1175711801 zir_switch: *const Zir.UnwrappedSwitchBlock,
......@@ -11772,77 +11816,28 @@ fn analyzeSwitchProng(
1177211816 }
1177311817 }
1177411818
11775 const need_load: bool = need_load: {
11776 if (capture == .none and !has_tag_capture) {
11777 // No need to load the operand for this prong!
11778 break :need_load false;
11779 }
11780 if (capture != .none and operand_ty.zigTypeTag(zcu) == .@"union" and
11781 operand_ty.containerLayout(zcu) != .@"packed")
11782 {
11783 // Non-OPV tagged union payload captures are always runtime-known.
11784 break :need_load true;
11785 }
11786 if (kind == .inline_ref) {
11787 // `inline_ref` *is* the (comptime-known) capture.
11788 break :need_load false;
11789 }
11790 assert(zir_switch.any_maybe_runtime_capture); // should have caught everything else by now
11791 if (capture != .by_ref and
11792 kind == .item_refs and kind.item_refs.len == 1)
11793 {
11794 // Capture is comptime-known because it's the only prong item
11795 break :need_load false;
11796 }
11797 break :need_load true;
11798 };
11799
11800 const operand_val: Air.Inst.Ref, const operand_ptr: Air.Inst.Ref = load_operand: {
11801 if (!need_load) break :load_operand .{ .none, .none };
11802 switch (operand) {
11803 .simple => |s| break :load_operand .{ s.by_val, s.by_ref },
11804 .loop => |l| {
11805 const loaded = try sema.analyzeLoad(case_block, operand_src, l.operand_alloc, operand_src);
11806 if (l.operand_is_ref) {
11807 const by_val = try sema.analyzeLoad(case_block, operand_src, loaded, operand_src);
11808 break :load_operand .{ by_val, loaded };
11809 } else {
11810 break :load_operand .{ loaded, .none };
11811 }
11812 },
11813 }
11814 };
11819 const analyzed_captures = try sema.analyzeSwitchCaptures(
11820 case_block,
11821 operand,
11822 operand_src,
11823 operand_ty,
11824 capture_src,
11825 capture,
11826 has_tag_capture,
11827 prong_items,
11828 else_err_ty,
11829 );
1181511830
11816 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
11831 const payload_inst = if (capture != .none) inst: {
1181711832 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
11818 const payload_ref = try sema.analyzeSwitchPayloadCapture(
11819 case_block,
11820 operand,
11821 operand_val,
11822 operand_ptr,
11823 operand_ty,
11824 operand_src,
11825 capture_src,
11826 capture == .by_ref,
11827 kind,
11828 else_err_ty,
11829 );
11830 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
11831 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
11833 sema.inst_map.putAssumeCapacity(payload_inst, analyzed_captures.payload_ref);
1183211834 break :inst payload_inst;
1183311835 } else undefined;
1183411836 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1183511837
1183611838 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
1183711839 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
11838 const tag_ref = try sema.analyzeSwitchTagCapture(
11839 case_block,
11840 operand_val,
11841 operand_ty,
11842 capture_src,
11843 kind,
11844 );
11845 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
11840 sema.inst_map.putAssumeCapacity(tag_inst, analyzed_captures.tag_ref);
1184611841 break :inst tag_inst;
1184711842 } else undefined;
1184811843 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
......@@ -11853,157 +11848,295 @@ fn analyzeSwitchProng(
1185311848 return sema.analyzeBodyRuntimeBreak(case_block, prong_body);
1185411849}
1185511850
11856fn analyzeSwitchTagCapture(
11851fn analyzeSwitchCaptures(
1185711852 sema: *Sema,
1185811853 case_block: *Block,
11859 /// May be `none` if this is an inline capture or if `kind.item_refs.len == 1`.
11860 operand_val: Air.Inst.Ref,
11854 operand: SwitchOperand,
11855 operand_src: LazySrcLoc,
1186111856 operand_ty: Type,
1186211857 capture_src: LazySrcLoc,
11863 kind: SwitchProngKind,
11864) CompileError!Air.Inst.Ref {
11858 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
11859 has_tag_capture: bool,
11860 prong_items: SwitchProngItems,
11861 else_err_ty: ?Type,
11862) CompileError!struct {
11863 payload_ref: Air.Inst.Ref,
11864 tag_ref: Air.Inst.Ref,
11865} {
1186511866 const pt = sema.pt;
1186611867 const zcu = pt.zcu;
1186711868
11868 const tag_capture_src: LazySrcLoc = .{
11869 .base_node_inst = capture_src.base_node_inst,
11870 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
11871 };
11869 if (operand_ty.zigTypeTag(zcu) == .@"union" and
11870 operand_ty.containerLayout(zcu) != .@"packed")
11871 {
11872 if (capture == .none) {
11873 const tag_ref: Air.Inst.Ref = tag_ref: {
11874 if (!has_tag_capture) break :tag_ref .none;
11875 if (sema.resolveSwitchCaptureFromProngItems(prong_items, false)) |tag_val| {
11876 break :tag_ref .fromValue(tag_val);
11877 }
11878 const loaded_operand = try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, false);
11879 break :tag_ref try sema.unionToTag(case_block, loaded_operand);
11880 };
11881 return .{ .payload_ref = .none, .tag_ref = tag_ref };
11882 }
1187211883
11873 if (operand_ty.zigTypeTag(zcu) != .@"union") {
11874 return sema.fail(case_block, tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
11875 operand_ty.fmt(pt),
11876 });
11877 }
11878 if (operand_ty.containerLayout(zcu) == .@"packed") {
11879 return sema.fail(case_block, tag_capture_src, "cannot capture tag of packed union", .{});
11884 // We always have to load the operand for tagged union payload captures
11885 // since we can't derive the payload value from the tag (except for OPV
11886 // types, for which the load is always basically a noop anyway).
11887
11888 const loaded_operand = try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, capture == .by_ref);
11889
11890 if (sema.resolveSwitchCaptureFromProngItems(prong_items, capture == .by_ref)) |tag_val| {
11891 const payload_ref = try sema.resolveSwitchPayloadCaptureTaggedUnion(
11892 case_block,
11893 loaded_operand,
11894 operand_src,
11895 operand_ty,
11896 tag_val,
11897 capture == .by_ref,
11898 );
11899 const tag_ref: Air.Inst.Ref = if (has_tag_capture) .fromValue(tag_val) else .none;
11900 return .{ .payload_ref = payload_ref, .tag_ref = tag_ref };
11901 }
11902
11903 const payload_ref = try sema.analyzeSwitchPayloadCaptureTaggedUnion(
11904 case_block,
11905 operand,
11906 loaded_operand,
11907 operand_src,
11908 operand_ty,
11909 capture == .by_ref,
11910 capture_src,
11911 prong_items,
11912 );
11913
11914 const tag_ref: Air.Inst.Ref = tag_ref: {
11915 if (!has_tag_capture) break :tag_ref .none;
11916 const operand_val = switch (capture) {
11917 .none => unreachable, // handled above
11918 .by_val => loaded_operand,
11919 .by_ref => try sema.analyzeLoad(case_block, operand_src, loaded_operand, operand_src),
11920 };
11921 break :tag_ref try sema.unionToTag(case_block, operand_val);
11922 };
11923
11924 assert(!sema.typeOf(payload_ref).isNoReturn(zcu));
11925 return .{ .payload_ref = payload_ref, .tag_ref = tag_ref };
1188011926 }
11881 switch (kind) {
11882 .has_ranges => unreachable,
11883 .inline_ref => |ref| return ref,
11884 .item_refs => |refs| if (refs.len == 1) return refs[0],
11885 .special => {},
11927
11928 const payload_ref: Air.Inst.Ref = payload_ref: {
11929 if (capture == .none) break :payload_ref .none;
11930
11931 if (operand_ty.zigTypeTag(zcu) == .error_set) {
11932 // Error captures need to have their type narrowed!
11933
11934 if (capture == .by_ref) {
11935 return sema.fail(
11936 case_block,
11937 capture_src,
11938 "error set cannot be captured by reference",
11939 .{},
11940 );
11941 }
11942 assert(capture == .by_val);
11943
11944 if (sema.resolveSwitchCaptureFromProngItems(prong_items, false)) |err_val| {
11945 const err_name = err_val.getErrorName(zcu).unwrap().?;
11946 break :payload_ref .fromIntern((try pt.intern(.{ .err = .{
11947 .ty = (try pt.singleErrorSetType(err_name)).toIntern(),
11948 .name = err_name,
11949 } })));
11950 }
11951
11952 const loaded_operand = try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, false);
11953
11954 switch (prong_items) {
11955 .@"inline" => unreachable, // handled above
11956 .has_ranges => unreachable, // not possible for error set
11957 .special => {
11958 if (else_err_ty) |err_ty| {
11959 break :payload_ref try sema.bitCast(case_block, err_ty, loaded_operand, operand_src, null);
11960 } else {
11961 try sema.analyzeUnreachable(case_block, operand_src, false);
11962 break :payload_ref .unreachable_value;
11963 }
11964 },
11965 .item_refs => |item_refs| {
11966 var names: InferredErrorSet.NameMap = .{};
11967 try names.ensureUnusedCapacity(sema.arena, item_refs.len);
11968 for (item_refs) |item_ref| {
11969 const item_val = sema.resolveValue(item_ref).?;
11970 names.putAssumeCapacityNoClobber(item_val.getErrorName(zcu).unwrap().?, {});
11971 }
11972 const narrowed_ty = try pt.errorSetFromUnsortedNames(names.keys());
11973 break :payload_ref try sema.bitCast(case_block, narrowed_ty, loaded_operand, operand_src, null);
11974 },
11975 }
11976 }
11977
11978 // We try to make the capture comptime-known based on `prong_items` first:
11979
11980 if (sema.resolveSwitchCaptureFromProngItems(prong_items, capture == .by_ref)) |item_val| {
11981 break :payload_ref switch (capture) {
11982 .none => unreachable, // handled above
11983 .by_val => .fromValue(item_val),
11984 .by_ref => try sema.uavRef(item_val),
11985 };
11986 }
11987
11988 // Otherwise the capture value is just the passed-through value of the
11989 // switch condition (which we might have to load first).
11990
11991 break :payload_ref try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, capture == .by_ref);
11992 };
11993
11994 if (has_tag_capture) {
11995 const tag_capture_src: LazySrcLoc = .{
11996 .base_node_inst = capture_src.base_node_inst,
11997 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
11998 };
11999 return sema.failWithInvalidSwitchTagCapture(case_block, tag_capture_src, operand_ty);
1188612000 }
11887 return sema.unionToTag(case_block, operand_val);
12001
12002 return .{ .payload_ref = payload_ref, .tag_ref = .none };
1188812003}
1188912004
11890fn analyzeSwitchPayloadCapture(
12005fn resolveSwitchPayloadCaptureTaggedUnion(
1189112006 sema: *Sema,
1189212007 case_block: *Block,
11893 operand: SwitchOperand,
11894 /// Always has to be not-`none` if this is a tagged union payload capture.
11895 /// For non-tagged-union captures, this may be `none` if this is an inline
11896 /// capture or if `kind.item_refs.len == 1` and capture is by val.
11897 operand_val: Air.Inst.Ref,
11898 /// May be `none` if `capture_by_ref` is `false` or if `operand_val` is also `none`.
11899 operand_ptr: Air.Inst.Ref,
12008 loaded_operand: Air.Inst.Ref,
12009 operand_src: LazySrcLoc,
1190012010 operand_ty: Type,
12011 tag_val: Value,
12012 capture_by_ref: bool,
12013) CompileError!Air.Inst.Ref {
12014 const pt = sema.pt;
12015 const zcu = pt.zcu;
12016 const ip = &zcu.intern_pool;
12017
12018 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(tag_val, zcu).?);
12019 const union_obj = zcu.typeToUnion(operand_ty).?;
12020 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
12021 const payload_ref: Air.Inst.Ref = payload_ref: {
12022 if (capture_by_ref) {
12023 const operand_ptr_info = sema.typeOf(loaded_operand).ptrInfo(zcu);
12024 const ptr_field_ty = try pt.ptrType(.{
12025 .child = field_ty.toIntern(),
12026 .flags = .{
12027 .is_const = operand_ptr_info.flags.is_const,
12028 .is_volatile = operand_ptr_info.flags.is_volatile,
12029 .address_space = operand_ptr_info.flags.address_space,
12030 },
12031 });
12032 break :payload_ref try case_block.addStructFieldPtr(loaded_operand, field_index, ptr_field_ty);
12033 }
12034 if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |union_val| {
12035 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
12036 break :payload_ref .fromIntern(tag_and_val.val);
12037 }
12038 if (try field_ty.onePossibleValue(pt)) |opv| break :payload_ref .fromValue(opv);
12039 break :payload_ref try case_block.addStructFieldVal(loaded_operand, field_index, field_ty);
12040 };
12041 assert(!sema.typeOf(payload_ref).isNoReturn(zcu));
12042 return payload_ref;
12043}
12044
12045fn analyzeSwitchPayloadCaptureTaggedUnion(
12046 sema: *Sema,
12047 case_block: *Block,
12048 operand: SwitchOperand,
12049 loaded_operand: Air.Inst.Ref,
1190112050 operand_src: LazySrcLoc,
11902 capture_src: LazySrcLoc,
12051 operand_ty: Type,
1190312052 capture_by_ref: bool,
11904 kind: SwitchProngKind,
11905 else_err_ty: ?Type,
12053 capture_src: LazySrcLoc,
12054 prong_items: SwitchProngItems,
1190612055) CompileError!Air.Inst.Ref {
1190712056 const pt = sema.pt;
1190812057 const zcu = pt.zcu;
1190912058 const ip = &zcu.intern_pool;
12059 const gpa = sema.gpa;
12060
12061 const item_refs: []const Air.Inst.Ref = switch (prong_items) {
12062 .@"inline" => unreachable, // handled above
12063 .has_ranges => unreachable, // not possible for tagged union
12064 .special => return loaded_operand,
12065 .item_refs => |item_refs| item_refs,
12066 };
1191012067
1191112068 const switch_node_offset = operand_src.offset.node_offset_switch_operand;
1191212069
11913 const tagged_union_originally = operand_ty.zigTypeTag(zcu) == .@"union" and
11914 operand_ty.containerLayout(zcu) != .@"packed";
11915 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
12070 const union_obj = zcu.typeToUnion(operand_ty).?;
1191612071
11917 if (err_set and capture_by_ref) {
11918 return sema.fail(
11919 case_block,
11920 capture_src,
11921 "error set cannot be captured by reference",
11922 .{},
11923 );
11924 }
12072 const first_item_val = sema.resolveValue(item_refs[0]).?;
12073 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;
12074 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);
1192512075
11926 if (kind == .inline_ref) {
11927 const item_val = sema.resolveValue(kind.inline_ref).?;
11928 if (tagged_union_originally) {
11929 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);
11930 const union_obj = zcu.typeToUnion(operand_ty).?;
11931 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
11932 if (capture_by_ref) {
11933 const operand_ptr_info = sema.typeOf(operand_ptr).ptrInfo(zcu);
11934 const ptr_field_ty = try pt.ptrType(.{
11935 .child = field_ty.toIntern(),
11936 .flags = .{
11937 .is_const = operand_ptr_info.flags.is_const,
11938 .is_volatile = operand_ptr_info.flags.is_volatile,
11939 .address_space = operand_ptr_info.flags.address_space,
11940 },
11941 });
11942 return case_block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);
11943 } else {
11944 if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |union_val| {
11945 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
11946 return .fromIntern(tag_and_val.val);
11947 }
11948 if (try field_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
11949 return case_block.addStructFieldVal(operand_val, field_index, field_ty);
11950 }
11951 } else if (capture_by_ref) {
11952 return sema.uavRef(item_val);
11953 } else {
11954 return kind.inline_ref;
11955 }
12076 const field_indices = try sema.arena.alloc(u32, item_refs.len);
12077 for (item_refs, field_indices) |item_ref, *field_idx| {
12078 const item_val = sema.resolveValue(item_ref).?;
12079 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
1195612080 }
1195712081
11958 if (kind == .special) {
11959 if (err_set) {
11960 if (else_err_ty) |err_ty| {
11961 return sema.bitCast(case_block, err_ty, operand_val, operand_src, null);
11962 } else {
11963 try sema.analyzeUnreachable(case_block, operand_src, false);
11964 return .unreachable_value;
11965 }
11966 }
11967 if (capture_by_ref) {
11968 return operand_ptr;
11969 }
11970 return operand_val;
11971 }
12082 // Fast path: if all the operands are the same type already, we don't need to hit
12083 // PTR! This will also allow us to emit simpler code.
12084 const same_types = for (field_indices[1..]) |field_idx| {
12085 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12086 if (!field_ty.eql(first_field_ty, zcu)) break false;
12087 } else true;
1197212088
11973 if (tagged_union_originally) {
11974 const case_vals = kind.item_refs;
11975
11976 const union_obj = zcu.typeToUnion(operand_ty).?;
11977 const first_item_val = sema.resolveValue(case_vals[0]).?;
11978
11979 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;
11980 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);
12089 const capture_ty: Type = capture_ty: {
12090 if (same_types) break :capture_ty first_field_ty;
12091 // We need values to run PTR on, so make a bunch of undef constants.
12092 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, item_refs.len);
12093 for (dummy_captures, field_indices) |*dummy, field_idx| {
12094 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12095 dummy.* = try pt.undefRef(field_ty);
12096 }
1198112097
11982 const field_indices = try sema.arena.alloc(u32, case_vals.len);
11983 for (case_vals, field_indices) |item, *field_idx| {
11984 const item_val = sema.resolveValue(item).?;
11985 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
12098 const item_srcs = try sema.arena.alloc(?LazySrcLoc, item_refs.len);
12099 for (item_srcs, 0..) |*item_src, item_i| {
12100 item_src.* = .{
12101 .base_node_inst = capture_src.base_node_inst,
12102 .offset = .{ .switch_case_item = .{
12103 .switch_node_offset = switch_node_offset,
12104 .case_idx = capture_src.offset.switch_capture.case_idx,
12105 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
12106 } },
12107 };
1198612108 }
1198712109
11988 // Fast path: if all the operands are the same type already, we don't need to hit
11989 // PTR! This will also allow us to emit simpler code.
11990 const same_types = for (field_indices[1..]) |field_idx| {
11991 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
11992 if (!field_ty.eql(first_field_ty, zcu)) break false;
11993 } else true;
12110 break :capture_ty sema.resolvePeerTypes(
12111 case_block,
12112 capture_src,
12113 dummy_captures,
12114 .{ .override = item_srcs },
12115 ) catch |err| switch (err) {
12116 error.AnalysisFail => {
12117 const msg = sema.err orelse return error.AnalysisFail;
12118 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12119 return error.AnalysisFail;
12120 },
12121 else => |e| return e,
12122 };
12123 };
1199412124
11995 const capture_ty: Type = capture_ty: {
11996 if (same_types) break :capture_ty first_field_ty;
12125 // By-reference captures have some further restrictions which make them easier to emit
12126 if (capture_by_ref) {
12127 const operand_ptr_ty = sema.typeOf(loaded_operand);
12128 const capture_ptr_ty = resolve: {
12129 // By-ref captures of hetereogeneous types are only allowed if all field
12130 // pointer types are peer resolvable to each other.
1199712131 // We need values to run PTR on, so make a bunch of undef constants.
11998 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
11999 for (dummy_captures, field_indices) |*dummy, field_idx| {
12000 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12001 dummy.* = try pt.undefRef(field_ty);
12002 }
12003
12004 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
12005 for (case_srcs, 0..) |*case_src, item_i| {
12006 case_src.* = .{
12132 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, item_refs.len);
12133 for (field_indices, dummy_captures) |field_index, *dummy| {
12134 const field_ptr_ty = try operand_ptr_ty.fieldPtrType(field_index, pt);
12135 dummy.* = try pt.undefRef(field_ptr_ty);
12136 }
12137 const item_srcs = try sema.arena.alloc(?LazySrcLoc, item_refs.len);
12138 for (item_srcs, 0..) |*item_src, item_i| {
12139 item_src.* = .{
1200712140 .base_node_inst = capture_src.base_node_inst,
1200812141 .offset = .{ .switch_case_item = .{
1200912142 .switch_node_offset = switch_node_offset,
......@@ -12013,14 +12146,15 @@ fn analyzeSwitchPayloadCapture(
1201312146 };
1201412147 }
1201512148
12016 break :capture_ty sema.resolvePeerTypes(
12149 break :resolve sema.resolvePeerTypes(
1201712150 case_block,
1201812151 capture_src,
1201912152 dummy_captures,
12020 .{ .override = case_srcs },
12153 .{ .override = item_srcs },
1202112154 ) catch |err| switch (err) {
1202212155 error.AnalysisFail => {
1202312156 const msg = sema.err orelse return error.AnalysisFail;
12157 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
1202412158 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
1202512159 return error.AnalysisFail;
1202612160 },
......@@ -12028,262 +12162,188 @@ fn analyzeSwitchPayloadCapture(
1202812162 };
1202912163 };
1203012164
12031 // By-reference captures have some further restrictions which make them easier to emit
12032 if (capture_by_ref) {
12033 const operand_ptr_ty = sema.typeOf(operand_ptr);
12034 const capture_ptr_ty = resolve: {
12035 // By-ref captures of hetereogeneous types are only allowed if all field
12036 // pointer types are peer resolvable to each other.
12037 // We need values to run PTR on, so make a bunch of undef constants.
12038 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
12039 for (field_indices, dummy_captures) |field_index, *dummy| {
12040 const field_ptr_ty = try operand_ptr_ty.fieldPtrType(field_index, pt);
12041 dummy.* = try pt.undefRef(field_ptr_ty);
12042 }
12043 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
12044 for (case_srcs, 0..) |*case_src, item_i| {
12045 case_src.* = .{
12046 .base_node_inst = capture_src.base_node_inst,
12047 .offset = .{ .switch_case_item = .{
12048 .switch_node_offset = switch_node_offset,
12049 .case_idx = capture_src.offset.switch_capture.case_idx,
12050 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
12051 } },
12052 };
12053 }
12054
12055 break :resolve sema.resolvePeerTypes(
12056 case_block,
12057 capture_src,
12058 dummy_captures,
12059 .{ .override = case_srcs },
12060 ) catch |err| switch (err) {
12061 error.AnalysisFail => {
12062 const msg = sema.err orelse return error.AnalysisFail;
12063 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
12064 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12065 return error.AnalysisFail;
12066 },
12067 else => |e| return e,
12068 };
12069 };
12070
12071 if (try sema.resolveDefinedValue(case_block, operand_src, operand_ptr)) |op_ptr_val| {
12072 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
12073 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
12074 return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
12075 }
12076
12077 try sema.requireRuntimeBlock(case_block, operand_src, null);
12078 return case_block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);
12079 }
12080
12081 if (try capture_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
12082
12083 if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |operand_val_val| {
12084 if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);
12085 const union_val = ip.indexToKey(operand_val_val.toIntern()).un;
12086 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
12087 const uncoerced: Air.Inst.Ref = .fromIntern(union_val.val);
12088 return sema.coerce(case_block, capture_ty, uncoerced, operand_src);
12165 if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |op_ptr_val| {
12166 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
12167 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
12168 return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
1208912169 }
1209012170
1209112171 try sema.requireRuntimeBlock(case_block, operand_src, null);
12172 return case_block.addStructFieldPtr(loaded_operand, first_field_index, capture_ptr_ty);
12173 }
1209212174
12093 if (same_types) {
12094 return case_block.addStructFieldVal(operand_val, first_field_index, capture_ty);
12095 }
12175 if (try capture_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
1209612176
12097 // We may have to emit a switch block which coerces the operand to the capture type.
12098 // If we can, try to avoid that using in-memory coercions.
12099 const first_non_imc = in_mem: {
12100 for (field_indices, 0..) |field_idx, i| {
12101 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12102 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12103 break :in_mem i;
12104 }
12105 }
12106 // All fields are in-memory coercible to the resolved type!
12107 // Just take the first field and bitcast the result.
12108 const uncoerced = try case_block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
12109 return case_block.addBitCast(capture_ty, uncoerced);
12110 };
12177 if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |operand_val| {
12178 if (operand_val.isUndef(zcu)) return pt.undefRef(capture_ty);
12179 const union_val = ip.indexToKey(operand_val.toIntern()).un;
12180 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
12181 const uncoerced: Air.Inst.Ref = .fromIntern(union_val.val);
12182 return sema.coerce(case_block, capture_ty, uncoerced, operand_src);
12183 }
1211112184
12112 // By-val capture with heterogeneous types which are not all in-memory coercible to
12113 // the resolved capture type. We finally have to fall back to the ugly method.
12185 try sema.requireRuntimeBlock(case_block, operand_src, null);
1211412186
12115 // However, let's first track which operands are in-memory coercible. There may well
12116 // be several, and we can squash all of these cases into the same switch prong using
12117 // a simple bitcast. We'll make this the 'else' prong.
12187 if (same_types) {
12188 return case_block.addStructFieldVal(loaded_operand, first_field_index, capture_ty);
12189 }
1211812190
12119 var in_mem_coercible: std.bit_set.Dynamic = try .initFull(sema.arena, field_indices.len);
12120 in_mem_coercible.unset(first_non_imc);
12121 {
12122 const next = first_non_imc + 1;
12123 for (field_indices[next..], next..) |field_idx, i| {
12124 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12125 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12126 in_mem_coercible.unset(i);
12127 }
12191 // We may have to emit a switch block which coerces the operand to the capture type.
12192 // If we can, try to avoid that using in-memory coercions.
12193 const first_non_imc = in_mem: {
12194 for (field_indices, 0..) |field_idx, i| {
12195 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12196 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12197 break :in_mem i;
1212812198 }
1212912199 }
12200 // All fields are in-memory coercible to the resolved type!
12201 // Just take the first field and bitcast the result.
12202 const uncoerced = try case_block.addStructFieldVal(loaded_operand, first_field_index, first_field_ty);
12203 return case_block.addBitCast(capture_ty, uncoerced);
12204 };
1213012205
12131 const capture_block_inst = try case_block.addInstAsIndex(.{
12132 .tag = .block,
12133 .data = .{
12134 .ty_pl = .{
12135 .ty = .fromType(capture_ty),
12136 .payload = undefined, // updated below
12137 },
12138 },
12139 });
12140
12141 const prong_count = field_indices.len - in_mem_coercible.count();
12206 // By-val capture with heterogeneous types which are not all in-memory coercible to
12207 // the resolved capture type. We finally have to fall back to the ugly method.
1214212208
12143 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
12144 var cases_extra = try std.array_list.Managed(u32).initCapacity(sema.gpa, estimated_extra);
12145 defer cases_extra.deinit();
12209 // However, let's first track which operands are in-memory coercible. There may well
12210 // be several, and we can squash all of these cases into the same switch prong using
12211 // a simple bitcast. We'll make this the 'else' prong.
1214612212
12147 {
12148 // All branch hints are `.none`, so just add zero elems.
12149 comptime assert(@intFromEnum(std.lang.BranchHint.none) == 0);
12150 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
12151 try cases_extra.appendNTimes(0, need_elems);
12213 var in_mem_coercible: std.bit_set.Dynamic = try .initFull(sema.arena, field_indices.len);
12214 in_mem_coercible.unset(first_non_imc);
12215 {
12216 const next = first_non_imc + 1;
12217 for (field_indices[next..], next..) |field_idx, i| {
12218 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12219 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12220 in_mem_coercible.unset(i);
12221 }
1215212222 }
12223 }
1215312224
12154 {
12155 // Non-bitcast cases
12156 var it = in_mem_coercible.iterator(.{ .kind = .unset });
12157 while (it.next()) |idx| {
12158 var coerce_block = case_block.makeSubBlock();
12159 defer coerce_block.instructions.deinit(sema.gpa);
12225 const capture_block_inst = try case_block.addInstAsIndex(.{
12226 .tag = .block,
12227 .data = .{
12228 .ty_pl = .{
12229 .ty = .fromType(capture_ty),
12230 .payload = undefined, // updated below
12231 },
12232 },
12233 });
1216012234
12161 const case_src: LazySrcLoc = .{
12162 .base_node_inst = capture_src.base_node_inst,
12163 .offset = .{ .switch_case_item = .{
12164 .switch_node_offset = switch_node_offset,
12165 .case_idx = capture_src.offset.switch_capture.case_idx,
12166 .item_idx = .{ .kind = .single, .value = @intCast(idx) },
12167 } },
12168 };
12235 const prong_count = field_indices.len - in_mem_coercible.count();
1216912236
12170 const field_idx = field_indices[idx];
12171 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12172 const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);
12173 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
12174 _ = try coerce_block.addBr(capture_block_inst, coerced);
12237 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
12238 var cases_extra = try std.ArrayList(u32).initCapacity(gpa, estimated_extra);
12239 defer cases_extra.deinit(gpa);
1217512240
12176 try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".field_names.len +
12177 1 + // `item`, no ranges
12178 coerce_block.instructions.items.len);
12179 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12180 .items_len = 1,
12181 .ranges_len = 0,
12182 .body_len = @intCast(coerce_block.instructions.items.len),
12183 }));
12184 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item
12185 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
12186 }
12187 }
12188 const else_body_len = len: {
12189 // 'else' prong uses a bitcast
12241 {
12242 // All branch hints are `.none`, so just add zero elems.
12243 comptime assert(@intFromEnum(std.lang.BranchHint.none) == 0);
12244 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
12245 try cases_extra.appendNTimes(gpa, 0, need_elems);
12246 }
12247
12248 {
12249 // Non-bitcast cases
12250 var it = in_mem_coercible.iterator(.{ .kind = .unset });
12251 while (it.next()) |idx| {
1219012252 var coerce_block = case_block.makeSubBlock();
1219112253 defer coerce_block.instructions.deinit(sema.gpa);
1219212254
12193 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
12194 const first_imc_field_idx = field_indices[first_imc_item_idx];
12195 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
12196 const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);
12197 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
12198 _ = try coerce_block.addBr(capture_block_inst, coerced);
12199
12200 try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));
12201 break :len coerce_block.instructions.items.len;
12202 };
12255 const case_src: LazySrcLoc = .{
12256 .base_node_inst = capture_src.base_node_inst,
12257 .offset = .{ .switch_case_item = .{
12258 .switch_node_offset = switch_node_offset,
12259 .case_idx = capture_src.offset.switch_capture.case_idx,
12260 .item_idx = .{ .kind = .single, .value = @intCast(idx) },
12261 } },
12262 };
1220312263
12204 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".field_names.len +
12205 cases_extra.items.len +
12206 @typeInfo(Air.Block).@"struct".field_names.len +
12207 1);
12264 const field_idx = field_indices[idx];
12265 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12266 const uncoerced = try coerce_block.addStructFieldVal(loaded_operand, field_idx, field_ty);
12267 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
12268 _ = try coerce_block.addBr(capture_block_inst, coerced);
1220812269
12209 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
12210 try sema.air_instructions.append(sema.gpa, .{
12211 .tag = .switch_br,
12212 .data = .{
12213 .pl_op = .{
12214 .operand = undefined, // set by switch below
12215 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
12216 .cases_len = @intCast(prong_count),
12217 .else_body_len = @intCast(else_body_len),
12218 }),
12219 },
12220 },
12221 });
12222 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
12223
12224 // Set up block body
12225 switch (operand) {
12226 .simple => |s| {
12227 const air_datas = sema.air_instructions.items(.data);
12228 air_datas[switch_br_inst].pl_op.operand = s.cond;
12229 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12230 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
12231 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12232 },
12233 .loop => {
12234 // The block must first extract the tag from the loaded union.
12235 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
12236 try sema.air_instructions.append(sema.gpa, .{
12237 .tag = .get_union_tag,
12238 .data = .{ .ty_op = .{
12239 .ty = .fromIntern(union_obj.enum_tag_type),
12240 .operand = operand_val,
12241 } },
12242 });
12243 const air_datas = sema.air_instructions.items(.data);
12244 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
12245 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12246 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 2 });
12247 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
12248 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12249 },
12270 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".field_names.len +
12271 1 + // `item`, no ranges
12272 coerce_block.instructions.items.len);
12273 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12274 .items_len = 1,
12275 .ranges_len = 0,
12276 .body_len = @intCast(coerce_block.instructions.items.len),
12277 }));
12278 cases_extra.appendAssumeCapacity(@intFromEnum(item_refs[idx])); // item
12279 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
1225012280 }
12251
12252 return capture_block_inst.toRef();
1225312281 }
12282 const else_body_len = len: {
12283 // 'else' prong uses a bitcast
12284 var coerce_block = case_block.makeSubBlock();
12285 defer coerce_block.instructions.deinit(sema.gpa);
1225412286
12255 if (err_set) {
12256 const case_vals = kind.item_refs;
12257 if (case_vals.len == 1) {
12258 const item_val = sema.resolveValue(case_vals[0]).?;
12259 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
12260 return sema.bitCast(case_block, item_ty, .fromValue(item_val), operand_src, null);
12261 }
12287 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
12288 const first_imc_field_idx = field_indices[first_imc_item_idx];
12289 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
12290 const uncoerced = try coerce_block.addStructFieldVal(loaded_operand, first_imc_field_idx, first_imc_field_ty);
12291 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
12292 _ = try coerce_block.addBr(capture_block_inst, coerced);
1226212293
12263 var names: InferredErrorSet.NameMap = .{};
12264 try names.ensureUnusedCapacity(sema.arena, case_vals.len);
12265 for (case_vals) |err| {
12266 const err_val = sema.resolveValue(err).?;
12267 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});
12268 }
12269 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());
12270 return sema.bitCast(case_block, error_ty, operand_val, operand_src, null);
12271 }
12294 try cases_extra.appendSlice(gpa, @ptrCast(coerce_block.instructions.items));
12295 break :len coerce_block.instructions.items.len;
12296 };
1227212297
12273 // In this case the capture value is just the passed-through value of the
12274 // switch condition. It is comptime-known if there is only one item.
12275 if (capture_by_ref) {
12276 return operand_ptr;
12277 }
12278 switch (kind) {
12279 .inline_ref, .special => unreachable,
12280 .item_refs => |case_vals| {
12281 // If there's only a single item, the capture is comptime-known!
12282 if (case_vals.len == 1) return case_vals[0];
12298 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).@"struct".field_names.len +
12299 cases_extra.items.len +
12300 @typeInfo(Air.Block).@"struct".field_names.len +
12301 1);
12302
12303 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
12304 try sema.air_instructions.append(gpa, .{
12305 .tag = .switch_br,
12306 .data = .{
12307 .pl_op = .{
12308 .operand = undefined, // set by switch below
12309 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
12310 .cases_len = @intCast(prong_count),
12311 .else_body_len = @intCast(else_body_len),
12312 }),
12313 },
12314 },
12315 });
12316 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
12317
12318 // Set up block body
12319 switch (operand) {
12320 .simple => |s| {
12321 const air_datas = sema.air_instructions.items(.data);
12322 air_datas[switch_br_inst].pl_op.operand = s.cond;
12323 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12324 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
12325 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12326 },
12327 .loop => {
12328 // The block must first extract the tag from the loaded union.
12329 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
12330 try sema.air_instructions.append(sema.gpa, .{
12331 .tag = .get_union_tag,
12332 .data = .{ .ty_op = .{
12333 .ty = .fromIntern(union_obj.enum_tag_type),
12334 .operand = loaded_operand,
12335 } },
12336 });
12337 const air_datas = sema.air_instructions.items(.data);
12338 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
12339 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12340 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 2 });
12341 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
12342 sema.air_extra.appendAssumeCapacity(switch_br_inst);
1228312343 },
12284 .has_ranges => {},
1228512344 }
12286 return operand_val;
12345
12346 return capture_block_inst.toRef();
1228712347}
1228812348
1228912349const ResolvedSwitchItem = struct {
test/behavior/switch.zig+33
......@@ -1486,3 +1486,36 @@ test "switch on large types" {
14861486 try S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, 0xFFFF_1234);
14871487 try comptime S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, 0xFFFF_1234);
14881488}
1489
1490test "error captures narrow error sets" {
1491 const S = struct {
1492 fn doTheTest(err: error{ A, B, C, D }) !void {
1493 switch (err) {
1494 error.A, error.B => |e| comptime assert(@TypeOf(e) == error{ A, B }),
1495 else => |e| comptime assert(@TypeOf(e) == error{ C, D }),
1496 }
1497 switch (err) {
1498 inline error.A, error.B => |e| comptime {
1499 if (e == error.A)
1500 assert(@TypeOf(e) == error{A})
1501 else if (e == error.B)
1502 assert(@TypeOf(e) == error{B})
1503 else
1504 unreachable;
1505 },
1506 inline else => |e| comptime {
1507 if (e == error.C)
1508 assert(@TypeOf(e) == error{C})
1509 else if (e == error.D)
1510 assert(@TypeOf(e) == error{D})
1511 else
1512 unreachable;
1513 },
1514 }
1515 }
1516 };
1517
1518 try S.doTheTest(error.B);
1519 try comptime S.doTheTest(error.B);
1520 try comptime S.doTheTest(error.C);
1521}
test/cases/compile_errors/switch_capture_packed_union_tag.zig deleted-15
......@@ -1,15 +0,0 @@
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_invalid_tag_capture.zig created+78
......@@ -0,0 +1,78 @@
1const P = packed union(u8) {
2 a: u8,
3 b: i8,
4};
5export fn entry1(p: P) void {
6 switch (p) {
7 .{ .a = 123 } => |_, tag| _ = tag,
8 else => {},
9 }
10}
11export fn entry2(p: P) void {
12 label: switch (p) {
13 .{ .a = 123 } => |_, tag| _ = tag,
14 else => continue :label .{ .a = 123 },
15 }
16}
17
18const E = enum(u8) { a, b };
19export fn entry3(e: E) void {
20 switch (e) {
21 .a => |_, tag| _ = tag,
22 else => {},
23 }
24}
25export fn entry4(e: E) void {
26 label: switch (e) {
27 .a => |_, tag| _ = tag,
28 else => continue :label .a,
29 }
30}
31
32const Error = error{ MyError, MyOtherError };
33export fn entry5(ok: bool) void {
34 switch (foo(ok)) {
35 error.MyError => |_, tag| _ = tag,
36 else => {},
37 }
38}
39export fn entry6(ok: bool) void {
40 label: switch (foo(ok)) {
41 error.MyError => |_, tag| _ = tag,
42 else => continue :label error.MyError,
43 }
44}
45fn foo(ok: bool) Error {
46 return if (ok) error.MyError else error.MyOtherError;
47}
48
49export fn entry7() void {
50 switch (@as(u0, 0)) {
51 0 => |_, tag| _ = tag,
52 }
53}
54export fn entry8() void {
55 label: switch (@as(u0, 0)) {
56 0 => |_, tag| {
57 _ = tag;
58 continue :label 0;
59 },
60 }
61}
62
63// error
64//
65// :7:30: error: cannot capture tag of packed union
66// :1:18: note: union declared here
67// :1:18: note: consider using a tagged union
68// :13:30: error: cannot capture tag of packed union
69// :1:18: note: union declared here
70// :1:18: note: consider using a tagged union
71// :21:19: error: cannot capture tag of non-union type 'tmp.E'
72// :18:11: note: enum declared here
73// :27:19: error: cannot capture tag of non-union type 'tmp.E'
74// :18:11: note: enum declared here
75// :35:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'
76// :41:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'
77// :51:18: error: cannot capture tag of non-union type 'u0'
78// :56:18: error: cannot capture tag of non-union type 'u0'