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(...@@ -10216,7 +10216,7 @@ fn analyzeSwitchBlock(
1021610216
10217 const case_vals = validated_switch.case_vals;10217 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: {
10220 var case_val_idx: usize = 0;10220 var case_val_idx: usize = 0;
10221 var case_it = zir_switch.iterateCases();10221 var case_it = zir_switch.iterateCases();
10222 var extra_index = zir_switch.end;10222 var extra_index = zir_switch.end;
...@@ -10239,12 +10239,12 @@ fn analyzeSwitchBlock(...@@ -10239,12 +10239,12 @@ fn analyzeSwitchBlock(
10239 }10239 }
10240 continue;10240 continue;
10241 }10241 }
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 };
10243 }10243 }
10244 if (has_else) {10244 if (has_else) {
10245 // This *has* to be checked after iterating all regular cases because10245 // This *has* to be checked after iterating all regular cases because
10246 // we allow simple noreturn else prongs when switching on error sets!10246 // 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 };
10248 }10248 }
10249 unreachable; // malformed validated switch10249 unreachable; // malformed validated switch
10250 };10250 };
...@@ -10255,58 +10255,33 @@ fn analyzeSwitchBlock(...@@ -10255,58 +10255,33 @@ fn analyzeSwitchBlock(
10255 if (!(err_set and10255 if (!(err_set and
10256 try sema.maybeErrorUnwrap(&case_block, body, cond_ref, operand_src, true)))10256 try sema.maybeErrorUnwrap(&case_block, body, cond_ref, operand_src, true)))
10257 {10257 {
10258 // Set up captures manually to avoid special cases in the main logic.10258 const payload_inst = if (capture != .none) inst: {
10259 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
10260 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;10259 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
10261 const payload_ref: Air.Inst.Ref = payload_ref: {10260 const payload_ref: Air.Inst.Ref = payload_ref: {
10262 const item_val: Value = item_val: {10261 const captured_opv: Value = captured_opv: {
10263 if (!tagged_union_originally) {10262 if (!tagged_union_originally) {
10264 break :item_val item_opv;10263 break :captured_opv item_opv;
10265 }10264 }
10266 if (maybe_operand_opv) |operand_opv| {10265 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);
10268 }10267 }
10269 assert(zir_switch.any_maybe_runtime_capture); // there's a payload capture10268 assert(zir_switch.any_maybe_runtime_capture); // there's a payload capture
10270 const operand_val, const operand_ref = switch (operand) {10269 const loaded_operand = try sema.analyzeSwitchOperandLoad(&case_block, operand, operand_src, capture == .by_ref);
10271 .simple => unreachable,10270 break :payload_ref try sema.resolveSwitchPayloadCaptureTaggedUnion(
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(
10288 &case_block,10271 &case_block,
10289 operand,10272 loaded_operand,
10290 operand_val,
10291 operand_ref,
10292 operand_ty,
10293 operand_src,10273 operand_src,
10294 block.src(.{ .switch_capture = .{10274 operand_ty,
10295 .switch_node_offset = src_node_offset,10275 item_opv,
10296 .case_idx = index,
10297 } }),
10298 capture == .by_ref,10276 capture == .by_ref,
10299 prong_kind,
10300 validated_switch.else_err_ty,
10301 );10277 );
10302 };10278 };
10303 break :payload_ref switch (capture) {10279 break :payload_ref switch (capture) {
10304 .by_val => .fromValue(item_val),10280 .by_val => .fromValue(captured_opv),
10305 .by_ref => try sema.uavRef(item_val),10281 .by_ref => try sema.uavRef(captured_opv),
10306 .none => unreachable,10282 .none => unreachable,
10307 };10283 };
10308 };10284 };
10309 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
10310 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);10285 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
10311 break :inst payload_inst;10286 break :inst payload_inst;
10312 } else undefined;10287 } else undefined;
...@@ -10599,7 +10574,7 @@ fn finishSwitchBr(...@@ -10599,7 +10574,7 @@ fn finishSwitchBr(
10599 } }),10574 } }),
10600 prong_info.capture,10575 prong_info.capture,
10601 prong_info.has_tag_capture,10576 prong_info.has_tag_capture,
10602 .{ .inline_ref = item_ref },10577 .{ .@"inline" = item_ref },
10603 validated_switch.else_err_ty,10578 validated_switch.else_err_ty,
10604 switch_inst,10579 switch_inst,
10605 zir_switch,10580 zir_switch,
...@@ -10689,7 +10664,7 @@ fn finishSwitchBr(...@@ -10689,7 +10664,7 @@ fn finishSwitchBr(
10689 } }),10664 } }),
10690 prong_info.capture,10665 prong_info.capture,
10691 prong_info.has_tag_capture,10666 prong_info.has_tag_capture,
10692 .{ .inline_ref = item_ref },10667 .{ .@"inline" = item_ref },
10693 validated_switch.else_err_ty,10668 validated_switch.else_err_ty,
10694 switch_inst,10669 switch_inst,
10695 zir_switch,10670 zir_switch,
...@@ -10829,7 +10804,7 @@ fn finishSwitchBr(...@@ -10829,7 +10804,7 @@ fn finishSwitchBr(
10829 } }),10804 } }),
10830 else_case.capture,10805 else_case.capture,
10831 else_case.has_tag_capture,10806 else_case.has_tag_capture,
10832 .{ .inline_ref = item_ref },10807 .{ .@"inline" = item_ref },
10833 validated_switch.else_err_ty,10808 validated_switch.else_err_ty,
10834 switch_inst,10809 switch_inst,
10835 zir_switch,10810 zir_switch,
...@@ -11269,8 +11244,7 @@ fn validateSwitchBlock(...@@ -11269,8 +11244,7 @@ fn validateSwitchBlock(
11269 const has_else = zir_switch.else_case != null;11244 const has_else = zir_switch.else_case != null;
11270 const has_under = zir_switch.has_under;11245 const has_under = zir_switch.has_under;
1127111246
11272 var case_vals: std.ArrayList(Air.Inst.Ref) = .empty;11247 var case_vals: std.ArrayList(Air.Inst.Ref) = try .initCapacity(arena, zir_switch.item_infos.len);
11273 try case_vals.ensureUnusedCapacity(arena, zir_switch.item_infos.len);
1127411248
11275 // Duplicate checking variables later also used for `inline else`.11249 // Duplicate checking variables later also used for `inline else`.
11276 var seen_enum_fields: []?LazySrcLoc = &.{};11250 var seen_enum_fields: []?LazySrcLoc = &.{};
...@@ -11676,10 +11650,10 @@ fn resolveSwitchBlock(...@@ -11676,10 +11650,10 @@ fn resolveSwitchBlock(
11676 // This prong should be unreachable!11650 // This prong should be unreachable!
11677 return .unreachable_value;11651 return .unreachable_value;
11678 }11652 }
11679 const prong_kind: SwitchProngKind = kind: {11653 const prong_items: SwitchProngItems = prong_items: {
11680 if (prong_info.is_inline) break :kind .{ .inline_ref = cond_ref };11654 if (prong_info.is_inline) break :prong_items .{ .@"inline" = cond_ref };
11681 if (range_refs.len > 0) break :kind .has_ranges;11655 if (range_refs.len > 0) break :prong_items .has_ranges;
11682 break :kind .{ .item_refs = item_refs };11656 break :prong_items .{ .item_refs = item_refs };
11683 };11657 };
11684 return sema.resolveSwitchProng(11658 return sema.resolveSwitchProng(
11685 block,11659 block,
...@@ -11693,7 +11667,7 @@ fn resolveSwitchBlock(...@@ -11693,7 +11667,7 @@ fn resolveSwitchBlock(
11693 } }),11667 } }),
11694 prong_info.capture,11668 prong_info.capture,
11695 prong_info.has_tag_capture,11669 prong_info.has_tag_capture,
11696 prong_kind,11670 prong_items,
11697 validated_switch.else_err_ty,11671 validated_switch.else_err_ty,
11698 merges,11672 merges,
11699 switch_inst,11673 switch_inst,
...@@ -11707,8 +11681,8 @@ fn resolveSwitchBlock(...@@ -11707,8 +11681,8 @@ fn resolveSwitchBlock(
11707 if ((try sema.compareAll(cond_val, .gte, first_val, item_ty)) and11681 if ((try sema.compareAll(cond_val, .gte, first_val, item_ty)) and
11708 (try sema.compareAll(cond_val, .lte, last_val, item_ty)))11682 (try sema.compareAll(cond_val, .lte, last_val, item_ty)))
11709 {11683 {
11710 const prong_kind: SwitchProngKind = if (prong_info.is_inline)11684 const prong_items: SwitchProngItems = if (prong_info.is_inline)
11711 .{ .inline_ref = cond_ref }11685 .{ .@"inline" = cond_ref }
11712 else11686 else
11713 .has_ranges;11687 .has_ranges;
11714 return sema.resolveSwitchProng(11688 return sema.resolveSwitchProng(
...@@ -11723,7 +11697,7 @@ fn resolveSwitchBlock(...@@ -11723,7 +11697,7 @@ fn resolveSwitchBlock(
11723 } }),11697 } }),
11724 prong_info.capture,11698 prong_info.capture,
11725 prong_info.has_tag_capture,11699 prong_info.has_tag_capture,
11726 prong_kind,11700 prong_items,
11727 validated_switch.else_err_ty,11701 validated_switch.else_err_ty,
11728 merges,11702 merges,
11729 switch_inst,11703 switch_inst,
...@@ -11742,8 +11716,8 @@ fn resolveSwitchBlock(...@@ -11742,8 +11716,8 @@ fn resolveSwitchBlock(
1174211716
11743 if (else_is_named_only and item_ty.enumTagFieldIndex(cond_val, zcu) != null) {11717 if (else_is_named_only and item_ty.enumTagFieldIndex(cond_val, zcu) != null) {
11744 assert(item_ty.isNonexhaustiveEnum(zcu));11718 assert(item_ty.isNonexhaustiveEnum(zcu));
11745 const prong_kind: SwitchProngKind = if (else_case.is_inline)11719 const prong_items: SwitchProngItems = if (else_case.is_inline)
11746 .{ .inline_ref = cond_ref }11720 .{ .@"inline" = cond_ref }
11747 else11721 else
11748 .special;11722 .special;
11749 return sema.resolveSwitchProng(11723 return sema.resolveSwitchProng(
...@@ -11758,7 +11732,7 @@ fn resolveSwitchBlock(...@@ -11758,7 +11732,7 @@ fn resolveSwitchBlock(
11758 } }),11732 } }),
11759 else_case.capture,11733 else_case.capture,
11760 else_case.has_tag_capture,11734 else_case.has_tag_capture,
11761 prong_kind,11735 prong_items,
11762 validated_switch.else_err_ty,11736 validated_switch.else_err_ty,
11763 merges,11737 merges,
11764 switch_inst,11738 switch_inst,
...@@ -11782,8 +11756,8 @@ fn resolveSwitchBlock(...@@ -11782,8 +11756,8 @@ fn resolveSwitchBlock(
11782 return .unreachable_value;11756 return .unreachable_value;
11783 }11757 }
11784 }11758 }
11785 const prong_kind: SwitchProngKind = if (is_inline)11759 const prong_items: SwitchProngItems = if (is_inline)
11786 .{ .inline_ref = cond_ref }11760 .{ .@"inline" = cond_ref }
11787 else11761 else
11788 .special;11762 .special;
11789 return sema.resolveSwitchProng(11763 return sema.resolveSwitchProng(
...@@ -11798,7 +11772,7 @@ fn resolveSwitchBlock(...@@ -11798,7 +11772,7 @@ fn resolveSwitchBlock(
11798 } }),11772 } }),
11799 capture,11773 capture,
11800 has_tag_capture,11774 has_tag_capture,
11801 prong_kind,11775 prong_items,
11802 validated_switch.else_err_ty,11776 validated_switch.else_err_ty,
11803 merges,11777 merges,
11804 switch_inst,11778 switch_inst,
...@@ -11834,14 +11808,59 @@ const SwitchOperand = union(enum) {...@@ -11834,14 +11808,59 @@ const SwitchOperand = union(enum) {
11834 },11808 },
11835};11809};
1183611810
11837const SwitchProngKind = union(enum) {11811fn analyzeSwitchOperandLoad(
11838 /// Prefer populating this field over the others, if possible.11812 sema: *Sema,
11839 inline_ref: Air.Inst.Ref,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,
11840 item_refs: []const Air.Inst.Ref,11844 item_refs: []const Air.Inst.Ref,
11841 has_ranges,11845 has_ranges,
11842 special,11846 special,
11843};11847};
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
11845/// Resolve a switch prong which is determined at comptime to have no peers.11864/// Resolve a switch prong which is determined at comptime to have no peers.
11846/// Sets up captures as needed. Uses `analyzeBodyRuntimeBreak`.11865/// Sets up captures as needed. Uses `analyzeBodyRuntimeBreak`.
11847fn resolveSwitchProng(11866fn resolveSwitchProng(
...@@ -11855,7 +11874,7 @@ fn resolveSwitchProng(...@@ -11855,7 +11874,7 @@ fn resolveSwitchProng(
11855 capture_src: LazySrcLoc,11874 capture_src: LazySrcLoc,
11856 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,11875 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
11857 has_tag_capture: bool,11876 has_tag_capture: bool,
11858 kind: SwitchProngKind,11877 prong_items: SwitchProngItems,
11859 else_err_ty: ?Type,11878 else_err_ty: ?Type,
11860 merges: *Block.Merges,11879 merges: *Block.Merges,
11861 switch_inst: Zir.Inst.Index,11880 switch_inst: Zir.Inst.Index,
...@@ -11870,36 +11889,28 @@ fn resolveSwitchProng(...@@ -11870,36 +11889,28 @@ fn resolveSwitchProng(
11870 const parent_hint = sema.branch_hint;11889 const parent_hint = sema.branch_hint;
11871 defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;11890 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: {
11874 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;11905 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
11875 const payload_ref = try sema.analyzeSwitchPayloadCapture(11906 sema.inst_map.putAssumeCapacity(payload_inst, analyzed_captures.payload_ref);
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);
11889 break :inst payload_inst;11907 break :inst payload_inst;
11890 } else undefined;11908 } else undefined;
11891 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));11909 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1189211910
11893 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {11911 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
11894 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;11912 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
11895 const tag_ref = try sema.analyzeSwitchTagCapture(11913 sema.inst_map.putAssumeCapacity(tag_inst, analyzed_captures.tag_ref);
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);
11903 break :inst tag_inst;11914 break :inst tag_inst;
11904 } else undefined;11915 } else undefined;
11905 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));11916 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
...@@ -11945,7 +11956,7 @@ fn analyzeSwitchProng(...@@ -11945,7 +11956,7 @@ fn analyzeSwitchProng(
11945 capture_src: LazySrcLoc,11956 capture_src: LazySrcLoc,
11946 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,11957 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
11947 has_tag_capture: bool,11958 has_tag_capture: bool,
11948 kind: SwitchProngKind,11959 prong_items: SwitchProngItems,
11949 else_err_ty: ?Type,11960 else_err_ty: ?Type,
11950 switch_inst: Zir.Inst.Index,11961 switch_inst: Zir.Inst.Index,
11951 zir_switch: *const Zir.UnwrappedSwitchBlock,11962 zir_switch: *const Zir.UnwrappedSwitchBlock,
...@@ -11966,77 +11977,28 @@ fn analyzeSwitchProng(...@@ -11966,77 +11977,28 @@ fn analyzeSwitchProng(
11966 }11977 }
11967 }11978 }
1196811979
11969 const need_load: bool = need_load: {11980 const analyzed_captures = try sema.analyzeSwitchCaptures(
11970 if (capture == .none and !has_tag_capture) {11981 case_block,
11971 // No need to load the operand for this prong!11982 operand,
11972 break :need_load false;11983 operand_src,
11973 }11984 operand_ty,
11974 if (capture != .none and operand_ty.zigTypeTag(zcu) == .@"union" and11985 capture_src,
11975 operand_ty.containerLayout(zcu) != .@"packed")11986 capture,
11976 {11987 has_tag_capture,
11977 // Non-OPV tagged union payload captures are always runtime-known.11988 prong_items,
11978 break :need_load true;11989 else_err_ty,
11979 }11990 );
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 };
1200911991
12010 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {11992 const payload_inst = if (capture != .none) inst: {
12011 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;11993 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
12012 const payload_ref = try sema.analyzeSwitchPayloadCapture(11994 sema.inst_map.putAssumeCapacity(payload_inst, analyzed_captures.payload_ref);
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);
12026 break :inst payload_inst;11995 break :inst payload_inst;
12027 } else undefined;11996 } else undefined;
12028 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));11997 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1202911998
12030 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {11999 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
12031 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;12000 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
12032 const tag_ref = try sema.analyzeSwitchTagCapture(12001 sema.inst_map.putAssumeCapacity(tag_inst, analyzed_captures.tag_ref);
12033 case_block,
12034 operand_val,
12035 operand_ty,
12036 capture_src,
12037 kind,
12038 );
12039 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
12040 break :inst tag_inst;12002 break :inst tag_inst;
12041 } else undefined;12003 } else undefined;
12042 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));12004 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
...@@ -12047,157 +12009,314 @@ fn analyzeSwitchProng(...@@ -12047,157 +12009,314 @@ fn analyzeSwitchProng(
12047 return sema.analyzeBodyRuntimeBreak(case_block, prong_body);12009 return sema.analyzeBodyRuntimeBreak(case_block, prong_body);
12048}12010}
1204912011
12050fn analyzeSwitchTagCapture(12012fn analyzeSwitchCaptures(
12051 sema: *Sema,12013 sema: *Sema,
12052 case_block: *Block,12014 case_block: *Block,
12053 /// May be `none` if this is an inline capture or if `kind.item_refs.len == 1`.12015 operand: SwitchOperand,
12054 operand_val: Air.Inst.Ref,12016 operand_src: LazySrcLoc,
12055 operand_ty: Type,12017 operand_ty: Type,
12056 capture_src: LazySrcLoc,12018 capture_src: LazySrcLoc,
12057 kind: SwitchProngKind,12019 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
12058) CompileError!Air.Inst.Ref {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} {
12059 const pt = sema.pt;12027 const pt = sema.pt;
12060 const zcu = pt.zcu;12028 const zcu = pt.zcu;
1206112029
12062 const tag_capture_src: LazySrcLoc = .{12030 if (operand_ty.zigTypeTag(zcu) == .@"union" and
12063 .base_node_inst = capture_src.base_node_inst,12031 operand_ty.containerLayout(zcu) != .@"packed")
12064 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },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);
12065 };12153 };
1206612154
12067 if (operand_ty.zigTypeTag(zcu) != .@"union") {12155 if (has_tag_capture) {
12068 return sema.fail(case_block, tag_capture_src, "cannot capture tag of non-union type '{f}'", .{12156 const tag_capture_src: LazySrcLoc = .{
12069 operand_ty.fmt(pt),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;
12070 });12179 });
12071 }12180 }
12072 if (operand_ty.containerLayout(zcu) == .@"packed") {12181
12073 return sema.fail(case_block, tag_capture_src, "cannot capture tag of packed union", .{});12182 return .{ .payload_ref = payload_ref, .tag_ref = .none };
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);
12082}12183}
1208312184
12084fn analyzeSwitchPayloadCapture(12185fn resolveSwitchPayloadCaptureTaggedUnion(
12085 sema: *Sema,12186 sema: *Sema,
12086 case_block: *Block,12187 case_block: *Block,
12087 operand: SwitchOperand,12188 loaded_operand: Air.Inst.Ref,
12088 /// Always has to be not-`none` if this is a tagged union payload capture.12189 operand_src: LazySrcLoc,
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,
12094 operand_ty: Type,12190 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,
12095 operand_src: LazySrcLoc,12230 operand_src: LazySrcLoc,
12096 capture_src: LazySrcLoc,12231 operand_ty: Type,
12097 capture_by_ref: bool,12232 capture_by_ref: bool,
12098 kind: SwitchProngKind,12233 capture_src: LazySrcLoc,
12099 else_err_ty: ?Type,12234 prong_items: SwitchProngItems,
12100) CompileError!Air.Inst.Ref {12235) CompileError!Air.Inst.Ref {
12101 const pt = sema.pt;12236 const pt = sema.pt;
12102 const zcu = pt.zcu;12237 const zcu = pt.zcu;
12103 const ip = &zcu.intern_pool;12238 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
12105 const switch_node_offset = operand_src.offset.node_offset_switch_operand;12248 const switch_node_offset = operand_src.offset.node_offset_switch_operand;
1210612249
12107 const tagged_union_originally = operand_ty.zigTypeTag(zcu) == .@"union" and12250 const union_obj = zcu.typeToUnion(operand_ty).?;
12108 operand_ty.containerLayout(zcu) != .@"packed";
12109 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
1211012251
12111 if (err_set and capture_by_ref) {12252 const first_item_val = sema.resolveValue(item_refs[0]).?;
12112 return sema.fail(12253 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;
12113 case_block,12254 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);
12114 capture_src,
12115 "error set cannot be captured by reference",
12116 .{},
12117 );
12118 }
1211912255
12120 if (kind == .inline_ref) {12256 const field_indices = try sema.arena.alloc(u32, item_refs.len);
12121 const item_val = sema.resolveValue(kind.inline_ref).?;12257 for (item_refs, field_indices) |item_ref, *field_idx| {
12122 if (tagged_union_originally) {12258 const item_val = sema.resolveValue(item_ref).?;
12123 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);12259 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
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;
12165 }12260 }
1216612261
12167 if (tagged_union_originally) {12262 // Fast path: if all the operands are the same type already, we don't need to hit
12168 const case_vals = kind.item_refs;12263 // PTR! This will also allow us to emit simpler code.
1216912264 const same_types = for (field_indices[1..]) |field_idx| {
12170 const union_obj = zcu.typeToUnion(operand_ty).?;12265 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12171 const first_item_val = sema.resolveValue(case_vals[0]).?;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).?;12269 const capture_ty: Type = capture_ty: {
12174 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);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);12278 const item_srcs = try sema.arena.alloc(?LazySrcLoc, item_refs.len);
12177 for (case_vals, field_indices) |item, *field_idx| {12279 for (item_srcs, 0..) |*item_src, item_i| {
12178 const item_val = sema.resolveValue(item).?;12280 item_src.* = .{
12179 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;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 };
12180 }12288 }
1218112289
12182 // Fast path: if all the operands are the same type already, we don't need to hit12290 break :capture_ty sema.resolvePeerTypes(
12183 // PTR! This will also allow us to emit simpler code.12291 case_block,
12184 const same_types = for (field_indices[1..]) |field_idx| {12292 capture_src,
12185 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12293 dummy_captures,
12186 if (!field_ty.eql(first_field_ty, zcu)) break false;12294 .{ .override = item_srcs },
12187 } else true;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: {12305 // By-reference captures have some further restrictions which make them easier to emit
12190 if (same_types) break :capture_ty first_field_ty;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.
12191 // We need values to run PTR on, so make a bunch of undef constants.12311 // 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);12312 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, item_refs.len);
12193 for (dummy_captures, field_indices) |*dummy, field_idx| {12313 for (field_indices, dummy_captures) |field_index, *dummy| {
12194 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12314 const field_ptr_ty = try operand_ptr_ty.fieldPtrType(field_index, pt);
12195 dummy.* = try pt.undefRef(field_ty);12315 dummy.* = try pt.undefRef(field_ptr_ty);
12196 }12316 }
1219712317 const item_srcs = try sema.arena.alloc(?LazySrcLoc, item_refs.len);
12198 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);12318 for (item_srcs, 0..) |*item_src, item_i| {
12199 for (case_srcs, 0..) |*case_src, item_i| {12319 item_src.* = .{
12200 case_src.* = .{
12201 .base_node_inst = capture_src.base_node_inst,12320 .base_node_inst = capture_src.base_node_inst,
12202 .offset = .{ .switch_case_item = .{12321 .offset = .{ .switch_case_item = .{
12203 .switch_node_offset = switch_node_offset,12322 .switch_node_offset = switch_node_offset,
...@@ -12207,14 +12326,15 @@ fn analyzeSwitchPayloadCapture(...@@ -12207,14 +12326,15 @@ fn analyzeSwitchPayloadCapture(
12207 };12326 };
12208 }12327 }
1220912328
12210 break :capture_ty sema.resolvePeerTypes(12329 break :resolve sema.resolvePeerTypes(
12211 case_block,12330 case_block,
12212 capture_src,12331 capture_src,
12213 dummy_captures,12332 dummy_captures,
12214 .{ .override = case_srcs },12333 .{ .override = item_srcs },
12215 ) catch |err| switch (err) {12334 ) catch |err| switch (err) {
12216 error.AnalysisFail => {12335 error.AnalysisFail => {
12217 const msg = sema.err orelse return error.AnalysisFail;12336 const msg = sema.err orelse return error.AnalysisFail;
12337 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
12218 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});12338 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12219 return error.AnalysisFail;12339 return error.AnalysisFail;
12220 },12340 },
...@@ -12222,262 +12342,188 @@ fn analyzeSwitchPayloadCapture(...@@ -12222,262 +12342,188 @@ fn analyzeSwitchPayloadCapture(
12222 };12342 };
12223 };12343 };
1222412344
12225 // By-reference captures have some further restrictions which make them easier to emit12345 if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |op_ptr_val| {
12226 if (capture_by_ref) {12346 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
12227 const operand_ptr_ty = sema.typeOf(operand_ptr);12347 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
12228 const capture_ptr_ty = resolve: {12348 return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
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);
12283 }12349 }
1228412350
12285 try sema.requireRuntimeBlock(case_block, operand_src, null);12351 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) {12355 if (try capture_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
12288 return case_block.addStructFieldVal(operand_val, first_field_index, capture_ty);
12289 }
1229012356
12291 // We may have to emit a switch block which coerces the operand to the capture type.12357 if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |operand_val| {
12292 // If we can, try to avoid that using in-memory coercions.12358 if (operand_val.isUndef(zcu)) return pt.undefRef(capture_ty);
12293 const first_non_imc = in_mem: {12359 const union_val = ip.indexToKey(operand_val.toIntern()).un;
12294 for (field_indices, 0..) |field_idx, i| {12360 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
12295 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12361 const uncoerced: Air.Inst.Ref = .fromIntern(union_val.val);
12296 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {12362 return sema.coerce(case_block, capture_ty, uncoerced, operand_src);
12297 break :in_mem i;12363 }
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 };
1230512364
12306 // By-val capture with heterogeneous types which are not all in-memory coercible to12365 try sema.requireRuntimeBlock(case_block, operand_src, null);
12307 // the resolved capture type. We finally have to fall back to the ugly method.
1230812366
12309 // However, let's first track which operands are in-memory coercible. There may well12367 if (same_types) {
12310 // be several, and we can squash all of these cases into the same switch prong using12368 return case_block.addStructFieldVal(loaded_operand, first_field_index, capture_ty);
12311 // a simple bitcast. We'll make this the 'else' prong.12369 }
1231212370
12313 var in_mem_coercible: std.bit_set.Dynamic = try .initFull(sema.arena, field_indices.len);12371 // We may have to emit a switch block which coerces the operand to the capture type.
12314 in_mem_coercible.unset(first_non_imc);12372 // If we can, try to avoid that using in-memory coercions.
12315 {12373 const first_non_imc = in_mem: {
12316 const next = first_non_imc + 1;12374 for (field_indices, 0..) |field_idx, i| {
12317 for (field_indices[next..], next..) |field_idx, i| {12375 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12318 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)) {
12319 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {12377 break :in_mem i;
12320 in_mem_coercible.unset(i);
12321 }
12322 }12378 }
12323 }12379 }
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(.{12386 // By-val capture with heterogeneous types which are not all in-memory coercible to
12326 .tag = .block,12387 // the resolved capture type. We finally have to fall back to the ugly method.
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();
1233612388
12337 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints12389 // However, let's first track which operands are in-memory coercible. There may well
12338 var cases_extra = try std.array_list.Managed(u32).initCapacity(sema.gpa, estimated_extra);12390 // be several, and we can squash all of these cases into the same switch prong using
12339 defer cases_extra.deinit();12391 // a simple bitcast. We'll make this the 'else' prong.
1234012392
12341 {12393 var in_mem_coercible: std.bit_set.Dynamic = try .initFull(sema.arena, field_indices.len);
12342 // All branch hints are `.none`, so just add zero elems.12394 in_mem_coercible.unset(first_non_imc);
12343 comptime assert(@intFromEnum(std.lang.BranchHint.none) == 0);12395 {
12344 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;12396 const next = first_non_imc + 1;
12345 try cases_extra.appendNTimes(0, need_elems);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 }
12346 }12402 }
12403 }
1234712404
12348 {12405 const capture_block_inst = try case_block.addInstAsIndex(.{
12349 // Non-bitcast cases12406 .tag = .block,
12350 var it = in_mem_coercible.iterator(.{ .kind = .unset });12407 .data = .{
12351 while (it.next()) |idx| {12408 .ty_pl = .{
12352 var coerce_block = case_block.makeSubBlock();12409 .ty = .fromType(capture_ty),
12353 defer coerce_block.instructions.deinit(sema.gpa);12410 .payload = undefined, // updated below
12411 },
12412 },
12413 });
1235412414
12355 const case_src: LazySrcLoc = .{12415 const prong_count = field_indices.len - in_mem_coercible.count();
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 };
1236312416
12364 const field_idx = field_indices[idx];12417 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
12365 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12418 var cases_extra = try std.ArrayList(u32).initCapacity(gpa, estimated_extra);
12366 const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);12419 defer cases_extra.deinit(gpa);
12367 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
12368 _ = try coerce_block.addBr(capture_block_inst, coerced);
1236912420
12370 try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".field_names.len +12421 {
12371 1 + // `item`, no ranges12422 // All branch hints are `.none`, so just add zero elems.
12372 coerce_block.instructions.items.len);12423 comptime assert(@intFromEnum(std.lang.BranchHint.none) == 0);
12373 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{12424 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
12374 .items_len = 1,12425 try cases_extra.appendNTimes(gpa, 0, need_elems);
12375 .ranges_len = 0,12426 }
12376 .body_len = @intCast(coerce_block.instructions.items.len),12427
12377 }));12428 {
12378 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item12429 // Non-bitcast cases
12379 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body12430 var it = in_mem_coercible.iterator(.{ .kind = .unset });
12380 }12431 while (it.next()) |idx| {
12381 }
12382 const else_body_len = len: {
12383 // 'else' prong uses a bitcast
12384 var coerce_block = case_block.makeSubBlock();12432 var coerce_block = case_block.makeSubBlock();
12385 defer coerce_block.instructions.deinit(sema.gpa);12433 defer coerce_block.instructions.deinit(sema.gpa);
1238612434
12387 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;12435 const case_src: LazySrcLoc = .{
12388 const first_imc_field_idx = field_indices[first_imc_item_idx];12436 .base_node_inst = capture_src.base_node_inst,
12389 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);12437 .offset = .{ .switch_case_item = .{
12390 const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);12438 .switch_node_offset = switch_node_offset,
12391 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);12439 .case_idx = capture_src.offset.switch_capture.case_idx,
12392 _ = try coerce_block.addBr(capture_block_inst, coerced);12440 .item_idx = .{ .kind = .single, .value = @intCast(idx) },
1239312441 } },
12394 try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));12442 };
12395 break :len coerce_block.instructions.items.len;
12396 };
1239712443
12398 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".field_names.len +12444 const field_idx = field_indices[idx];
12399 cases_extra.items.len +12445 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12400 @typeInfo(Air.Block).@"struct".field_names.len +12446 const uncoerced = try coerce_block.addStructFieldVal(loaded_operand, field_idx, field_ty);
12401 1);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);12450 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".field_names.len +
12404 try sema.air_instructions.append(sema.gpa, .{12451 1 + // `item`, no ranges
12405 .tag = .switch_br,12452 coerce_block.instructions.items.len);
12406 .data = .{12453 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12407 .pl_op = .{12454 .items_len = 1,
12408 .operand = undefined, // set by switch below12455 .ranges_len = 0,
12409 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{12456 .body_len = @intCast(coerce_block.instructions.items.len),
12410 .cases_len = @intCast(prong_count),12457 }));
12411 .else_body_len = @intCast(else_body_len),12458 cases_extra.appendAssumeCapacity(@intFromEnum(item_refs[idx])); // item
12412 }),12459 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
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 },
12444 }12460 }
12445
12446 return capture_block_inst.toRef();
12447 }12461 }
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) {12467 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
12450 const case_vals = kind.item_refs;12468 const first_imc_field_idx = field_indices[first_imc_item_idx];
12451 if (case_vals.len == 1) {12469 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
12452 const item_val = sema.resolveValue(case_vals[0]).?;12470 const uncoerced = try coerce_block.addStructFieldVal(loaded_operand, first_imc_field_idx, first_imc_field_ty);
12453 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);12471 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
12454 return sema.bitCast(case_block, item_ty, .fromValue(item_val), operand_src, null);12472 _ = try coerce_block.addBr(capture_block_inst, coerced);
12455 }
1245612473
12457 var names: InferredErrorSet.NameMap = .{};12474 try cases_extra.appendSlice(gpa, @ptrCast(coerce_block.instructions.items));
12458 try names.ensureUnusedCapacity(sema.arena, case_vals.len);12475 break :len coerce_block.instructions.items.len;
12459 for (case_vals) |err| {12476 };
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 }
1246612477
12467 // In this case the capture value is just the passed-through value of the12478 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).@"struct".field_names.len +
12468 // switch condition. It is comptime-known if there is only one item.12479 cases_extra.items.len +
12469 if (capture_by_ref) {12480 @typeInfo(Air.Block).@"struct".field_names.len +
12470 return operand_ptr;12481 1);
12471 }12482
12472 switch (kind) {12483 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
12473 .inline_ref, .special => unreachable,12484 try sema.air_instructions.append(gpa, .{
12474 .item_refs => |case_vals| {12485 .tag = .switch_br,
12475 // If there's only a single item, the capture is comptime-known!12486 .data = .{
12476 if (case_vals.len == 1) return case_vals[0];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);
12477 },12523 },
12478 .has_ranges => {},
12479 }12524 }
12480 return operand_val;12525
12526 return capture_block_inst.toRef();
12481}12527}
1248212528
12483const ResolvedSwitchItem = struct {12529const ResolvedSwitchItem = struct {
test/behavior/switch.zig+33
...@@ -1486,3 +1486,36 @@ test "switch on large types" {...@@ -1486,3 +1486,36 @@ test "switch on large types" {
1486 try S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, 0xFFFF_1234);1486 try S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, 0xFFFF_1234);
1487 try comptime S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, 0xFFFF_1234);1487 try comptime S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, 0xFFFF_1234);
1488}1488}
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}'