authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-05-05 01:15:05+02:00
committergravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-05-28 19:35:48+02:00
log73b760e03361a86c2121b0e8b6e62de98721ee0d
treeaed9c6ee38d8683a2afcf22b9cd3d40cb9cfcf5e
parent3f1dead2fc5922b588fbfb108f421ca957d6934a

Sema: simplify switch capture logic

Previously all switch capture logic was in a single gigantic function and had lots of weird rules around its args depending on each other which were only encoded in doc comments and not actually enforced by the compiler. That's odd since the 'happy path' of a switch capture is actually super simple; it's literally just loading the switch operand inside of the scope of the prong. Error sets introduce a little complexity since the type of an error capture must be narrowed to the set of values that can possibly be captured by their prong, but the real source of complexity here are tagged union captures. First of, they support *two* captures - a payload and a tag capture. The captured payload has to be set up correctly, and payload captures are not guaranteed to have the same type so Sema has to make sure that all possible payloads of a capture are compatible. Instead of throwing all of this logic into one large function with lots of special casing interspersed it is now split into three main paths: - tagged unions (+ a potential tag capture) - errors (including error set narrowing) - everything else This should greatly simplify reasoning about this logic in the future if there's a bug or a change to be made. There's also no more dependencies of args on each other; instead everything is a bit more modular, dependencies are now encoded either as control flow or in the type system. This has additionally lead to the OPV path being able to reuse some more of the capture logic. This commit also fixes inline prongs not narrowing error sets.

4 files changed, 610 insertions(+), 508 deletions(-)

