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

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

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

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

src/Sema.zig+554-494
...@@ -2415,6 +2415,32 @@ fn failWithModRemNegative(sema: *Sema, block: *Block, src: LazySrcLoc, lhs_ty: T...@@ -2415,6 +2415,32 @@ fn failWithModRemNegative(sema: *Sema, block: *Block, src: LazySrcLoc, lhs_ty: T
2415 });2415 });
2416}2416}
24172417
2418fn failWithInvalidSwitchTagCapture(sema: *Sema, block: *Block, tag_capture_src: LazySrcLoc, operand_ty: Type) CompileError {
2419 const pt = sema.pt;
2420 const zcu = pt.zcu;
2421
2422 if (operand_ty.zigTypeTag(zcu) == .@"union") {
2423 assert(operand_ty.containerLayout(zcu) == .@"packed");
2424 return sema.failWithOwnedErrorMsg(block, msg: {
2425 const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of packed union", .{});
2426 errdefer msg.destroy(sema.gpa);
2427 try sema.addDeclaredHereNote(msg, operand_ty);
2428 if (operand_ty.srcLocOrNull(zcu)) |ty_src| {
2429 try sema.errNote(ty_src, msg, "consider using a tagged union", .{});
2430 }
2431 break :msg msg;
2432 });
2433 }
2434 return sema.failWithOwnedErrorMsg(block, msg: {
2435 const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
2436 operand_ty.fmt(pt),
2437 });
2438 errdefer msg.destroy(sema.gpa);
2439 try sema.addDeclaredHereNote(msg, operand_ty);
2440 break :msg msg;
2441 });
2442}
2443
2418fn failWithExpectedOptionalType(sema: *Sema, block: *Block, src: LazySrcLoc, non_optional_ty: Type) CompileError {2444fn failWithExpectedOptionalType(sema: *Sema, block: *Block, src: LazySrcLoc, non_optional_ty: Type) CompileError {
2419 const pt = sema.pt;2445 const pt = sema.pt;
2420 const msg = msg: {2446 const msg = msg: {
...@@ -10022,7 +10048,7 @@ fn analyzeSwitchBlock(...@@ -10022,7 +10048,7 @@ fn analyzeSwitchBlock(
1002210048
10023 const case_vals = validated_switch.case_vals;10049 const case_vals = validated_switch.case_vals;
1002410050
10025 const index, const body, const capture, const has_tag_capture, const is_inline, const is_special = find_prong: {10051 const case_idx, const body, const capture, const has_tag_capture = find_prong: {
10026 var case_val_idx: usize = 0;10052 var case_val_idx: usize = 0;
10027 var case_it = zir_switch.iterateCases();10053 var case_it = zir_switch.iterateCases();
10028 var extra_index = zir_switch.end;10054 var extra_index = zir_switch.end;
...@@ -10045,12 +10071,12 @@ fn analyzeSwitchBlock(...@@ -10045,12 +10071,12 @@ fn analyzeSwitchBlock(
10045 }10071 }
10046 continue;10072 continue;
10047 }10073 }
10048 break :find_prong .{ case.index, prong_body, prong_info.capture, prong_info.has_tag_capture, prong_info.is_inline, false };10074 break :find_prong .{ case.index, prong_body, prong_info.capture, prong_info.has_tag_capture };
10049 }10075 }
10050 if (has_else) {10076 if (has_else) {
10051 // This *has* to be checked after iterating all regular cases because10077 // This *has* to be checked after iterating all regular cases because
10052 // we allow simple noreturn else prongs when switching on error sets!10078 // we allow simple noreturn else prongs when switching on error sets!
10053 break :find_prong .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline, true };10079 break :find_prong .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture };
10054 }10080 }
10055 unreachable; // malformed validated switch10081 unreachable; // malformed validated switch
10056 };10082 };
...@@ -10061,58 +10087,33 @@ fn analyzeSwitchBlock(...@@ -10061,58 +10087,33 @@ fn analyzeSwitchBlock(
10061 if (!(err_set and10087 if (!(err_set and
10062 try sema.maybeErrorUnwrap(&case_block, body, cond_ref, operand_src, true)))10088 try sema.maybeErrorUnwrap(&case_block, body, cond_ref, operand_src, true)))
10063 {10089 {
10064 // Set up captures manually to avoid special cases in the main logic.10090 const payload_inst = if (capture != .none) inst: {
10065 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
10066 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;10091 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
10067 const payload_ref: Air.Inst.Ref = payload_ref: {10092 const payload_ref: Air.Inst.Ref = payload_ref: {
10068 const item_val: Value = item_val: {10093 const captured_opv: Value = captured_opv: {
10069 if (!tagged_union_originally) {10094 if (!tagged_union_originally) {
10070 break :item_val item_opv;10095 break :captured_opv item_opv;
10071 }10096 }
10072 if (maybe_operand_opv) |operand_opv| {10097 if (maybe_operand_opv) |operand_opv| {
10073 break :item_val .fromInterned(zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val);10098 break :captured_opv .fromInterned(zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val);
10074 }10099 }
10075 assert(zir_switch.any_maybe_runtime_capture); // there's a payload capture10100 assert(zir_switch.any_maybe_runtime_capture); // there's a payload capture
10076 const operand_val, const operand_ref = switch (operand) {10101 const loaded_operand = try sema.analyzeSwitchOperandLoad(&case_block, operand, operand_src, capture == .by_ref);
10077 .simple => unreachable,10102 break :payload_ref try sema.resolveSwitchPayloadCaptureTaggedUnion(
10078 .loop => |l| load_operand: {
10079 const loaded = try sema.analyzeLoad(block, src, l.operand_alloc, src);
10080 if (l.operand_is_ref) {
10081 const by_val = try sema.analyzeLoad(block, src, loaded, src);
10082 break :load_operand .{ by_val, loaded };
10083 } else {
10084 break :load_operand .{ loaded, .none };
10085 }
10086 },
10087 };
10088 const prong_kind: SwitchProngKind = kind: {
10089 if (is_inline) break :kind .{ .inline_ref = .fromValue(item_opv) };
10090 if (is_special) break :kind .special;
10091 break :kind .{ .item_refs = &.{.fromValue(item_opv)} };
10092 };
10093 break :payload_ref try sema.analyzeSwitchPayloadCapture(
10094 &case_block,10103 &case_block,
10095 operand,10104 loaded_operand,
10096 operand_val,
10097 operand_ref,
10098 operand_ty,
10099 operand_src,10105 operand_src,
10100 block.src(.{ .switch_capture = .{10106 operand_ty,
10101 .switch_node_offset = src_node_offset,10107 item_opv,
10102 .case_idx = index,
10103 } }),
10104 capture == .by_ref,10108 capture == .by_ref,
10105 prong_kind,
10106 validated_switch.else_err_ty,
10107 );10109 );
10108 };10110 };
10109 break :payload_ref switch (capture) {10111 break :payload_ref switch (capture) {
10110 .by_val => .fromValue(item_val),10112 .by_val => .fromValue(captured_opv),
10111 .by_ref => try sema.uavRef(item_val),10113 .by_ref => try sema.uavRef(captured_opv),
10112 .none => unreachable,10114 .none => unreachable,
10113 };10115 };
10114 };10116 };
10115 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
10116 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);10117 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
10117 break :inst payload_inst;10118 break :inst payload_inst;
10118 } else undefined;10119 } else undefined;
...@@ -10120,6 +10121,13 @@ fn analyzeSwitchBlock(...@@ -10120,6 +10121,13 @@ fn analyzeSwitchBlock(
1012010121
10121 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {10122 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
10122 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;10123 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
10124 if (!tagged_union_originally) {
10125 const tag_capture_src = block.src(.{ .switch_tag_capture = .{
10126 .switch_node_offset = src_node_offset,
10127 .case_idx = case_idx,
10128 } });
10129 return sema.failWithInvalidSwitchTagCapture(block, tag_capture_src, operand_ty);
10130 }
10123 sema.inst_map.putAssumeCapacity(tag_inst, .fromValue(item_opv));10131 sema.inst_map.putAssumeCapacity(tag_inst, .fromValue(item_opv));
10124 break :inst tag_inst;10132 break :inst tag_inst;
10125 } else undefined;10133 } else undefined;
...@@ -10405,7 +10413,7 @@ fn finishSwitchBr(...@@ -10405,7 +10413,7 @@ fn finishSwitchBr(
10405 } }),10413 } }),
10406 prong_info.capture,10414 prong_info.capture,
10407 prong_info.has_tag_capture,10415 prong_info.has_tag_capture,
10408 .{ .inline_ref = item_ref },10416 .{ .@"inline" = item_ref },
10409 validated_switch.else_err_ty,10417 validated_switch.else_err_ty,
10410 switch_inst,10418 switch_inst,
10411 zir_switch,10419 zir_switch,
...@@ -10495,7 +10503,7 @@ fn finishSwitchBr(...@@ -10495,7 +10503,7 @@ fn finishSwitchBr(
10495 } }),10503 } }),
10496 prong_info.capture,10504 prong_info.capture,
10497 prong_info.has_tag_capture,10505 prong_info.has_tag_capture,
10498 .{ .inline_ref = item_ref },10506 .{ .@"inline" = item_ref },
10499 validated_switch.else_err_ty,10507 validated_switch.else_err_ty,
10500 switch_inst,10508 switch_inst,
10501 zir_switch,10509 zir_switch,
...@@ -10635,7 +10643,7 @@ fn finishSwitchBr(...@@ -10635,7 +10643,7 @@ fn finishSwitchBr(
10635 } }),10643 } }),
10636 else_case.capture,10644 else_case.capture,
10637 else_case.has_tag_capture,10645 else_case.has_tag_capture,
10638 .{ .inline_ref = item_ref },10646 .{ .@"inline" = item_ref },
10639 validated_switch.else_err_ty,10647 validated_switch.else_err_ty,
10640 switch_inst,10648 switch_inst,
10641 zir_switch,10649 zir_switch,
...@@ -11075,8 +11083,7 @@ fn validateSwitchBlock(...@@ -11075,8 +11083,7 @@ fn validateSwitchBlock(
11075 const has_else = zir_switch.else_case != null;11083 const has_else = zir_switch.else_case != null;
11076 const has_under = zir_switch.has_under;11084 const has_under = zir_switch.has_under;
1107711085
11078 var case_vals: std.ArrayList(Air.Inst.Ref) = .empty;11086 var case_vals: std.ArrayList(Air.Inst.Ref) = try .initCapacity(arena, zir_switch.item_infos.len);
11079 try case_vals.ensureUnusedCapacity(arena, zir_switch.item_infos.len);
1108011087
11081 // Duplicate checking variables later also used for `inline else`.11088 // Duplicate checking variables later also used for `inline else`.
11082 var seen_enum_fields: []?LazySrcLoc = &.{};11089 var seen_enum_fields: []?LazySrcLoc = &.{};
...@@ -11482,10 +11489,10 @@ fn resolveSwitchBlock(...@@ -11482,10 +11489,10 @@ fn resolveSwitchBlock(
11482 // This prong should be unreachable!11489 // This prong should be unreachable!
11483 return .unreachable_value;11490 return .unreachable_value;
11484 }11491 }
11485 const prong_kind: SwitchProngKind = kind: {11492 const prong_items: SwitchProngItems = prong_items: {
11486 if (prong_info.is_inline) break :kind .{ .inline_ref = cond_ref };11493 if (prong_info.is_inline) break :prong_items .{ .@"inline" = cond_ref };
11487 if (range_refs.len > 0) break :kind .has_ranges;11494 if (range_refs.len > 0) break :prong_items .has_ranges;
11488 break :kind .{ .item_refs = item_refs };11495 break :prong_items .{ .item_refs = item_refs };
11489 };11496 };
11490 return sema.resolveSwitchProng(11497 return sema.resolveSwitchProng(
11491 block,11498 block,
...@@ -11499,7 +11506,7 @@ fn resolveSwitchBlock(...@@ -11499,7 +11506,7 @@ fn resolveSwitchBlock(
11499 } }),11506 } }),
11500 prong_info.capture,11507 prong_info.capture,
11501 prong_info.has_tag_capture,11508 prong_info.has_tag_capture,
11502 prong_kind,11509 prong_items,
11503 validated_switch.else_err_ty,11510 validated_switch.else_err_ty,
11504 merges,11511 merges,
11505 switch_inst,11512 switch_inst,
...@@ -11513,8 +11520,8 @@ fn resolveSwitchBlock(...@@ -11513,8 +11520,8 @@ fn resolveSwitchBlock(
11513 if ((try sema.compareAll(cond_val, .gte, first_val, item_ty)) and11520 if ((try sema.compareAll(cond_val, .gte, first_val, item_ty)) and
11514 (try sema.compareAll(cond_val, .lte, last_val, item_ty)))11521 (try sema.compareAll(cond_val, .lte, last_val, item_ty)))
11515 {11522 {
11516 const prong_kind: SwitchProngKind = if (prong_info.is_inline)11523 const prong_items: SwitchProngItems = if (prong_info.is_inline)
11517 .{ .inline_ref = cond_ref }11524 .{ .@"inline" = cond_ref }
11518 else11525 else
11519 .has_ranges;11526 .has_ranges;
11520 return sema.resolveSwitchProng(11527 return sema.resolveSwitchProng(
...@@ -11529,7 +11536,7 @@ fn resolveSwitchBlock(...@@ -11529,7 +11536,7 @@ fn resolveSwitchBlock(
11529 } }),11536 } }),
11530 prong_info.capture,11537 prong_info.capture,
11531 prong_info.has_tag_capture,11538 prong_info.has_tag_capture,
11532 prong_kind,11539 prong_items,
11533 validated_switch.else_err_ty,11540 validated_switch.else_err_ty,
11534 merges,11541 merges,
11535 switch_inst,11542 switch_inst,
...@@ -11548,8 +11555,8 @@ fn resolveSwitchBlock(...@@ -11548,8 +11555,8 @@ fn resolveSwitchBlock(
1154811555
11549 if (else_is_named_only and item_ty.enumTagFieldIndex(cond_val, zcu) != null) {11556 if (else_is_named_only and item_ty.enumTagFieldIndex(cond_val, zcu) != null) {
11550 assert(item_ty.isNonexhaustiveEnum(zcu));11557 assert(item_ty.isNonexhaustiveEnum(zcu));
11551 const prong_kind: SwitchProngKind = if (else_case.is_inline)11558 const prong_items: SwitchProngItems = if (else_case.is_inline)
11552 .{ .inline_ref = cond_ref }11559 .{ .@"inline" = cond_ref }
11553 else11560 else
11554 .special;11561 .special;
11555 return sema.resolveSwitchProng(11562 return sema.resolveSwitchProng(
...@@ -11564,7 +11571,7 @@ fn resolveSwitchBlock(...@@ -11564,7 +11571,7 @@ fn resolveSwitchBlock(
11564 } }),11571 } }),
11565 else_case.capture,11572 else_case.capture,
11566 else_case.has_tag_capture,11573 else_case.has_tag_capture,
11567 prong_kind,11574 prong_items,
11568 validated_switch.else_err_ty,11575 validated_switch.else_err_ty,
11569 merges,11576 merges,
11570 switch_inst,11577 switch_inst,
...@@ -11588,8 +11595,8 @@ fn resolveSwitchBlock(...@@ -11588,8 +11595,8 @@ fn resolveSwitchBlock(
11588 return .unreachable_value;11595 return .unreachable_value;
11589 }11596 }
11590 }11597 }
11591 const prong_kind: SwitchProngKind = if (is_inline)11598 const prong_items: SwitchProngItems = if (is_inline)
11592 .{ .inline_ref = cond_ref }11599 .{ .@"inline" = cond_ref }
11593 else11600 else
11594 .special;11601 .special;
11595 return sema.resolveSwitchProng(11602 return sema.resolveSwitchProng(
...@@ -11604,7 +11611,7 @@ fn resolveSwitchBlock(...@@ -11604,7 +11611,7 @@ fn resolveSwitchBlock(
11604 } }),11611 } }),
11605 capture,11612 capture,
11606 has_tag_capture,11613 has_tag_capture,
11607 prong_kind,11614 prong_items,
11608 validated_switch.else_err_ty,11615 validated_switch.else_err_ty,
11609 merges,11616 merges,
11610 switch_inst,11617 switch_inst,
...@@ -11640,14 +11647,59 @@ const SwitchOperand = union(enum) {...@@ -11640,14 +11647,59 @@ const SwitchOperand = union(enum) {
11640 },11647 },
11641};11648};
1164211649
11643const SwitchProngKind = union(enum) {11650fn analyzeSwitchOperandLoad(
11644 /// Prefer populating this field over the others, if possible.11651 sema: *Sema,
11645 inline_ref: Air.Inst.Ref,11652 block: *Block,
11653 operand: SwitchOperand,
11654 operand_src: LazySrcLoc,
11655 by_ref: bool,
11656) CompileError!Air.Inst.Ref {
11657 switch (operand) {
11658 .simple => |s| {
11659 if (by_ref) {
11660 assert(s.by_ref != .none);
11661 return s.by_ref;
11662 } else {
11663 return s.by_val;
11664 }
11665 },
11666 .loop => |l| {
11667 const loaded = try sema.analyzeLoad(block, operand_src, l.operand_alloc, operand_src);
11668 assert(loaded != .none); // there are no captures, so no need to load the switch operand
11669 if (by_ref) {
11670 assert(l.operand_is_ref);
11671 return loaded;
11672 }
11673 return if (l.operand_is_ref)
11674 try sema.analyzeLoad(block, operand_src, loaded, operand_src)
11675 else
11676 loaded;
11677 },
11678 }
11679}
11680
11681const SwitchProngItems = union(enum) {
11682 @"inline": Air.Inst.Ref,
11646 item_refs: []const Air.Inst.Ref,11683 item_refs: []const Air.Inst.Ref,
11647 has_ranges,11684 has_ranges,
11648 special,11685 special,
11649};11686};
1165011687
11688/// A switch capture is comptime-known if it is `inline` and/or it is a by-value
11689/// capture of a prong with a single item.
11690fn resolveSwitchCaptureFromProngItems(
11691 sema: *Sema,
11692 prong_items: SwitchProngItems,
11693 by_ref: bool,
11694) ?Value {
11695 const ref: Air.Inst.Ref = switch (prong_items) {
11696 .@"inline" => |ref| ref,
11697 .item_refs => |refs| if (refs.len == 1 and !by_ref) refs[0] else return null,
11698 .has_ranges, .special => return null,
11699 };
11700 return sema.resolveValue(ref).?;
11701}
11702
11651/// Resolve a switch prong which is determined at comptime to have no peers.11703/// Resolve a switch prong which is determined at comptime to have no peers.
11652/// Sets up captures as needed. Uses `analyzeBodyRuntimeBreak`.11704/// Sets up captures as needed. Uses `analyzeBodyRuntimeBreak`.
11653fn resolveSwitchProng(11705fn resolveSwitchProng(
...@@ -11661,7 +11713,7 @@ fn resolveSwitchProng(...@@ -11661,7 +11713,7 @@ fn resolveSwitchProng(
11661 capture_src: LazySrcLoc,11713 capture_src: LazySrcLoc,
11662 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,11714 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
11663 has_tag_capture: bool,11715 has_tag_capture: bool,
11664 kind: SwitchProngKind,11716 prong_items: SwitchProngItems,
11665 else_err_ty: ?Type,11717 else_err_ty: ?Type,
11666 merges: *Block.Merges,11718 merges: *Block.Merges,
11667 switch_inst: Zir.Inst.Index,11719 switch_inst: Zir.Inst.Index,
...@@ -11676,36 +11728,28 @@ fn resolveSwitchProng(...@@ -11676,36 +11728,28 @@ fn resolveSwitchProng(
11676 const parent_hint = sema.branch_hint;11728 const parent_hint = sema.branch_hint;
11677 defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;11729 defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
1167811730
11679 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {11731 const analyzed_captures = try sema.analyzeSwitchCaptures(
11732 child_block,
11733 operand,
11734 operand_src,
11735 sema.typeOf(operand.simple.by_val),
11736 capture_src,
11737 capture,
11738 has_tag_capture,
11739 prong_items,
11740 else_err_ty,
11741 );
11742
11743 const payload_inst = if (capture != .none) inst: {
11680 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;11744 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
11681 const payload_ref = try sema.analyzeSwitchPayloadCapture(11745 sema.inst_map.putAssumeCapacity(payload_inst, analyzed_captures.payload_ref);
11682 child_block,
11683 operand,
11684 operand.simple.by_val,
11685 operand.simple.by_ref,
11686 sema.typeOf(operand.simple.by_val),
11687 operand_src,
11688 capture_src,
11689 capture == .by_ref,
11690 kind,
11691 else_err_ty,
11692 );
11693 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
11694 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
11695 break :inst payload_inst;11746 break :inst payload_inst;
11696 } else undefined;11747 } else undefined;
11697 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));11748 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1169811749
11699 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {11750 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
11700 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;11751 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
11701 const tag_ref = try sema.analyzeSwitchTagCapture(11752 sema.inst_map.putAssumeCapacity(tag_inst, analyzed_captures.tag_ref);
11702 child_block,
11703 operand.simple.by_val,
11704 sema.typeOf(operand.simple.by_val),
11705 capture_src,
11706 kind,
11707 );
11708 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
11709 break :inst tag_inst;11753 break :inst tag_inst;
11710 } else undefined;11754 } else undefined;
11711 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));11755 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
...@@ -11751,7 +11795,7 @@ fn analyzeSwitchProng(...@@ -11751,7 +11795,7 @@ fn analyzeSwitchProng(
11751 capture_src: LazySrcLoc,11795 capture_src: LazySrcLoc,
11752 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,11796 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
11753 has_tag_capture: bool,11797 has_tag_capture: bool,
11754 kind: SwitchProngKind,11798 prong_items: SwitchProngItems,
11755 else_err_ty: ?Type,11799 else_err_ty: ?Type,
11756 switch_inst: Zir.Inst.Index,11800 switch_inst: Zir.Inst.Index,
11757 zir_switch: *const Zir.UnwrappedSwitchBlock,11801 zir_switch: *const Zir.UnwrappedSwitchBlock,
...@@ -11772,77 +11816,28 @@ fn analyzeSwitchProng(...@@ -11772,77 +11816,28 @@ fn analyzeSwitchProng(
11772 }11816 }
11773 }11817 }
1177411818
11775 const need_load: bool = need_load: {11819 const analyzed_captures = try sema.analyzeSwitchCaptures(
11776 if (capture == .none and !has_tag_capture) {11820 case_block,
11777 // No need to load the operand for this prong!11821 operand,
11778 break :need_load false;11822 operand_src,
11779 }11823 operand_ty,
11780 if (capture != .none and operand_ty.zigTypeTag(zcu) == .@"union" and11824 capture_src,
11781 operand_ty.containerLayout(zcu) != .@"packed")11825 capture,
11782 {11826 has_tag_capture,
11783 // Non-OPV tagged union payload captures are always runtime-known.11827 prong_items,
11784 break :need_load true;11828 else_err_ty,
11785 }11829 );
11786 if (kind == .inline_ref) {
11787 // `inline_ref` *is* the (comptime-known) capture.
11788 break :need_load false;
11789 }
11790 assert(zir_switch.any_maybe_runtime_capture); // should have caught everything else by now
11791 if (capture != .by_ref and
11792 kind == .item_refs and kind.item_refs.len == 1)
11793 {
11794 // Capture is comptime-known because it's the only prong item
11795 break :need_load false;
11796 }
11797 break :need_load true;
11798 };
11799
11800 const operand_val: Air.Inst.Ref, const operand_ptr: Air.Inst.Ref = load_operand: {
11801 if (!need_load) break :load_operand .{ .none, .none };
11802 switch (operand) {
11803 .simple => |s| break :load_operand .{ s.by_val, s.by_ref },
11804 .loop => |l| {
11805 const loaded = try sema.analyzeLoad(case_block, operand_src, l.operand_alloc, operand_src);
11806 if (l.operand_is_ref) {
11807 const by_val = try sema.analyzeLoad(case_block, operand_src, loaded, operand_src);
11808 break :load_operand .{ by_val, loaded };
11809 } else {
11810 break :load_operand .{ loaded, .none };
11811 }
11812 },
11813 }
11814 };
1181511830
11816 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {11831 const payload_inst = if (capture != .none) inst: {
11817 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;11832 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
11818 const payload_ref = try sema.analyzeSwitchPayloadCapture(11833 sema.inst_map.putAssumeCapacity(payload_inst, analyzed_captures.payload_ref);
11819 case_block,
11820 operand,
11821 operand_val,
11822 operand_ptr,
11823 operand_ty,
11824 operand_src,
11825 capture_src,
11826 capture == .by_ref,
11827 kind,
11828 else_err_ty,
11829 );
11830 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
11831 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
11832 break :inst payload_inst;11834 break :inst payload_inst;
11833 } else undefined;11835 } else undefined;
11834 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));11836 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1183511837
11836 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {11838 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
11837 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;11839 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
11838 const tag_ref = try sema.analyzeSwitchTagCapture(11840 sema.inst_map.putAssumeCapacity(tag_inst, analyzed_captures.tag_ref);
11839 case_block,
11840 operand_val,
11841 operand_ty,
11842 capture_src,
11843 kind,
11844 );
11845 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
11846 break :inst tag_inst;11841 break :inst tag_inst;
11847 } else undefined;11842 } else undefined;
11848 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));11843 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
...@@ -11853,157 +11848,295 @@ fn analyzeSwitchProng(...@@ -11853,157 +11848,295 @@ fn analyzeSwitchProng(
11853 return sema.analyzeBodyRuntimeBreak(case_block, prong_body);11848 return sema.analyzeBodyRuntimeBreak(case_block, prong_body);
11854}11849}
1185511850
11856fn analyzeSwitchTagCapture(11851fn analyzeSwitchCaptures(
11857 sema: *Sema,11852 sema: *Sema,
11858 case_block: *Block,11853 case_block: *Block,
11859 /// May be `none` if this is an inline capture or if `kind.item_refs.len == 1`.11854 operand: SwitchOperand,
11860 operand_val: Air.Inst.Ref,11855 operand_src: LazySrcLoc,
11861 operand_ty: Type,11856 operand_ty: Type,
11862 capture_src: LazySrcLoc,11857 capture_src: LazySrcLoc,
11863 kind: SwitchProngKind,11858 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
11864) CompileError!Air.Inst.Ref {11859 has_tag_capture: bool,
11860 prong_items: SwitchProngItems,
11861 else_err_ty: ?Type,
11862) CompileError!struct {
11863 payload_ref: Air.Inst.Ref,
11864 tag_ref: Air.Inst.Ref,
11865} {
11865 const pt = sema.pt;11866 const pt = sema.pt;
11866 const zcu = pt.zcu;11867 const zcu = pt.zcu;
1186711868
11868 const tag_capture_src: LazySrcLoc = .{11869 if (operand_ty.zigTypeTag(zcu) == .@"union" and
11869 .base_node_inst = capture_src.base_node_inst,11870 operand_ty.containerLayout(zcu) != .@"packed")
11870 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },11871 {
11871 };11872 if (capture == .none) {
11873 const tag_ref: Air.Inst.Ref = tag_ref: {
11874 if (!has_tag_capture) break :tag_ref .none;
11875 if (sema.resolveSwitchCaptureFromProngItems(prong_items, false)) |tag_val| {
11876 break :tag_ref .fromValue(tag_val);
11877 }
11878 const loaded_operand = try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, false);
11879 break :tag_ref try sema.unionToTag(case_block, loaded_operand);
11880 };
11881 return .{ .payload_ref = .none, .tag_ref = tag_ref };
11882 }
1187211883
11873 if (operand_ty.zigTypeTag(zcu) != .@"union") {11884 // We always have to load the operand for tagged union payload captures
11874 return sema.fail(case_block, tag_capture_src, "cannot capture tag of non-union type '{f}'", .{11885 // since we can't derive the payload value from the tag (except for OPV
11875 operand_ty.fmt(pt),11886 // types, for which the load is always basically a noop anyway).
11876 });11887
11877 }11888 const loaded_operand = try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, capture == .by_ref);
11878 if (operand_ty.containerLayout(zcu) == .@"packed") {11889
11879 return sema.fail(case_block, tag_capture_src, "cannot capture tag of packed union", .{});11890 if (sema.resolveSwitchCaptureFromProngItems(prong_items, capture == .by_ref)) |tag_val| {
11891 const payload_ref = try sema.resolveSwitchPayloadCaptureTaggedUnion(
11892 case_block,
11893 loaded_operand,
11894 operand_src,
11895 operand_ty,
11896 tag_val,
11897 capture == .by_ref,
11898 );
11899 const tag_ref: Air.Inst.Ref = if (has_tag_capture) .fromValue(tag_val) else .none;
11900 return .{ .payload_ref = payload_ref, .tag_ref = tag_ref };
11901 }
11902
11903 const payload_ref = try sema.analyzeSwitchPayloadCaptureTaggedUnion(
11904 case_block,
11905 operand,
11906 loaded_operand,
11907 operand_src,
11908 operand_ty,
11909 capture == .by_ref,
11910 capture_src,
11911 prong_items,
11912 );
11913
11914 const tag_ref: Air.Inst.Ref = tag_ref: {
11915 if (!has_tag_capture) break :tag_ref .none;
11916 const operand_val = switch (capture) {
11917 .none => unreachable, // handled above
11918 .by_val => loaded_operand,
11919 .by_ref => try sema.analyzeLoad(case_block, operand_src, loaded_operand, operand_src),
11920 };
11921 break :tag_ref try sema.unionToTag(case_block, operand_val);
11922 };
11923
11924 assert(!sema.typeOf(payload_ref).isNoReturn(zcu));
11925 return .{ .payload_ref = payload_ref, .tag_ref = tag_ref };
11880 }11926 }
11881 switch (kind) {11927
11882 .has_ranges => unreachable,11928 const payload_ref: Air.Inst.Ref = payload_ref: {
11883 .inline_ref => |ref| return ref,11929 if (capture == .none) break :payload_ref .none;
11884 .item_refs => |refs| if (refs.len == 1) return refs[0],11930
11885 .special => {},11931 if (operand_ty.zigTypeTag(zcu) == .error_set) {
11932 // Error captures need to have their type narrowed!
11933
11934 if (capture == .by_ref) {
11935 return sema.fail(
11936 case_block,
11937 capture_src,
11938 "error set cannot be captured by reference",
11939 .{},
11940 );
11941 }
11942 assert(capture == .by_val);
11943
11944 if (sema.resolveSwitchCaptureFromProngItems(prong_items, false)) |err_val| {
11945 const err_name = err_val.getErrorName(zcu).unwrap().?;
11946 break :payload_ref .fromIntern((try pt.intern(.{ .err = .{
11947 .ty = (try pt.singleErrorSetType(err_name)).toIntern(),
11948 .name = err_name,
11949 } })));
11950 }
11951
11952 const loaded_operand = try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, false);
11953
11954 switch (prong_items) {
11955 .@"inline" => unreachable, // handled above
11956 .has_ranges => unreachable, // not possible for error set
11957 .special => {
11958 if (else_err_ty) |err_ty| {
11959 break :payload_ref try sema.bitCast(case_block, err_ty, loaded_operand, operand_src, null);
11960 } else {
11961 try sema.analyzeUnreachable(case_block, operand_src, false);
11962 break :payload_ref .unreachable_value;
11963 }
11964 },
11965 .item_refs => |item_refs| {
11966 var names: InferredErrorSet.NameMap = .{};
11967 try names.ensureUnusedCapacity(sema.arena, item_refs.len);
11968 for (item_refs) |item_ref| {
11969 const item_val = sema.resolveValue(item_ref).?;
11970 names.putAssumeCapacityNoClobber(item_val.getErrorName(zcu).unwrap().?, {});
11971 }
11972 const narrowed_ty = try pt.errorSetFromUnsortedNames(names.keys());
11973 break :payload_ref try sema.bitCast(case_block, narrowed_ty, loaded_operand, operand_src, null);
11974 },
11975 }
11976 }
11977
11978 // We try to make the capture comptime-known based on `prong_items` first:
11979
11980 if (sema.resolveSwitchCaptureFromProngItems(prong_items, capture == .by_ref)) |item_val| {
11981 break :payload_ref switch (capture) {
11982 .none => unreachable, // handled above
11983 .by_val => .fromValue(item_val),
11984 .by_ref => try sema.uavRef(item_val),
11985 };
11986 }
11987
11988 // Otherwise the capture value is just the passed-through value of the
11989 // switch condition (which we might have to load first).
11990
11991 break :payload_ref try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, capture == .by_ref);
11992 };
11993
11994 if (has_tag_capture) {
11995 const tag_capture_src: LazySrcLoc = .{
11996 .base_node_inst = capture_src.base_node_inst,
11997 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
11998 };
11999 return sema.failWithInvalidSwitchTagCapture(case_block, tag_capture_src, operand_ty);
11886 }12000 }
11887 return sema.unionToTag(case_block, operand_val);12001
12002 return .{ .payload_ref = payload_ref, .tag_ref = .none };
11888}12003}
1188912004
11890fn analyzeSwitchPayloadCapture(12005fn resolveSwitchPayloadCaptureTaggedUnion(
11891 sema: *Sema,12006 sema: *Sema,
11892 case_block: *Block,12007 case_block: *Block,
11893 operand: SwitchOperand,12008 loaded_operand: Air.Inst.Ref,
11894 /// Always has to be not-`none` if this is a tagged union payload capture.12009 operand_src: LazySrcLoc,
11895 /// For non-tagged-union captures, this may be `none` if this is an inline
11896 /// capture or if `kind.item_refs.len == 1` and capture is by val.
11897 operand_val: Air.Inst.Ref,
11898 /// May be `none` if `capture_by_ref` is `false` or if `operand_val` is also `none`.
11899 operand_ptr: Air.Inst.Ref,
11900 operand_ty: Type,12010 operand_ty: Type,
12011 tag_val: Value,
12012 capture_by_ref: bool,
12013) CompileError!Air.Inst.Ref {
12014 const pt = sema.pt;
12015 const zcu = pt.zcu;
12016 const ip = &zcu.intern_pool;
12017
12018 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(tag_val, zcu).?);
12019 const union_obj = zcu.typeToUnion(operand_ty).?;
12020 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
12021 const payload_ref: Air.Inst.Ref = payload_ref: {
12022 if (capture_by_ref) {
12023 const operand_ptr_info = sema.typeOf(loaded_operand).ptrInfo(zcu);
12024 const ptr_field_ty = try pt.ptrType(.{
12025 .child = field_ty.toIntern(),
12026 .flags = .{
12027 .is_const = operand_ptr_info.flags.is_const,
12028 .is_volatile = operand_ptr_info.flags.is_volatile,
12029 .address_space = operand_ptr_info.flags.address_space,
12030 },
12031 });
12032 break :payload_ref try case_block.addStructFieldPtr(loaded_operand, field_index, ptr_field_ty);
12033 }
12034 if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |union_val| {
12035 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
12036 break :payload_ref .fromIntern(tag_and_val.val);
12037 }
12038 if (try field_ty.onePossibleValue(pt)) |opv| break :payload_ref .fromValue(opv);
12039 break :payload_ref try case_block.addStructFieldVal(loaded_operand, field_index, field_ty);
12040 };
12041 assert(!sema.typeOf(payload_ref).isNoReturn(zcu));
12042 return payload_ref;
12043}
12044
12045fn analyzeSwitchPayloadCaptureTaggedUnion(
12046 sema: *Sema,
12047 case_block: *Block,
12048 operand: SwitchOperand,
12049 loaded_operand: Air.Inst.Ref,
11901 operand_src: LazySrcLoc,12050 operand_src: LazySrcLoc,
11902 capture_src: LazySrcLoc,12051 operand_ty: Type,
11903 capture_by_ref: bool,12052 capture_by_ref: bool,
11904 kind: SwitchProngKind,12053 capture_src: LazySrcLoc,
11905 else_err_ty: ?Type,12054 prong_items: SwitchProngItems,
11906) CompileError!Air.Inst.Ref {12055) CompileError!Air.Inst.Ref {
11907 const pt = sema.pt;12056 const pt = sema.pt;
11908 const zcu = pt.zcu;12057 const zcu = pt.zcu;
11909 const ip = &zcu.intern_pool;12058 const ip = &zcu.intern_pool;
12059 const gpa = sema.gpa;
12060
12061 const item_refs: []const Air.Inst.Ref = switch (prong_items) {
12062 .@"inline" => unreachable, // handled above
12063 .has_ranges => unreachable, // not possible for tagged union
12064 .special => return loaded_operand,
12065 .item_refs => |item_refs| item_refs,
12066 };
1191012067
11911 const switch_node_offset = operand_src.offset.node_offset_switch_operand;12068 const switch_node_offset = operand_src.offset.node_offset_switch_operand;
1191212069
11913 const tagged_union_originally = operand_ty.zigTypeTag(zcu) == .@"union" and12070 const union_obj = zcu.typeToUnion(operand_ty).?;
11914 operand_ty.containerLayout(zcu) != .@"packed";
11915 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
1191612071
11917 if (err_set and capture_by_ref) {12072 const first_item_val = sema.resolveValue(item_refs[0]).?;
11918 return sema.fail(12073 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;
11919 case_block,12074 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);
11920 capture_src,
11921 "error set cannot be captured by reference",
11922 .{},
11923 );
11924 }
1192512075
11926 if (kind == .inline_ref) {12076 const field_indices = try sema.arena.alloc(u32, item_refs.len);
11927 const item_val = sema.resolveValue(kind.inline_ref).?;12077 for (item_refs, field_indices) |item_ref, *field_idx| {
11928 if (tagged_union_originally) {12078 const item_val = sema.resolveValue(item_ref).?;
11929 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);12079 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
11930 const union_obj = zcu.typeToUnion(operand_ty).?;
11931 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
11932 if (capture_by_ref) {
11933 const operand_ptr_info = sema.typeOf(operand_ptr).ptrInfo(zcu);
11934 const ptr_field_ty = try pt.ptrType(.{
11935 .child = field_ty.toIntern(),
11936 .flags = .{
11937 .is_const = operand_ptr_info.flags.is_const,
11938 .is_volatile = operand_ptr_info.flags.is_volatile,
11939 .address_space = operand_ptr_info.flags.address_space,
11940 },
11941 });
11942 return case_block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);
11943 } else {
11944 if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |union_val| {
11945 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
11946 return .fromIntern(tag_and_val.val);
11947 }
11948 if (try field_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
11949 return case_block.addStructFieldVal(operand_val, field_index, field_ty);
11950 }
11951 } else if (capture_by_ref) {
11952 return sema.uavRef(item_val);
11953 } else {
11954 return kind.inline_ref;
11955 }
11956 }12080 }
1195712081
11958 if (kind == .special) {12082 // Fast path: if all the operands are the same type already, we don't need to hit
11959 if (err_set) {12083 // PTR! This will also allow us to emit simpler code.
11960 if (else_err_ty) |err_ty| {12084 const same_types = for (field_indices[1..]) |field_idx| {
11961 return sema.bitCast(case_block, err_ty, operand_val, operand_src, null);12085 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
11962 } else {12086 if (!field_ty.eql(first_field_ty, zcu)) break false;
11963 try sema.analyzeUnreachable(case_block, operand_src, false);12087 } else true;
11964 return .unreachable_value;
11965 }
11966 }
11967 if (capture_by_ref) {
11968 return operand_ptr;
11969 }
11970 return operand_val;
11971 }
1197212088
11973 if (tagged_union_originally) {12089 const capture_ty: Type = capture_ty: {
11974 const case_vals = kind.item_refs;12090 if (same_types) break :capture_ty first_field_ty;
1197512091 // We need values to run PTR on, so make a bunch of undef constants.
11976 const union_obj = zcu.typeToUnion(operand_ty).?;12092 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, item_refs.len);
11977 const first_item_val = sema.resolveValue(case_vals[0]).?;12093 for (dummy_captures, field_indices) |*dummy, field_idx| {
1197812094 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
11979 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;12095 dummy.* = try pt.undefRef(field_ty);
11980 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);12096 }
1198112097
11982 const field_indices = try sema.arena.alloc(u32, case_vals.len);12098 const item_srcs = try sema.arena.alloc(?LazySrcLoc, item_refs.len);
11983 for (case_vals, field_indices) |item, *field_idx| {12099 for (item_srcs, 0..) |*item_src, item_i| {
11984 const item_val = sema.resolveValue(item).?;12100 item_src.* = .{
11985 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;12101 .base_node_inst = capture_src.base_node_inst,
12102 .offset = .{ .switch_case_item = .{
12103 .switch_node_offset = switch_node_offset,
12104 .case_idx = capture_src.offset.switch_capture.case_idx,
12105 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
12106 } },
12107 };
11986 }12108 }
1198712109
11988 // Fast path: if all the operands are the same type already, we don't need to hit12110 break :capture_ty sema.resolvePeerTypes(
11989 // PTR! This will also allow us to emit simpler code.12111 case_block,
11990 const same_types = for (field_indices[1..]) |field_idx| {12112 capture_src,
11991 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12113 dummy_captures,
11992 if (!field_ty.eql(first_field_ty, zcu)) break false;12114 .{ .override = item_srcs },
11993 } else true;12115 ) catch |err| switch (err) {
12116 error.AnalysisFail => {
12117 const msg = sema.err orelse return error.AnalysisFail;
12118 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12119 return error.AnalysisFail;
12120 },
12121 else => |e| return e,
12122 };
12123 };
1199412124
11995 const capture_ty: Type = capture_ty: {12125 // By-reference captures have some further restrictions which make them easier to emit
11996 if (same_types) break :capture_ty first_field_ty;12126 if (capture_by_ref) {
12127 const operand_ptr_ty = sema.typeOf(loaded_operand);
12128 const capture_ptr_ty = resolve: {
12129 // By-ref captures of hetereogeneous types are only allowed if all field
12130 // pointer types are peer resolvable to each other.
11997 // We need values to run PTR on, so make a bunch of undef constants.12131 // We need values to run PTR on, so make a bunch of undef constants.
11998 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);12132 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, item_refs.len);
11999 for (dummy_captures, field_indices) |*dummy, field_idx| {12133 for (field_indices, dummy_captures) |field_index, *dummy| {
12000 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12134 const field_ptr_ty = try operand_ptr_ty.fieldPtrType(field_index, pt);
12001 dummy.* = try pt.undefRef(field_ty);12135 dummy.* = try pt.undefRef(field_ptr_ty);
12002 }12136 }
1200312137 const item_srcs = try sema.arena.alloc(?LazySrcLoc, item_refs.len);
12004 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);12138 for (item_srcs, 0..) |*item_src, item_i| {
12005 for (case_srcs, 0..) |*case_src, item_i| {12139 item_src.* = .{
12006 case_src.* = .{
12007 .base_node_inst = capture_src.base_node_inst,12140 .base_node_inst = capture_src.base_node_inst,
12008 .offset = .{ .switch_case_item = .{12141 .offset = .{ .switch_case_item = .{
12009 .switch_node_offset = switch_node_offset,12142 .switch_node_offset = switch_node_offset,
...@@ -12013,14 +12146,15 @@ fn analyzeSwitchPayloadCapture(...@@ -12013,14 +12146,15 @@ fn analyzeSwitchPayloadCapture(
12013 };12146 };
12014 }12147 }
1201512148
12016 break :capture_ty sema.resolvePeerTypes(12149 break :resolve sema.resolvePeerTypes(
12017 case_block,12150 case_block,
12018 capture_src,12151 capture_src,
12019 dummy_captures,12152 dummy_captures,
12020 .{ .override = case_srcs },12153 .{ .override = item_srcs },
12021 ) catch |err| switch (err) {12154 ) catch |err| switch (err) {
12022 error.AnalysisFail => {12155 error.AnalysisFail => {
12023 const msg = sema.err orelse return error.AnalysisFail;12156 const msg = sema.err orelse return error.AnalysisFail;
12157 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
12024 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});12158 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12025 return error.AnalysisFail;12159 return error.AnalysisFail;
12026 },12160 },
...@@ -12028,262 +12162,188 @@ fn analyzeSwitchPayloadCapture(...@@ -12028,262 +12162,188 @@ fn analyzeSwitchPayloadCapture(
12028 };12162 };
12029 };12163 };
1203012164
12031 // By-reference captures have some further restrictions which make them easier to emit12165 if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |op_ptr_val| {
12032 if (capture_by_ref) {12166 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
12033 const operand_ptr_ty = sema.typeOf(operand_ptr);12167 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
12034 const capture_ptr_ty = resolve: {12168 return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
12035 // By-ref captures of hetereogeneous types are only allowed if all field
12036 // pointer types are peer resolvable to each other.
12037 // We need values to run PTR on, so make a bunch of undef constants.
12038 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
12039 for (field_indices, dummy_captures) |field_index, *dummy| {
12040 const field_ptr_ty = try operand_ptr_ty.fieldPtrType(field_index, pt);
12041 dummy.* = try pt.undefRef(field_ptr_ty);
12042 }
12043 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
12044 for (case_srcs, 0..) |*case_src, item_i| {
12045 case_src.* = .{
12046 .base_node_inst = capture_src.base_node_inst,
12047 .offset = .{ .switch_case_item = .{
12048 .switch_node_offset = switch_node_offset,
12049 .case_idx = capture_src.offset.switch_capture.case_idx,
12050 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
12051 } },
12052 };
12053 }
12054
12055 break :resolve sema.resolvePeerTypes(
12056 case_block,
12057 capture_src,
12058 dummy_captures,
12059 .{ .override = case_srcs },
12060 ) catch |err| switch (err) {
12061 error.AnalysisFail => {
12062 const msg = sema.err orelse return error.AnalysisFail;
12063 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
12064 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12065 return error.AnalysisFail;
12066 },
12067 else => |e| return e,
12068 };
12069 };
12070
12071 if (try sema.resolveDefinedValue(case_block, operand_src, operand_ptr)) |op_ptr_val| {
12072 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
12073 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
12074 return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
12075 }
12076
12077 try sema.requireRuntimeBlock(case_block, operand_src, null);
12078 return case_block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);
12079 }
12080
12081 if (try capture_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
12082
12083 if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |operand_val_val| {
12084 if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);
12085 const union_val = ip.indexToKey(operand_val_val.toIntern()).un;
12086 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
12087 const uncoerced: Air.Inst.Ref = .fromIntern(union_val.val);
12088 return sema.coerce(case_block, capture_ty, uncoerced, operand_src);
12089 }12169 }
1209012170
12091 try sema.requireRuntimeBlock(case_block, operand_src, null);12171 try sema.requireRuntimeBlock(case_block, operand_src, null);
12172 return case_block.addStructFieldPtr(loaded_operand, first_field_index, capture_ptr_ty);
12173 }
1209212174
12093 if (same_types) {12175 if (try capture_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
12094 return case_block.addStructFieldVal(operand_val, first_field_index, capture_ty);
12095 }
1209612176
12097 // We may have to emit a switch block which coerces the operand to the capture type.12177 if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |operand_val| {
12098 // If we can, try to avoid that using in-memory coercions.12178 if (operand_val.isUndef(zcu)) return pt.undefRef(capture_ty);
12099 const first_non_imc = in_mem: {12179 const union_val = ip.indexToKey(operand_val.toIntern()).un;
12100 for (field_indices, 0..) |field_idx, i| {12180 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
12101 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12181 const uncoerced: Air.Inst.Ref = .fromIntern(union_val.val);
12102 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {12182 return sema.coerce(case_block, capture_ty, uncoerced, operand_src);
12103 break :in_mem i;12183 }
12104 }
12105 }
12106 // All fields are in-memory coercible to the resolved type!
12107 // Just take the first field and bitcast the result.
12108 const uncoerced = try case_block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
12109 return case_block.addBitCast(capture_ty, uncoerced);
12110 };
1211112184
12112 // By-val capture with heterogeneous types which are not all in-memory coercible to12185 try sema.requireRuntimeBlock(case_block, operand_src, null);
12113 // the resolved capture type. We finally have to fall back to the ugly method.
1211412186
12115 // However, let's first track which operands are in-memory coercible. There may well12187 if (same_types) {
12116 // be several, and we can squash all of these cases into the same switch prong using12188 return case_block.addStructFieldVal(loaded_operand, first_field_index, capture_ty);
12117 // a simple bitcast. We'll make this the 'else' prong.12189 }
1211812190
12119 var in_mem_coercible: std.bit_set.Dynamic = try .initFull(sema.arena, field_indices.len);12191 // We may have to emit a switch block which coerces the operand to the capture type.
12120 in_mem_coercible.unset(first_non_imc);12192 // If we can, try to avoid that using in-memory coercions.
12121 {12193 const first_non_imc = in_mem: {
12122 const next = first_non_imc + 1;12194 for (field_indices, 0..) |field_idx, i| {
12123 for (field_indices[next..], next..) |field_idx, i| {12195 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12124 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12196 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12125 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {12197 break :in_mem i;
12126 in_mem_coercible.unset(i);
12127 }
12128 }12198 }
12129 }12199 }
12200 // All fields are in-memory coercible to the resolved type!
12201 // Just take the first field and bitcast the result.
12202 const uncoerced = try case_block.addStructFieldVal(loaded_operand, first_field_index, first_field_ty);
12203 return case_block.addBitCast(capture_ty, uncoerced);
12204 };
1213012205
12131 const capture_block_inst = try case_block.addInstAsIndex(.{12206 // By-val capture with heterogeneous types which are not all in-memory coercible to
12132 .tag = .block,12207 // the resolved capture type. We finally have to fall back to the ugly method.
12133 .data = .{
12134 .ty_pl = .{
12135 .ty = .fromType(capture_ty),
12136 .payload = undefined, // updated below
12137 },
12138 },
12139 });
12140
12141 const prong_count = field_indices.len - in_mem_coercible.count();
1214212208
12143 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints12209 // However, let's first track which operands are in-memory coercible. There may well
12144 var cases_extra = try std.array_list.Managed(u32).initCapacity(sema.gpa, estimated_extra);12210 // be several, and we can squash all of these cases into the same switch prong using
12145 defer cases_extra.deinit();12211 // a simple bitcast. We'll make this the 'else' prong.
1214612212
12147 {12213 var in_mem_coercible: std.bit_set.Dynamic = try .initFull(sema.arena, field_indices.len);
12148 // All branch hints are `.none`, so just add zero elems.12214 in_mem_coercible.unset(first_non_imc);
12149 comptime assert(@intFromEnum(std.lang.BranchHint.none) == 0);12215 {
12150 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;12216 const next = first_non_imc + 1;
12151 try cases_extra.appendNTimes(0, need_elems);12217 for (field_indices[next..], next..) |field_idx, i| {
12218 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12219 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12220 in_mem_coercible.unset(i);
12221 }
12152 }12222 }
12223 }
1215312224
12154 {12225 const capture_block_inst = try case_block.addInstAsIndex(.{
12155 // Non-bitcast cases12226 .tag = .block,
12156 var it = in_mem_coercible.iterator(.{ .kind = .unset });12227 .data = .{
12157 while (it.next()) |idx| {12228 .ty_pl = .{
12158 var coerce_block = case_block.makeSubBlock();12229 .ty = .fromType(capture_ty),
12159 defer coerce_block.instructions.deinit(sema.gpa);12230 .payload = undefined, // updated below
12231 },
12232 },
12233 });
1216012234
12161 const case_src: LazySrcLoc = .{12235 const prong_count = field_indices.len - in_mem_coercible.count();
12162 .base_node_inst = capture_src.base_node_inst,
12163 .offset = .{ .switch_case_item = .{
12164 .switch_node_offset = switch_node_offset,
12165 .case_idx = capture_src.offset.switch_capture.case_idx,
12166 .item_idx = .{ .kind = .single, .value = @intCast(idx) },
12167 } },
12168 };
1216912236
12170 const field_idx = field_indices[idx];12237 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
12171 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);12238 var cases_extra = try std.ArrayList(u32).initCapacity(gpa, estimated_extra);
12172 const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);12239 defer cases_extra.deinit(gpa);
12173 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
12174 _ = try coerce_block.addBr(capture_block_inst, coerced);
1217512240
12176 try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".field_names.len +12241 {
12177 1 + // `item`, no ranges12242 // All branch hints are `.none`, so just add zero elems.
12178 coerce_block.instructions.items.len);12243 comptime assert(@intFromEnum(std.lang.BranchHint.none) == 0);
12179 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{12244 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
12180 .items_len = 1,12245 try cases_extra.appendNTimes(gpa, 0, need_elems);
12181 .ranges_len = 0,12246 }
12182 .body_len = @intCast(coerce_block.instructions.items.len),12247
12183 }));12248 {
12184 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item12249 // Non-bitcast cases
12185 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body12250 var it = in_mem_coercible.iterator(.{ .kind = .unset });
12186 }12251 while (it.next()) |idx| {
12187 }
12188 const else_body_len = len: {
12189 // 'else' prong uses a bitcast
12190 var coerce_block = case_block.makeSubBlock();12252 var coerce_block = case_block.makeSubBlock();
12191 defer coerce_block.instructions.deinit(sema.gpa);12253 defer coerce_block.instructions.deinit(sema.gpa);
1219212254
12193 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;12255 const case_src: LazySrcLoc = .{
12194 const first_imc_field_idx = field_indices[first_imc_item_idx];12256 .base_node_inst = capture_src.base_node_inst,
12195 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);12257 .offset = .{ .switch_case_item = .{
12196 const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);12258 .switch_node_offset = switch_node_offset,
12197 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);12259 .case_idx = capture_src.offset.switch_capture.case_idx,
12198 _ = try coerce_block.addBr(capture_block_inst, coerced);12260 .item_idx = .{ .kind = .single, .value = @intCast(idx) },
1219912261 } },
12200 try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));12262 };
12201 break :len coerce_block.instructions.items.len;
12202 };
1220312263
12204 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".field_names.len +12264 const field_idx = field_indices[idx];
12205 cases_extra.items.len +12265 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12206 @typeInfo(Air.Block).@"struct".field_names.len +12266 const uncoerced = try coerce_block.addStructFieldVal(loaded_operand, field_idx, field_ty);
12207 1);12267 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
12268 _ = try coerce_block.addBr(capture_block_inst, coerced);
1220812269
12209 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);12270 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".field_names.len +
12210 try sema.air_instructions.append(sema.gpa, .{12271 1 + // `item`, no ranges
12211 .tag = .switch_br,12272 coerce_block.instructions.items.len);
12212 .data = .{12273 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12213 .pl_op = .{12274 .items_len = 1,
12214 .operand = undefined, // set by switch below12275 .ranges_len = 0,
12215 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{12276 .body_len = @intCast(coerce_block.instructions.items.len),
12216 .cases_len = @intCast(prong_count),12277 }));
12217 .else_body_len = @intCast(else_body_len),12278 cases_extra.appendAssumeCapacity(@intFromEnum(item_refs[idx])); // item
12218 }),12279 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
12219 },
12220 },
12221 });
12222 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
12223
12224 // Set up block body
12225 switch (operand) {
12226 .simple => |s| {
12227 const air_datas = sema.air_instructions.items(.data);
12228 air_datas[switch_br_inst].pl_op.operand = s.cond;
12229 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12230 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
12231 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12232 },
12233 .loop => {
12234 // The block must first extract the tag from the loaded union.
12235 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
12236 try sema.air_instructions.append(sema.gpa, .{
12237 .tag = .get_union_tag,
12238 .data = .{ .ty_op = .{
12239 .ty = .fromIntern(union_obj.enum_tag_type),
12240 .operand = operand_val,
12241 } },
12242 });
12243 const air_datas = sema.air_instructions.items(.data);
12244 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
12245 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12246 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 2 });
12247 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
12248 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12249 },
12250 }12280 }
12251
12252 return capture_block_inst.toRef();
12253 }12281 }
12282 const else_body_len = len: {
12283 // 'else' prong uses a bitcast
12284 var coerce_block = case_block.makeSubBlock();
12285 defer coerce_block.instructions.deinit(sema.gpa);
1225412286
12255 if (err_set) {12287 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
12256 const case_vals = kind.item_refs;12288 const first_imc_field_idx = field_indices[first_imc_item_idx];
12257 if (case_vals.len == 1) {12289 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
12258 const item_val = sema.resolveValue(case_vals[0]).?;12290 const uncoerced = try coerce_block.addStructFieldVal(loaded_operand, first_imc_field_idx, first_imc_field_ty);
12259 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);12291 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
12260 return sema.bitCast(case_block, item_ty, .fromValue(item_val), operand_src, null);12292 _ = try coerce_block.addBr(capture_block_inst, coerced);
12261 }
1226212293
12263 var names: InferredErrorSet.NameMap = .{};12294 try cases_extra.appendSlice(gpa, @ptrCast(coerce_block.instructions.items));
12264 try names.ensureUnusedCapacity(sema.arena, case_vals.len);12295 break :len coerce_block.instructions.items.len;
12265 for (case_vals) |err| {12296 };
12266 const err_val = sema.resolveValue(err).?;
12267 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});
12268 }
12269 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());
12270 return sema.bitCast(case_block, error_ty, operand_val, operand_src, null);
12271 }
1227212297
12273 // In this case the capture value is just the passed-through value of the12298 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).@"struct".field_names.len +
12274 // switch condition. It is comptime-known if there is only one item.12299 cases_extra.items.len +
12275 if (capture_by_ref) {12300 @typeInfo(Air.Block).@"struct".field_names.len +
12276 return operand_ptr;12301 1);
12277 }12302
12278 switch (kind) {12303 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
12279 .inline_ref, .special => unreachable,12304 try sema.air_instructions.append(gpa, .{
12280 .item_refs => |case_vals| {12305 .tag = .switch_br,
12281 // If there's only a single item, the capture is comptime-known!12306 .data = .{
12282 if (case_vals.len == 1) return case_vals[0];12307 .pl_op = .{
12308 .operand = undefined, // set by switch below
12309 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
12310 .cases_len = @intCast(prong_count),
12311 .else_body_len = @intCast(else_body_len),
12312 }),
12313 },
12314 },
12315 });
12316 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
12317
12318 // Set up block body
12319 switch (operand) {
12320 .simple => |s| {
12321 const air_datas = sema.air_instructions.items(.data);
12322 air_datas[switch_br_inst].pl_op.operand = s.cond;
12323 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12324 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
12325 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12326 },
12327 .loop => {
12328 // The block must first extract the tag from the loaded union.
12329 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
12330 try sema.air_instructions.append(sema.gpa, .{
12331 .tag = .get_union_tag,
12332 .data = .{ .ty_op = .{
12333 .ty = .fromIntern(union_obj.enum_tag_type),
12334 .operand = loaded_operand,
12335 } },
12336 });
12337 const air_datas = sema.air_instructions.items(.data);
12338 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
12339 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
12340 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 2 });
12341 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
12342 sema.air_extra.appendAssumeCapacity(switch_br_inst);
12283 },12343 },
12284 .has_ranges => {},
12285 }12344 }
12286 return operand_val;12345
12346 return capture_block_inst.toRef();
12287}12347}
1228812348
12289const ResolvedSwitchItem = struct {12349const 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+78
...@@ -0,0 +1,78 @@
1const P = packed union(u8) {
2 a: u8,
3 b: i8,
4};
5export fn entry1(p: P) void {
6 switch (p) {
7 .{ .a = 123 } => |_, tag| _ = tag,
8 else => {},
9 }
10}
11export fn entry2(p: P) void {
12 label: switch (p) {
13 .{ .a = 123 } => |_, tag| _ = tag,
14 else => continue :label .{ .a = 123 },
15 }
16}
17
18const E = enum(u8) { a, b };
19export fn entry3(e: E) void {
20 switch (e) {
21 .a => |_, tag| _ = tag,
22 else => {},
23 }
24}
25export fn entry4(e: E) void {
26 label: switch (e) {
27 .a => |_, tag| _ = tag,
28 else => continue :label .a,
29 }
30}
31
32const Error = error{ MyError, MyOtherError };
33export fn entry5(ok: bool) void {
34 switch (foo(ok)) {
35 error.MyError => |_, tag| _ = tag,
36 else => {},
37 }
38}
39export fn entry6(ok: bool) void {
40 label: switch (foo(ok)) {
41 error.MyError => |_, tag| _ = tag,
42 else => continue :label error.MyError,
43 }
44}
45fn foo(ok: bool) Error {
46 return if (ok) error.MyError else error.MyOtherError;
47}
48
49export fn entry7() void {
50 switch (@as(u0, 0)) {
51 0 => |_, tag| _ = tag,
52 }
53}
54export fn entry8() void {
55 label: switch (@as(u0, 0)) {
56 0 => |_, tag| {
57 _ = tag;
58 continue :label 0;
59 },
60 }
61}
62
63// error
64//
65// :7:30: error: cannot capture tag of packed union
66// :1:18: note: union declared here
67// :1:18: note: consider using a tagged union
68// :13:30: error: cannot capture tag of packed union
69// :1:18: note: union declared here
70// :1:18: note: consider using a tagged union
71// :21:19: error: cannot capture tag of non-union type 'tmp.E'
72// :18:11: note: enum declared here
73// :27:19: error: cannot capture tag of non-union type 'tmp.E'
74// :18:11: note: enum declared here
75// :35:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'
76// :41:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'
77// :51:18: error: cannot capture tag of non-union type 'u0'
78// :56:18: error: cannot capture tag of non-union type 'u0'