src/Sema.zig+539-493
......@@ -10216,7 +10216,7 @@ fn analyzeSwitchBlock(
1021610216
1021710217 const case_vals = validated_switch.case_vals;
1021810218
10219 const index, const body, const capture, const has_tag_capture, const is_inline, const is_special = find_prong: {
10219 const body, const capture, const has_tag_capture = find_prong: {
1022010220 var case_val_idx: usize = 0;
1022110221 var case_it = zir_switch.iterateCases();
1022210222 var extra_index = zir_switch.end;
......@@ -10239,12 +10239,12 @@ fn analyzeSwitchBlock(
1023910239 }
1024010240 continue;
1024110241 }
10242 break :find_prong .{ case.index, prong_body, prong_info.capture, prong_info.has_tag_capture, prong_info.is_inline, false };
10242 break :find_prong .{ prong_body, prong_info.capture, prong_info.has_tag_capture };
1024310243 }
1024410244 if (has_else) {
1024510245 // This *has* to be checked after iterating all regular cases because
1024610246 // we allow simple noreturn else prongs when switching on error sets!
10247 break :find_prong .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline, true };
10247 break :find_prong .{ else_case.body, else_case.capture, else_case.has_tag_capture };
1024810248 }
1024910249 unreachable; // malformed validated switch
1025010250 };
......@@ -10255,58 +10255,33 @@ fn analyzeSwitchBlock(
1025510255 if (!(err_set and
1025610256 try sema.maybeErrorUnwrap(&case_block, body, cond_ref, operand_src, true)))
1025710257 {
10258 // Set up captures manually to avoid special cases in the main logic.
10259 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
10258 const payload_inst = if (capture != .none) inst: {
1026010259 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
1026110260 const payload_ref: Air.Inst.Ref = payload_ref: {
10262 const item_val: Value = item_val: {
10261 const captured_opv: Value = captured_opv: {
1026310262 if (!tagged_union_originally) {
10264 break :item_val item_opv;
10263 break :captured_opv item_opv;
1026510264 }
1026610265 if (maybe_operand_opv) |operand_opv| {
10267 break :item_val .fromInterned(zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val);
10266 break :captured_opv .fromInterned(zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val);
1026810267 }
1026910268 assert(zir_switch.any_maybe_runtime_capture); // there's a payload capture
10270 const operand_val, const operand_ref = switch (operand) {
10271 .simple => unreachable,
10272 .loop => |l| load_operand: {
10273 const loaded = try sema.analyzeLoad(block, src, l.operand_alloc, src);
10274 if (l.operand_is_ref) {
10275 const by_val = try sema.analyzeLoad(block, src, loaded, src);
10276 break :load_operand .{ by_val, loaded };
10277 } else {
10278 break :load_operand .{ loaded, .none };
10279 }
10280 },
10281 };
10282 const prong_kind: SwitchProngKind = kind: {
10283 if (is_inline) break :kind .{ .inline_ref = .fromValue(item_opv) };
10284 if (is_special) break :kind .special;
10285 break :kind .{ .item_refs = &.{.fromValue(item_opv)} };
10286 };
10287 break :payload_ref try sema.analyzeSwitchPayloadCapture(
10269 const loaded_operand = try sema.analyzeSwitchOperandLoad(&case_block, operand, operand_src, capture == .by_ref);
10270 break :payload_ref try sema.resolveSwitchPayloadCaptureTaggedUnion(
1028810271 &case_block,
10289 operand,
10290 operand_val,
10291 operand_ref,
10292 operand_ty,
10272 loaded_operand,
1029310273 operand_src,
10294 block.src(.{ .switch_capture = .{
10295 .switch_node_offset = src_node_offset,
10296 .case_idx = index,
10297 } }),
10274 operand_ty,
10275 item_opv,
1029810276 capture == .by_ref,
10299 prong_kind,
10300 validated_switch.else_err_ty,
1030110277 );
1030210278 };
1030310279 break :payload_ref switch (capture) {
10304 .by_val => .fromValue(item_val),
10305 .by_ref => try sema.uavRef(item_val),
10280 .by_val => .fromValue(captured_opv),
10281 .by_ref => try sema.uavRef(captured_opv),
1030610282 .none => unreachable,
1030710283 };
1030810284 };
10309 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
1031010285 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
1031110286 break :inst payload_inst;
1031210287 } else undefined;
......@@ -10599,7 +10574,7 @@ fn finishSwitchBr(
1059910574 } }),
1060010575 prong_info.capture,
1060110576 prong_info.has_tag_capture,
10602 .{ .inline_ref = item_ref },
10577 .{ .@"inline" = item_ref },
1060310578 validated_switch.else_err_ty,
1060410579 switch_inst,
1060510580 zir_switch,
......@@ -10689,7 +10664,7 @@ fn finishSwitchBr(
1068910664 } }),
1069010665 prong_info.capture,
1069110666 prong_info.has_tag_capture,
10692 .{ .inline_ref = item_ref },
10667 .{ .@"inline" = item_ref },
1069310668 validated_switch.else_err_ty,
1069410669 switch_inst,
1069510670 zir_switch,
......@@ -10829,7 +10804,7 @@ fn finishSwitchBr(
1082910804 } }),
1083010805 else_case.capture,
1083110806 else_case.has_tag_capture,
10832 .{ .inline_ref = item_ref },
10807 .{ .@"inline" = item_ref },
1083310808 validated_switch.else_err_ty,
1083410809 switch_inst,
1083510810 zir_switch,
......@@ -11269,8 +11244,7 @@ fn validateSwitchBlock(
1126911244 const has_else = zir_switch.else_case != null;
1127011245 const has_under = zir_switch.has_under;
1127111246
11272 var case_vals: std.ArrayList(Air.Inst.Ref) = .empty;
11273 try case_vals.ensureUnusedCapacity(arena, zir_switch.item_infos.len);
11247 var case_vals: std.ArrayList(Air.Inst.Ref) = try .initCapacity(arena, zir_switch.item_infos.len);
1127411248
1127511249 // Duplicate checking variables later also used for `inline else`.
1127611250 var seen_enum_fields: []?LazySrcLoc = &.{};
......@@ -11676,10 +11650,10 @@ fn resolveSwitchBlock(
1167611650 // This prong should be unreachable!
1167711651 return .unreachable_value;
1167811652 }
11679 const prong_kind: SwitchProngKind = kind: {
11680 if (prong_info.is_inline) break :kind .{ .inline_ref = cond_ref };
11681 if (range_refs.len > 0) break :kind .has_ranges;
11682 break :kind .{ .item_refs = item_refs };
11653 const prong_items: SwitchProngItems = prong_items: {
11654 if (prong_info.is_inline) break :prong_items .{ .@"inline" = cond_ref };
11655 if (range_refs.len > 0) break :prong_items .has_ranges;
11656 break :prong_items .{ .item_refs = item_refs };
1168311657 };
1168411658 return sema.resolveSwitchProng(
1168511659 block,
......@@ -11693,7 +11667,7 @@ fn resolveSwitchBlock(
1169311667 } }),
1169411668 prong_info.capture,
1169511669 prong_info.has_tag_capture,
11696 prong_kind,
11670 prong_items,
1169711671 validated_switch.else_err_ty,
1169811672 merges,
1169911673 switch_inst,
......@@ -11707,8 +11681,8 @@ fn resolveSwitchBlock(
1170711681 if ((try sema.compareAll(cond_val, .gte, first_val, item_ty)) and
1170811682 (try sema.compareAll(cond_val, .lte, last_val, item_ty)))
1170911683 {
11710 const prong_kind: SwitchProngKind = if (prong_info.is_inline)
11711 .{ .inline_ref = cond_ref }
11684 const prong_items: SwitchProngItems = if (prong_info.is_inline)
11685 .{ .@"inline" = cond_ref }
1171211686 else
1171311687 .has_ranges;
1171411688 return sema.resolveSwitchProng(
......@@ -11723,7 +11697,7 @@ fn resolveSwitchBlock(
1172311697 } }),
1172411698 prong_info.capture,
1172511699 prong_info.has_tag_capture,
11726 prong_kind,
11700 prong_items,
1172711701 validated_switch.else_err_ty,
1172811702 merges,
1172911703 switch_inst,
......@@ -11742,8 +11716,8 @@ fn resolveSwitchBlock(
1174211716
1174311717 if (else_is_named_only and item_ty.enumTagFieldIndex(cond_val, zcu) != null) {
1174411718 assert(item_ty.isNonexhaustiveEnum(zcu));
11745 const prong_kind: SwitchProngKind = if (else_case.is_inline)
11746 .{ .inline_ref = cond_ref }
11719 const prong_items: SwitchProngItems = if (else_case.is_inline)
11720 .{ .@"inline" = cond_ref }
1174711721 else
1174811722 .special;
1174911723 return sema.resolveSwitchProng(
......@@ -11758,7 +11732,7 @@ fn resolveSwitchBlock(
1175811732 } }),
1175911733 else_case.capture,
1176011734 else_case.has_tag_capture,
11761 prong_kind,
11735 prong_items,
1176211736 validated_switch.else_err_ty,
1176311737 merges,
1176411738 switch_inst,
......@@ -11782,8 +11756,8 @@ fn resolveSwitchBlock(
1178211756 return .unreachable_value;
1178311757 }
1178411758 }
11785 const prong_kind: SwitchProngKind = if (is_inline)
11786 .{ .inline_ref = cond_ref }
11759 const prong_items: SwitchProngItems = if (is_inline)
11760 .{ .@"inline" = cond_ref }
1178711761 else
1178811762 .special;
1178911763 return sema.resolveSwitchProng(
......@@ -11798,7 +11772,7 @@ fn resolveSwitchBlock(
1179811772 } }),
1179911773 capture,
1180011774 has_tag_capture,
11801 prong_kind,
11775 prong_items,
1180211776 validated_switch.else_err_ty,
1180311777 merges,
1180411778 switch_inst,
......@@ -11834,14 +11808,59 @@ const SwitchOperand = union(enum) {
1183411808 },
1183511809};
1183611810
11837const SwitchProngKind = union(enum) {
11838 /// Prefer populating this field over the others, if possible.
11839 inline_ref: Air.Inst.Ref,
11811fn analyzeSwitchOperandLoad(
11812 sema: *Sema,
11813 block: *Block,
11814 operand: SwitchOperand,
11815 operand_src: LazySrcLoc,
11816 by_ref: bool,
11817) CompileError!Air.Inst.Ref {
11818 switch (operand) {
11819 .simple => |s| {
11820 if (by_ref) {
11821 assert(s.by_ref != .none);
11822 return s.by_ref;
11823 } else {
11824 return s.by_val;
11825 }
11826 },
11827 .loop => |l| {
11828 const loaded = try sema.analyzeLoad(block, operand_src, l.operand_alloc, operand_src);
11829 assert(loaded != .none); // there are no captures, so no need to load the switch operand
11830 if (by_ref) {
11831 assert(l.operand_is_ref);
11832 return loaded;
11833 }
11834 return if (l.operand_is_ref)
11835 try sema.analyzeLoad(block, operand_src, loaded, operand_src)
11836 else
11837 loaded;
11838 },
11839 }
11840}
11841
11842const SwitchProngItems = union(enum) {
11843 @"inline": Air.Inst.Ref,
1184011844 item_refs: []const Air.Inst.Ref,
1184111845 has_ranges,
1184211846 special,
1184311847};
1184411848
11849/// A switch capture is comptime-known if it is `inline` and/or it is a by-value
11850/// capture of a prong with a single item.
11851fn resolveSwitchCaptureFromProngItems(
11852 sema: *Sema,
11853 prong_items: SwitchProngItems,
11854 by_ref: bool,
11855) ?Value {
11856 const ref: Air.Inst.Ref = switch (prong_items) {
11857 .@"inline" => |ref| ref,
11858 .item_refs => |refs| if (refs.len == 1 and !by_ref) refs[0] else return null,
11859 .has_ranges, .special => return null,
11860 };
11861 return sema.resolveValue(ref).?;
11862}
11863
1184511864/// Resolve a switch prong which is determined at comptime to have no peers.
1184611865/// Sets up captures as needed. Uses `analyzeBodyRuntimeBreak`.
1184711866fn resolveSwitchProng(
......@@ -11855,7 +11874,7 @@ fn resolveSwitchProng(
1185511874 capture_src: LazySrcLoc,
1185611875 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
1185711876 has_tag_capture: bool,
11858 kind: SwitchProngKind,
11877 prong_items: SwitchProngItems,
1185911878 else_err_ty: ?Type,
1186011879 merges: *Block.Merges,
1186111880 switch_inst: Zir.Inst.Index,
......@@ -11870,36 +11889,28 @@ fn resolveSwitchProng(
1187011889 const parent_hint = sema.branch_hint;
1187111890 defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
1187211891
11873 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
11892 const analyzed_captures = try sema.analyzeSwitchCaptures(
11893 child_block,
11894 operand,
11895 operand_src,
11896 sema.typeOf(operand.simple.by_val),
11897 capture_src,
11898 capture,
11899 has_tag_capture,
11900 prong_items,
11901 else_err_ty,
11902 );
11903
11904 const payload_inst = if (capture != .none) inst: {
1187411905 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
11875 const payload_ref = try sema.analyzeSwitchPayloadCapture(
11876 child_block,
11877 operand,
11878 operand.simple.by_val,
11879 operand.simple.by_ref,
11880 sema.typeOf(operand.simple.by_val),
11881 operand_src,
11882 capture_src,
11883 capture == .by_ref,
11884 kind,
11885 else_err_ty,
11886 );
11887 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
11888 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
11906 sema.inst_map.putAssumeCapacity(payload_inst, analyzed_captures.payload_ref);
1188911907 break :inst payload_inst;
1189011908 } else undefined;
1189111909 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1189211910
1189311911 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
1189411912 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
11895 const tag_ref = try sema.analyzeSwitchTagCapture(
11896 child_block,
11897 operand.simple.by_val,
11898 sema.typeOf(operand.simple.by_val),
11899 capture_src,
11900 kind,
11901 );
11902 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
11913 sema.inst_map.putAssumeCapacity(tag_inst, analyzed_captures.tag_ref);
1190311914 break :inst tag_inst;
1190411915 } else undefined;
1190511916 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
......@@ -11945,7 +11956,7 @@ fn analyzeSwitchProng(
1194511956 capture_src: LazySrcLoc,
1194611957 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
1194711958 has_tag_capture: bool,
11948 kind: SwitchProngKind,
11959 prong_items: SwitchProngItems,
1194911960 else_err_ty: ?Type,
1195011961 switch_inst: Zir.Inst.Index,
1195111962 zir_switch: *const Zir.UnwrappedSwitchBlock,
......@@ -11966,77 +11977,28 @@ fn analyzeSwitchProng(
1196611977 }
1196711978 }
1196811979
11969 const need_load: bool = need_load: {
11970 if (capture == .none and !has_tag_capture) {
11971 // No need to load the operand for this prong!
11972 break :need_load false;
11973 }
11974 if (capture != .none and operand_ty.zigTypeTag(zcu) == .@"union" and
11975 operand_ty.containerLayout(zcu) != .@"packed")
11976 {
11977 // Non-OPV tagged union payload captures are always runtime-known.
11978 break :need_load true;
11979 }
11980 if (kind == .inline_ref) {
11981 // `inline_ref` *is* the (comptime-known) capture.
11982 break :need_load false;
11983 }
11984 assert(zir_switch.any_maybe_runtime_capture); // should have caught everything else by now
11985 if (capture != .by_ref and
11986 kind == .item_refs and kind.item_refs.len == 1)
11987 {
11988 // Capture is comptime-known because it's the only prong item
11989 break :need_load false;
11990 }
11991 break :need_load true;
11992 };
11993
11994 const operand_val: Air.Inst.Ref, const operand_ptr: Air.Inst.Ref = load_operand: {
11995 if (!need_load) break :load_operand .{ .none, .none };
11996 switch (operand) {
11997 .simple => |s| break :load_operand .{ s.by_val, s.by_ref },
11998 .loop => |l| {
11999 const loaded = try sema.analyzeLoad(case_block, operand_src, l.operand_alloc, operand_src);
12000 if (l.operand_is_ref) {
12001 const by_val = try sema.analyzeLoad(case_block, operand_src, loaded, operand_src);
12002 break :load_operand .{ by_val, loaded };
12003 } else {
12004 break :load_operand .{ loaded, .none };
12005 }
12006 },
12007 }
12008 };
11980 const analyzed_captures = try sema.analyzeSwitchCaptures(
11981 case_block,
11982 operand,
11983 operand_src,
11984 operand_ty,
11985 capture_src,
11986 capture,
11987 has_tag_capture,
11988 prong_items,
11989 else_err_ty,
11990 );
1200911991
12010 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
11992 const payload_inst = if (capture != .none) inst: {
1201111993 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
12012 const payload_ref = try sema.analyzeSwitchPayloadCapture(
12013 case_block,
12014 operand,
12015 operand_val,
12016 operand_ptr,
12017 operand_ty,
12018 operand_src,
12019 capture_src,
12020 capture == .by_ref,
12021 kind,
12022 else_err_ty,
12023 );
12024 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
12025 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
11994 sema.inst_map.putAssumeCapacity(payload_inst, analyzed_captures.payload_ref);
1202611995 break :inst payload_inst;
1202711996 } else undefined;
1202811997 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1202911998
1203011999 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
1203112000 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
12032 const tag_ref = try sema.analyzeSwitchTagCapture(
12033 case_block,
12034 operand_val,
12035 operand_ty,
12036 capture_src,
12037 kind,
12038 );
12039 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
12001 sema.inst_map.putAssumeCapacity(tag_inst, analyzed_captures.tag_ref);
1204012002 break :inst tag_inst;
1204112003 } else undefined;
1204212004 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
......@@ -12047,157 +12009,314 @@ fn analyzeSwitchProng(
1204712009 return sema.analyzeBodyRuntimeBreak(case_block, prong_body);
1204812010}
1204912011
12050fn analyzeSwitchTagCapture(
12012fn analyzeSwitchCaptures(
1205112013 sema: *Sema,
1205212014 case_block: *Block,
12053 /// May be `none` if this is an inline capture or if `kind.item_refs.len == 1`.
12054 operand_val: Air.Inst.Ref,
12015 operand: SwitchOperand,
12016 operand_src: LazySrcLoc,
1205512017 operand_ty: Type,
1205612018 capture_src: LazySrcLoc,
12057 kind: SwitchProngKind,
12058) CompileError!Air.Inst.Ref {
12019 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
12020 has_tag_capture: bool,
12021 prong_items: SwitchProngItems,
12022 else_err_ty: ?Type,
12023) CompileError!struct {
12024 payload_ref: Air.Inst.Ref,
12025 tag_ref: Air.Inst.Ref,
12026} {
1205912027 const pt = sema.pt;
1206012028 const zcu = pt.zcu;
1206112029
12062 const tag_capture_src: LazySrcLoc = .{
12063 .base_node_inst = capture_src.base_node_inst,
12064 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
12030 if (operand_ty.zigTypeTag(zcu) == .@"union" and
12031 operand_ty.containerLayout(zcu) != .@"packed")
12032 {
12033 if (capture == .none) {
12034 const tag_ref: Air.Inst.Ref = tag_ref: {
12035 if (!has_tag_capture) break :tag_ref .none;
12036 if (sema.resolveSwitchCaptureFromProngItems(prong_items, false)) |tag_val| {
12037 break :tag_ref .fromValue(tag_val);
12038 }
12039 const loaded_operand = try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, false);
12040 break :tag_ref try sema.unionToTag(case_block, loaded_operand);
12041 };
12042 return .{ .payload_ref = .none, .tag_ref = tag_ref };
12043 }
12044
12045 // We always have to load the operand for tagged union payload captures
12046 // since we can't derive the payload value from the tag (except for OPV
12047 // types, for which the load is always basically a noop anyway).
12048
12049 const loaded_operand = try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, capture == .by_ref);
12050
12051 if (sema.resolveSwitchCaptureFromProngItems(prong_items, capture == .by_ref)) |tag_val| {
12052 const payload_ref = try sema.resolveSwitchPayloadCaptureTaggedUnion(
12053 case_block,
12054 loaded_operand,
12055 operand_src,
12056 operand_ty,
12057 tag_val,
12058 capture == .by_ref,
12059 );
12060 const tag_ref: Air.Inst.Ref = if (has_tag_capture) .fromValue(tag_val) else .none;
12061 return .{ .payload_ref = payload_ref, .tag_ref = tag_ref };
12062 }
12063
12064 const payload_ref = try sema.analyzeSwitchPayloadCaptureTaggedUnion(
12065 case_block,
12066 operand,
12067 loaded_operand,
12068 operand_src,
12069 operand_ty,
12070 capture == .by_ref,
12071 capture_src,
12072 prong_items,
12073 );
12074
12075 const tag_ref: Air.Inst.Ref = tag_ref: {
12076 if (!has_tag_capture) break :tag_ref .none;
12077 const operand_val = switch (capture) {
12078 .none => unreachable, // handled above
12079 .by_val => loaded_operand,
12080 .by_ref => try sema.analyzeLoad(case_block, operand_src, loaded_operand, operand_src),
12081 };
12082 break :tag_ref try sema.unionToTag(case_block, operand_val);
12083 };
12084
12085 assert(!sema.typeOf(payload_ref).isNoReturn(zcu));
12086 return .{ .payload_ref = payload_ref, .tag_ref = tag_ref };
12087 }
12088
12089 const payload_ref: Air.Inst.Ref = payload_ref: {
12090 if (capture == .none) break :payload_ref .none;
12091
12092 if (operand_ty.zigTypeTag(zcu) == .error_set) {
12093 // Error captures need to have their type narrowed!
12094
12095 if (capture == .by_ref) {
12096 return sema.fail(
12097 case_block,
12098 capture_src,
12099 "error set cannot be captured by reference",
12100 .{},
12101 );
12102 }
12103 assert(capture == .by_val);
12104
12105 if (sema.resolveSwitchCaptureFromProngItems(prong_items, false)) |err_val| {
12106 const err_name = err_val.getErrorName(zcu).unwrap().?;
12107 break :payload_ref .fromIntern((try pt.intern(.{ .err = .{
12108 .ty = (try pt.singleErrorSetType(err_name)).toIntern(),
12109 .name = err_name,
12110 } })));
12111 }
12112
12113 const loaded_operand = try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, false);
12114
12115 switch (prong_items) {
12116 .@"inline" => unreachable, // handled above
12117 .has_ranges => unreachable, // not possible for error set
12118 .special => {
12119 if (else_err_ty) |err_ty| {
12120 break :payload_ref try sema.bitCast(case_block, err_ty, loaded_operand, operand_src, null);
12121 } else {
12122 try sema.analyzeUnreachable(case_block, operand_src, false);
12123 break :payload_ref .unreachable_value;
12124 }
12125 },
12126 .item_refs => |item_refs| {
12127 var names: InferredErrorSet.NameMap = .{};
12128 try names.ensureUnusedCapacity(sema.arena, item_refs.len);
12129 for (item_refs) |item_ref| {
12130 const item_val = sema.resolveValue(item_ref).?;
12131 names.putAssumeCapacityNoClobber(item_val.getErrorName(zcu).unwrap().?, {});
12132 }
12133 const narrowed_ty = try pt.errorSetFromUnsortedNames(names.keys());
12134 break :payload_ref try sema.bitCast(case_block, narrowed_ty, loaded_operand, operand_src, null);
12135 },
12136 }
12137 }
12138
12139 // We try to make the capture comptime-known based on `prong_items` first:
12140
12141 if (sema.resolveSwitchCaptureFromProngItems(prong_items, capture == .by_ref)) |item_val| {
12142 break :payload_ref switch (capture) {
12143 .none => unreachable, // handled above
12144 .by_val => .fromValue(item_val),
12145 .by_ref => try sema.uavRef(item_val),
12146 };
12147 }
12148
12149 // Otherwise the capture value is just the passed-through value of the
12150 // switch condition (which we might have to load first).
12151
12152 break :payload_ref try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, capture == .by_ref);
1206512153 };
1206612154
12067 if (operand_ty.zigTypeTag(zcu) != .@"union") {
12068 return sema.fail(case_block, tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
12069 operand_ty.fmt(pt),
12155 if (has_tag_capture) {
12156 const tag_capture_src: LazySrcLoc = .{
12157 .base_node_inst = capture_src.base_node_inst,
12158 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
12159 };
12160 if (operand_ty.zigTypeTag(zcu) == .@"union") {
12161 assert(operand_ty.containerLayout(zcu) == .@"packed");
12162 return sema.failWithOwnedErrorMsg(case_block, msg: {
12163 const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of packed union", .{});
12164 errdefer msg.destroy(sema.gpa);
12165 try sema.addDeclaredHereNote(msg, operand_ty);
12166 if (operand_ty.srcLocOrNull(zcu)) |ty_src| {
12167 try sema.errNote(ty_src, msg, "consider using a tagged union", .{});
12168 }
12169 break :msg msg;
12170 });
12171 }
12172 return sema.failWithOwnedErrorMsg(case_block, msg: {
12173 const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
12174 operand_ty.fmt(pt),
12175 });
12176 errdefer msg.destroy(sema.gpa);
12177 try sema.addDeclaredHereNote(msg, operand_ty);
12178 break :msg msg;
1207012179 });
1207112180 }
12072 if (operand_ty.containerLayout(zcu) == .@"packed") {
12073 return sema.fail(case_block, tag_capture_src, "cannot capture tag of packed union", .{});
12074 }
12075 switch (kind) {
12076 .has_ranges => unreachable,
12077 .inline_ref => |ref| return ref,
12078 .item_refs => |refs| if (refs.len == 1) return refs[0],
12079 .special => {},
12080 }
12081 return sema.unionToTag(case_block, operand_val);
12181
12182 return .{ .payload_ref = payload_ref, .tag_ref = .none };
1208212183}
1208312184
12084fn analyzeSwitchPayloadCapture(
12185fn resolveSwitchPayloadCaptureTaggedUnion(
1208512186 sema: *Sema,
1208612187 case_block: *Block,
12087 operand: SwitchOperand,
12088 /// Always has to be not-`none` if this is a tagged union payload capture.
12089 /// For non-tagged-union captures, this may be `none` if this is an inline
12090 /// capture or if `kind.item_refs.len == 1` and capture is by val.
12091 operand_val: Air.Inst.Ref,
12092 /// May be `none` if `capture_by_ref` is `false` or if `operand_val` is also `none`.
12093 operand_ptr: Air.Inst.Ref,
12188 loaded_operand: Air.Inst.Ref,
12189 operand_src: LazySrcLoc,
1209412190 operand_ty: Type,
12191 tag_val: Value,
12192 capture_by_ref: bool,
12193) CompileError!Air.Inst.Ref {
12194 const pt = sema.pt;
12195 const zcu = pt.zcu;
12196 const ip = &zcu.intern_pool;
12197
12198 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(tag_val, zcu).?);
12199 const union_obj = zcu.typeToUnion(operand_ty).?;
12200 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
12201 const payload_ref: Air.Inst.Ref = payload_ref: {
12202 if (capture_by_ref) {
12203 const operand_ptr_info = sema.typeOf(loaded_operand).ptrInfo(zcu);
12204 const ptr_field_ty = try pt.ptrType(.{
12205 .child = field_ty.toIntern(),
12206 .flags = .{
12207 .is_const = operand_ptr_info.flags.is_const,
12208 .is_volatile = operand_ptr_info.flags.is_volatile,
12209 .address_space = operand_ptr_info.flags.address_space,
12210 },
12211 });
12212 break :payload_ref try case_block.addStructFieldPtr(loaded_operand, field_index, ptr_field_ty);
12213 }
12214 if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |union_val| {
12215 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
12216 break :payload_ref .fromIntern(tag_and_val.val);
12217 }
12218 if (try field_ty.onePossibleValue(pt)) |opv| break :payload_ref .fromValue(opv);
12219 break :payload_ref try case_block.addStructFieldVal(loaded_operand, field_index, field_ty);
12220 };
12221 assert(!sema.typeOf(payload_ref).isNoReturn(zcu));
12222 return payload_ref;
12223}
12224
12225fn analyzeSwitchPayloadCaptureTaggedUnion(
12226 sema: *Sema,
12227 case_block: *Block,
12228 operand: SwitchOperand,
12229 loaded_operand: Air.Inst.Ref,
1209512230 operand_src: LazySrcLoc,
12096 capture_src: LazySrcLoc,
12231 operand_ty: Type,
1209712232 capture_by_ref: bool,
12098 kind: SwitchProngKind,
12099 else_err_ty: ?Type,
12233 capture_src: LazySrcLoc,
12234 prong_items: SwitchProngItems,
1210012235) CompileError!Air.Inst.Ref {
1210112236 const pt = sema.pt;
1210212237 const zcu = pt.zcu;
1210312238 const ip = &zcu.intern_pool;
12239 const gpa = sema.gpa;
12240
12241 const item_refs: []const Air.Inst.Ref = switch (prong_items) {
12242 .@"inline" => unreachable, // handled above
12243 .has_ranges => unreachable, // not possible for tagged union
12244 .special => return loaded_operand,
12245 .item_refs => |item_refs| item_refs,
12246 };
1210412247
1210512248 const switch_node_offset = operand_src.offset.node_offset_switch_operand;
1210612249
12107 const tagged_union_originally = operand_ty.zigTypeTag(zcu) == .@"union" and
12108 operand_ty.containerLayout(zcu) != .@"packed";
12109 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
12250 const union_obj = zcu.typeToUnion(operand_ty).?;
1211012251
12111 if (err_set and capture_by_ref) {
12112 return sema.fail(
12113 case_block,
12114 capture_src,
12115 "error set cannot be captured by reference",
12116 .{},
12117 );
12118 }
12252 const first_item_val = sema.resolveValue(item_refs[0]).?;
12253 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;
12254 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);
1211912255
12120 if (kind == .inline_ref) {
12121 const item_val = sema.resolveValue(kind.inline_ref).?;
12122 if (tagged_union_originally) {
12123 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);
12124 const union_obj = zcu.typeToUnion(operand_ty).?;
12125 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
12126 if (capture_by_ref) {
12127 const operand_ptr_info = sema.typeOf(operand_ptr).ptrInfo(zcu);
12128 const ptr_field_ty = try pt.ptrType(.{
12129 .child = field_ty.toIntern(),
12130 .flags = .{
12131 .is_const = operand_ptr_info.flags.is_const,
12132 .is_volatile = operand_ptr_info.flags.is_volatile,
12133 .address_space = operand_ptr_info.flags.address_space,
12134 },
12135 });
12136 return case_block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);
12137 } else {
12138 if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |union_val| {
12139 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
12140 return .fromIntern(tag_and_val.val);
12141 }
12142 if (try field_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
12143 return case_block.addStructFieldVal(operand_val, field_index, field_ty);
12144 }
12145 } else if (capture_by_ref) {
12146 return sema.uavRef(item_val);
12147 } else {
12148 return kind.inline_ref;
12149 }
12150 }
12151
12152 if (kind == .special) {
12153 if (err_set) {
12154 if (else_err_ty) |err_ty| {
12155 return sema.bitCast(case_block, err_ty, operand_val, operand_src, null);
12156 } else {
12157 try sema.analyzeUnreachable(case_block, operand_src, false);
12158 return .unreachable_value;
12159 }
12160 }
12161 if (capture_by_ref) {
12162 return operand_ptr;
12163 }
12164 return operand_val;
12256 const field_indices = try sema.arena.alloc(u32, item_refs.len);
12257 for (item_refs, field_indices) |item_ref, *field_idx| {
12258 const item_val = sema.resolveValue(item_ref).?;
12259 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
1216512260 }
1216612261
12167 if (tagged_union_originally) {
12168 const case_vals = kind.item_refs;
12169
12170 const union_obj = zcu.typeToUnion(operand_ty).?;
12171 const first_item_val = sema.resolveValue(case_vals[0]).?;
12262 // Fast path: if all the operands are the same type already, we don't need to hit
12263 // PTR! This will also allow us to emit simpler code.
12264 const same_types = for (field_indices[1..]) |field_idx| {
12265 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12266 if (!field_ty.eql(first_field_ty, zcu)) break false;
12267 } else true;
1217212268
12173 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;
12174 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);
12269 const capture_ty: Type = capture_ty: {
12270 if (same_types) break :capture_ty first_field_ty;
12271 // We need values to run PTR on, so make a bunch of undef constants.
12272 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, item_refs.len);
12273 for (dummy_captures, field_indices) |*dummy, field_idx| {
12274 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12275 dummy.* = try pt.undefRef(field_ty);
12276 }
1217512277
12176 const field_indices = try sema.arena.alloc(u32, case_vals.len);
12177 for (case_vals, field_indices) |item, *field_idx| {
12178 const item_val = sema.resolveValue(item).?;
12179 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
12278 const item_srcs = try sema.arena.alloc(?LazySrcLoc, item_refs.len);
12279 for (item_srcs, 0..) |*item_src, item_i| {
12280 item_src.* = .{
12281 .base_node_inst = capture_src.base_node_inst,
12282 .offset = .{ .switch_case_item = .{
12283 .switch_node_offset = switch_node_offset,
12284 .case_idx = capture_src.offset.switch_capture.case_idx,
12285 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
12286 } },
12287 };
1218012288 }
1218112289
12182 // Fast path: if all the operands are the same type already, we don't need to hit
12183 // PTR! This will also allow us to emit simpler code.
12184 const same_types = for (field_indices[1..]) |field_idx| {
12185 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12186 if (!field_ty.eql(first_field_ty, zcu)) break false;
12187 } else true;
12290 break :capture_ty sema.resolvePeerTypes(
12291 case_block,
12292 capture_src,
12293 dummy_captures,
12294 .{ .override = item_srcs },
12295 ) catch |err| switch (err) {
12296 error.AnalysisFail => {
12297 const msg = sema.err orelse return error.AnalysisFail;
12298 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12299 return error.AnalysisFail;
12300 },
12301 else => |e| return e,
12302 };
12303 };
1218812304
12189 const capture_ty: Type = capture_ty: {
12190 if (same_types) break :capture_ty first_field_ty;
12305 // By-reference captures have some further restrictions which make them easier to emit
12306 if (capture_by_ref) {
12307 const operand_ptr_ty = sema.typeOf(loaded_operand);
12308 const capture_ptr_ty = resolve: {
12309 // By-ref captures of hetereogeneous types are only allowed if all field
12310 // pointer types are peer resolvable to each other.
1219112311 // We need values to run PTR on, so make a bunch of undef constants.
12192 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
12193 for (dummy_captures, field_indices) |*dummy, field_idx| {
12194 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12195 dummy.* = try pt.undefRef(field_ty);
12196 }
12197
12198 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
12199 for (case_srcs, 0..) |*case_src, item_i| {
12200 case_src.* = .{
12312 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, item_refs.len);
12313 for (field_indices, dummy_captures) |field_index, *dummy| {
12314 const field_ptr_ty = try operand_ptr_ty.fieldPtrType(field_index, pt);
12315 dummy.* = try pt.undefRef(field_ptr_ty);
12316 }
12317 const item_srcs = try sema.arena.alloc(?LazySrcLoc, item_refs.len);
12318 for (item_srcs, 0..) |*item_src, item_i| {
12319 item_src.* = .{
1220112320 .base_node_inst = capture_src.base_node_inst,
1220212321 .offset = .{ .switch_case_item = .{
1220312322 .switch_node_offset = switch_node_offset,
......@@ -12207,14 +12326,15 @@ fn analyzeSwitchPayloadCapture(
1220712326 };
1220812327 }
1220912328
12210 break :capture_ty sema.resolvePeerTypes(
12329 break :resolve sema.resolvePeerTypes(
1221112330 case_block,
1221212331 capture_src,
1221312332 dummy_captures,
12214 .{ .override = case_srcs },
12333 .{ .override = item_srcs },
1221512334 ) catch |err| switch (err) {
1221612335 error.AnalysisFail => {
1221712336 const msg = sema.err orelse return error.AnalysisFail;
12337 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
1221812338 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
1221912339 return error.AnalysisFail;
1222012340 },
......@@ -12222,262 +12342,188 @@ fn analyzeSwitchPayloadCapture(
1222212342 };
1222312343 };
1222412344
12225 // By-reference captures have some further restrictions which make them easier to emit
12226 if (capture_by_ref) {
12227 const operand_ptr_ty = sema.typeOf(operand_ptr);
12228 const capture_ptr_ty = resolve: {
12229 // By-ref captures of hetereogeneous types are only allowed if all field
12230 // pointer types are peer resolvable to each other.
12231 // We need values to run PTR on, so make a bunch of undef constants.
12232 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
12233 for (field_indices, dummy_captures) |field_index, *dummy| {
12234 const field_ptr_ty = try operand_ptr_ty.fieldPtrType(field_index, pt);
12235 dummy.* = try pt.undefRef(field_ptr_ty);
12236 }
12237 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
12238 for (case_srcs, 0..) |*case_src, item_i| {
12239 case_src.* = .{
12240 .base_node_inst = capture_src.base_node_inst,
12241 .offset = .{ .switch_case_item = .{
12242 .switch_node_offset = switch_node_offset,
12243 .case_idx = capture_src.offset.switch_capture.case_idx,
12244 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
12245 } },
12246 };
12247 }
12248
12249 break :resolve sema.resolvePeerTypes(
12250 case_block,
12251 capture_src,
12252 dummy_captures,
12253 .{ .override = case_srcs },
12254 ) catch |err| switch (err) {
12255 error.AnalysisFail => {
12256 const msg = sema.err orelse return error.AnalysisFail;
12257 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
12258 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12259 return error.AnalysisFail;
12260 },
12261 else => |e| return e,
12262 };
12263 };
12264
12265 if (try sema.resolveDefinedValue(case_block, operand_src, operand_ptr)) |op_ptr_val| {
12266 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
12267 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
12268 return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
12269 }
12270
12271 try sema.requireRuntimeBlock(case_block, operand_src, null);
12272 return case_block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);
12273 }
12274
12275 if (try capture_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
12276
12277 if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |operand_val_val| {
12278 if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);
12279 const union_val = ip.indexToKey(operand_val_val.toIntern()).un;
12280 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
12281 const uncoerced: Air.Inst.Ref = .fromIntern(union_val.val);
12282 return sema.coerce(case_block, capture_ty, uncoerced, operand_src);
12345 if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |op_ptr_val| {
12346 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
12347 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
12348 return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
1228312349 }
1228412350
1228512351 try sema.requireRuntimeBlock(case_block, operand_src, null);
12352 return case_block.addStructFieldPtr(loaded_operand, first_field_index, capture_ptr_ty);
12353 }
1228612354
12287 if (same_types) {
12288 return case_block.addStructFieldVal(operand_val, first_field_index, capture_ty);
12289 }
12355 if (try capture_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
1229012356
12291 // We may have to emit a switch block which coerces the operand to the capture type.
12292 // If we can, try to avoid that using in-memory coercions.
12293 const first_non_imc = in_mem: {
12294 for (field_indices, 0..) |field_idx, i| {
12295 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12296 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12297 break :in_mem i;
12298 }
12299 }
12300 // All fields are in-memory coercible to the resolved type!
12301 // Just take the first field and bitcast the result.
12302 const uncoerced = try case_block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
12303 return case_block.addBitCast(capture_ty, uncoerced);
12304 };
12357 if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |operand_val| {
12358 if (operand_val.isUndef(zcu)) return pt.undefRef(capture_ty);
12359 const union_val = ip.indexToKey(operand_val.toIntern()).un;
12360 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
12361 const uncoerced: Air.Inst.Ref = .fromIntern(union_val.val);
12362 return sema.coerce(case_block, capture_ty, uncoerced, operand_src);
12363 }
1230512364
12306 // By-val capture with heterogeneous types which are not all in-memory coercible to
12307 // the resolved capture type. We finally have to fall back to the ugly method.
12365 try sema.requireRuntimeBlock(case_block, operand_src, null);
1230812366
12309 // However, let's first track which operands are in-memory coercible. There may well
12310 // be several, and we can squash all of these cases into the same switch prong using
12311 // a simple bitcast. We'll make this the 'else' prong.
12367 if (same_types) {
12368 return case_block.addStructFieldVal(loaded_operand, first_field_index, capture_ty);
12369 }
1231212370
12313 var in_mem_coercible: std.bit_set.Dynamic = try .initFull(sema.arena, field_indices.len);
12314 in_mem_coercible.unset(first_non_imc);
12315 {
12316 const next = first_non_imc + 1;
12317 for (field_indices[next..], next..) |field_idx, i| {
12318 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12319 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12320 in_mem_coercible.unset(i);
12321 }
12371 // We may have to emit a switch block which coerces the operand to the capture type.
12372 // If we can, try to avoid that using in-memory coercions.
12373 const first_non_imc = in_mem: {
12374 for (field_indices, 0..) |field_idx, i| {
12375 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12376 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12377 break :in_mem i;
1232212378 }
1232312379 }
12380 // All fields are in-memory coercible to the resolved type!
12381 // Just take the first field and bitcast the result.
12382 const uncoerced = try case_block.addStructFieldVal(loaded_operand, first_field_index, first_field_ty);
12383 return case_block.addBitCast(capture_ty, uncoerced);
12384 };
1232412385
12325 const capture_block_inst = try case_block.addInstAsIndex(.{
12326 .tag = .block,
12327 .data = .{
12328 .ty_pl = .{
12329 .ty = .fromType(capture_ty),
12330 .payload = undefined, // updated below
12331 },
12332 },
12333 });
12334
12335 const prong_count = field_indices.len - in_mem_coercible.count();
12386 // By-val capture with heterogeneous types which are not all in-memory coercible to
12387 // the resolved capture type. We finally have to fall back to the ugly method.
1233612388
12337 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
12338 var cases_extra = try std.array_list.Managed(u32).initCapacity(sema.gpa, estimated_extra);
12339 defer cases_extra.deinit();
12389 // However, let's first track which operands are in-memory coercible. There may well
12390 // be several, and we can squash all of these cases into the same switch prong using
12391 // a simple bitcast. We'll make this the 'else' prong.
1234012392
12341 {
12342 // All branch hints are `.none`, so just add zero elems.
12343 comptime assert(@intFromEnum(std.lang.BranchHint.none) == 0);
12344 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
12345 try cases_extra.appendNTimes(0, need_elems);
12393 var in_mem_coercible: std.bit_set.Dynamic = try .initFull(sema.arena, field_indices.len);
12394 in_mem_coercible.unset(first_non_imc);
12395 {
12396 const next = first_non_imc + 1;
12397 for (field_indices[next..], next..) |field_idx, i| {
12398 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12399 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12400 in_mem_coercible.unset(i);
12401 }
1234612402 }
12403 }
1234712404
12348 {
12349 // Non-bitcast cases
12350 var it = in_mem_coercible.iterator(.{ .kind = .unset });
12351 while (it.next()) |idx| {
12352 var coerce_block = case_block.makeSubBlock();
12353 defer coerce_block.instructions.deinit(sema.gpa);
12405 const capture_block_inst = try case_block.addInstAsIndex(.{
12406 .tag = .block,
12407 .data = .{
12408 .ty_pl = .{
12409 .ty = .fromType(capture_ty),
12410 .payload = undefined, // updated below
12411 },
12412 },
12413 });
1235412414
12355 const case_src: LazySrcLoc = .{
12356 .base_node_inst = capture_src.base_node_inst,
12357 .offset = .{ .switch_case_item = .{
12358 .switch_node_offset = switch_node_offset,
12359 .case_idx = capture_src.offset.switch_capture.case_idx,
12360 .item_idx = .{ .kind = .single, .value = @intCast(idx) },
12361 } },
12362 };
12415 const prong_count = field_indices.len - in_mem_coercible.count();
1236312416
12364 const field_idx = field_indices[idx];
12365 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12366 const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);
12367 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
12368 _ = try coerce_block.addBr(capture_block_inst, coerced);
12417 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
12418 var cases_extra = try std.ArrayList(u32).initCapacity(gpa, estimated_extra);
12419 defer cases_extra.deinit(gpa);
1236912420
12370 try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".field_names.len +
12371 1 + // `item`, no ranges
12372 coerce_block.instructions.items.len);
12373 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12374 .items_len = 1,
12375 .ranges_len = 0,
12376 .body_len = @intCast(coerce_block.instructions.items.len),
12377 }));
12378 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item
12379 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
12380 }
12381 }
12382 const else_body_len = len: {
12383 // 'else' prong uses a bitcast
12421 {
12422 // All branch hints are `.none`, so just add zero elems.
12423 comptime assert(@intFromEnum(std.lang.BranchHint.none) == 0);
12424 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
12425 try cases_extra.appendNTimes(gpa, 0, need_elems);
12426 }
12427
12428 {
12429 // Non-bitcast cases
12430 var it = in_mem_coercible.iterator(.{ .kind = .unset });
12431 while (it.next()) |idx| {
1238412432 var coerce_block = case_block.makeSubBlock();
1238512433 defer coerce_block.instructions.deinit(sema.gpa);
1238612434
12387 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
12388 const first_imc_field_idx = field_indices[first_imc_item_idx];
12389 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
12390 const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);
12391 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
12392 _ = try coerce_block.addBr(capture_block_inst, coerced);
12393
12394 try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));
12395 break :len coerce_block.instructions.items.len;
12396 };
12435 const case_src: LazySrcLoc = .{
12436 .base_node_inst = capture_src.base_node_inst,
12437 .offset = .{ .switch_case_item = .{
12438 .switch_node_offset = switch_node_offset,
12439 .case_idx = capture_src.offset.switch_capture.case_idx,
12440 .item_idx = .{ .kind = .single, .value = @intCast(idx) },
12441 } },
12442 };
1239712443
12398 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".field_names.len +
12399 cases_extra.items.len +
12400 @typeInfo(Air.Block).@"struct".field_names.len +
12401 1);
12444 const field_idx = field_indices[idx];
12445 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12446 const uncoerced = try coerce_block.addStructFieldVal(loaded_operand, field_idx, field_ty);
12447 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
12448 _ = try coerce_block.addBr(capture_block_inst, coerced);
1240212449
12403 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
12404 try sema.air_instructions.append(sema.gpa, .{
12405 .tag = .switch_br,
12406 .data = .{
12407 .pl_op = .{
12408 .operand = undefined, // set by switch below
12409 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
12410 .cases_len = @intCast(prong_count),
12411 .else_body_len = @intCast(else_body_len),
12412 }),
12413 },
12414 },
12415 });
12416 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
12417
12418 // Set up block body
12419 switch (operand) {
12420 .simple => |s| {
12421 const air_datas = sema.air_instructions.items(.data);
12422 air_datas[switch_br_inst].pl_op.operand = s.cond;
12423 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12424 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
12425 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12426 },
12427 .loop => {
12428 // The block must first extract the tag from the loaded union.
12429 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
12430 try sema.air_instructions.append(sema.gpa, .{
12431 .tag = .get_union_tag,
12432 .data = .{ .ty_op = .{
12433 .ty = .fromIntern(union_obj.enum_tag_type),
12434 .operand = operand_val,
12435 } },
12436 });
12437 const air_datas = sema.air_instructions.items(.data);
12438 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
12439 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12440 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 2 });
12441 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
12442 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12443 },
12450 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".field_names.len +
12451 1 + // `item`, no ranges
12452 coerce_block.instructions.items.len);
12453 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12454 .items_len = 1,
12455 .ranges_len = 0,
12456 .body_len = @intCast(coerce_block.instructions.items.len),
12457 }));
12458 cases_extra.appendAssumeCapacity(@intFromEnum(item_refs[idx])); // item
12459 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
1244412460 }
12445
12446 return capture_block_inst.toRef();
1244712461 }
12462 const else_body_len = len: {
12463 // 'else' prong uses a bitcast
12464 var coerce_block = case_block.makeSubBlock();
12465 defer coerce_block.instructions.deinit(sema.gpa);
1244812466
12449 if (err_set) {
12450 const case_vals = kind.item_refs;
12451 if (case_vals.len == 1) {
12452 const item_val = sema.resolveValue(case_vals[0]).?;
12453 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
12454 return sema.bitCast(case_block, item_ty, .fromValue(item_val), operand_src, null);
12455 }
12467 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
12468 const first_imc_field_idx = field_indices[first_imc_item_idx];
12469 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
12470 const uncoerced = try coerce_block.addStructFieldVal(loaded_operand, first_imc_field_idx, first_imc_field_ty);
12471 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
12472 _ = try coerce_block.addBr(capture_block_inst, coerced);
1245612473
12457 var names: InferredErrorSet.NameMap = .{};
12458 try names.ensureUnusedCapacity(sema.arena, case_vals.len);
12459 for (case_vals) |err| {
12460 const err_val = sema.resolveValue(err).?;
12461 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});
12462 }
12463 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());
12464 return sema.bitCast(case_block, error_ty, operand_val, operand_src, null);
12465 }
12474 try cases_extra.appendSlice(gpa, @ptrCast(coerce_block.instructions.items));
12475 break :len coerce_block.instructions.items.len;
12476 };
1246612477
12467 // In this case the capture value is just the passed-through value of the
12468 // switch condition. It is comptime-known if there is only one item.
12469 if (capture_by_ref) {
12470 return operand_ptr;
12471 }
12472 switch (kind) {
12473 .inline_ref, .special => unreachable,
12474 .item_refs => |case_vals| {
12475 // If there's only a single item, the capture is comptime-known!
12476 if (case_vals.len == 1) return case_vals[0];
12478 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).@"struct".field_names.len +
12479 cases_extra.items.len +
12480 @typeInfo(Air.Block).@"struct".field_names.len +
12481 1);
12482
12483 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
12484 try sema.air_instructions.append(gpa, .{
12485 .tag = .switch_br,
12486 .data = .{
12487 .pl_op = .{
12488 .operand = undefined, // set by switch below
12489 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
12490 .cases_len = @intCast(prong_count),
12491 .else_body_len = @intCast(else_body_len),
12492 }),
12493 },
12494 },
12495 });
12496 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
12497
12498 // Set up block body
12499 switch (operand) {
12500 .simple => |s| {
12501 const air_datas = sema.air_instructions.items(.data);
12502 air_datas[switch_br_inst].pl_op.operand = s.cond;
12503 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12504 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
12505 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12506 },
12507 .loop => {
12508 // The block must first extract the tag from the loaded union.
12509 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
12510 try sema.air_instructions.append(sema.gpa, .{
12511 .tag = .get_union_tag,
12512 .data = .{ .ty_op = .{
12513 .ty = .fromIntern(union_obj.enum_tag_type),
12514 .operand = loaded_operand,
12515 } },
12516 });
12517 const air_datas = sema.air_instructions.items(.data);
12518 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
12519 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12520 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 2 });
12521 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
12522 sema.air_extra.appendAssumeCapacity(switch_br_inst);
1247712523 },
12478 .has_ranges => {},
1247912524 }
12480 return operand_val;
12525
12526 return capture_block_inst.toRef();
1248112527}
1248212528
1248312529const 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+38
......@@ -0,0 +1,38 @@
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}
11
12const E = enum(u8) { a, b };
13export fn entry3(e: E) void {
14 switch (e) {
15 .a => |_, tag| _ = tag,
16 else => {},
17 }
18}
19
20const Error = error{ MyError, MyOtherError };
21export fn entry2(ok: bool) void {
22 switch (foo(ok)) {
23 error.MyError => |_, tag| _ = tag,
24 else => {},
25 }
26}
27fn foo(ok: bool) Error {
28 return if (ok) error.MyError else error.MyOtherError;
29}
30
31// error
32//
33// :7:30: error: cannot capture tag of packed union
34// :1:18: note: union declared here
35// :1:18: note: consider using a tagged union
36// :15:19: error: cannot capture tag of non-union type 'tmp.E'
37// :12:11: note: enum declared here
38// :23:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'