authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-01-09 05:04:58+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-11 11:37:17+00:00
logb79bd313566347873e022adcd61891339735d9a6
treeeeebc217c745a964d6f72788cf25df8ad2cbe9e9
parentd94137d23fff3958e003c7d6d70a5d1087a1e98c
signaturelock-open Commit is signed but in an unrecognized format.

Sema: rework `switch_block[_ref/_err_union]` logic

This commit aims to simplify and de-duplicate the logic required for semantically analyzing `switch` expressions. The core logic has been moved to `analyzeSwitchBlock`, `resolveSwitchBlock` and `finishSwitchBlock` and has been rewritten around the new iterator-based API exposed by `Zir.UnwrappedSwitchBlock`. All validation logic and switch prong item resolution have been moved to `validateSwitchBlock`, which produces a `ValidatedSwitchBlock` containing all the necessary information for further analysis. `Zir.UnwrappedSwitchBlock`, `ValidatedSwitchBlock` and `SwitchOperand` replace `SwitchProngAnalysis` while adding more flexibility, mainly for better integration with `switch_block_err_union`. `analyzeSwitchBlock` has an explicit code path for OPV types which lowers them to either a `block`-`br` or a `loop`-`repeat` construct instead of a switch. Backends expect `switch` to actually have an operand that exists at runtime, so this is a bug fix and avoids further special cases in the rest of the switch logic. `resolveSwitchBlock` and `finishSwitchBr` exclusively deal with operands which can have more than one value, at comptime and at runtime respectively. This commit also reworks `RangeSet` to be an unmanaged container and adds `Air.SwitchBr.BranchHints` to offload some complexity from Sema to there and save a few bytes of memory in the process. Additionally, some new features have been implemented: - decl literals and everything else requiring a result type (`@enumFromInt`!) may now be used as switch prong items - union tag captures are now allowed for all prongs, not just `inline` ones - switch prongs may contain errors which are not in the error set being switched on, if these prongs contain `=> comptime unreachable` and some bugs have been fixed: - lots of issues with switching on OPV types are now fixed - the rules around unreachable `else` prongs when switching on errors now apply to *any* switch on an error, not just to `switch_block_err_union`, and are applied properly based on the AST - switching on `void` no longer requires an `else` prong unconditionally - lazy values are properly resolved before any comparisons with prong items - evaluation order between all kinds of switch statements is now the same, with or without label

11 files changed, 3188 insertions(+), 3094 deletions(-)

src/Air.zig+44
...@@ -1339,6 +1339,50 @@ pub const SwitchBr = struct {...@@ -1339,6 +1339,50 @@ pub const SwitchBr = struct {
1339 ranges_len: u32,1339 ranges_len: u32,
1340 body_len: u32,1340 body_len: u32,
1341 };1341 };
1342
1343 pub const BranchHints = struct {
1344 bags: std.ArrayList(u32),
1345 count: u32,
1346
1347 const hints_per_bag = 10;
1348 const hint_bits = @bitSizeOf(std.builtin.BranchHint);
1349
1350 pub const empty: BranchHints = .{
1351 .bags = .empty,
1352 .count = 0,
1353 };
1354
1355 pub fn initCapacity(gpa: std.mem.Allocator, num: u32) std.mem.Allocator.Error!BranchHints {
1356 const bags_required = std.math.divCeil(u32, num, hints_per_bag) catch unreachable;
1357 const bags: std.ArrayList(u32) = try .initCapacity(gpa, bags_required);
1358 return .{ .bags = bags, .count = 0 };
1359 }
1360
1361 pub fn ensureUnusedCapacity(hints: *BranchHints, gpa: std.mem.Allocator, additional_count: u32) std.mem.Allocator.Error!void {
1362 const unused_hints = hints.bags.capacity * hints_per_bag - hints.count;
1363 if (unused_hints >= additional_count) return;
1364 const bags_required = std.math.divCeil(u32, hints.count + additional_count, hints_per_bag) catch unreachable;
1365 return hints.bags.ensureUnusedCapacity(gpa, bags_required);
1366 }
1367
1368 pub fn appendAssumeCapacity(hints: *BranchHints, hint: std.builtin.BranchHint) void {
1369 const idx_in_bag = hints.count % hints_per_bag;
1370 var bag: u32 = if (idx_in_bag > 0) hints.bags.pop().? else 0;
1371 bag |= @as(u32, @intFromEnum(hint)) << @intCast(hint_bits * idx_in_bag);
1372 hints.count += 1;
1373 return hints.bags.appendAssumeCapacity(bag);
1374 }
1375
1376 pub fn append(hints: *BranchHints, gpa: std.mem.Allocator, hint: std.builtin.BranchHint) std.mem.Allocator.Error!void {
1377 try hints.ensureUnusedCapacity(gpa, 1);
1378 return hints.appendAssumeCapacity(hint);
1379 }
1380
1381 pub fn deinit(hints: *BranchHints, gpa: std.mem.Allocator) void {
1382 hints.bags.deinit(gpa);
1383 hints.* = undefined;
1384 }
1385 };
1342};1386};
13431387
1344/// This data is stored inside extra. Trailing:1388/// This data is stored inside extra. Trailing:
src/RangeSet.zig+66-66
...@@ -1,102 +1,92 @@...@@ -1,102 +1,92 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const Order = std.math.Order;
4
5const InternPool = @import("InternPool.zig");
6const Type = @import("Type.zig");
7const Value = @import("Value.zig");
8const Zcu = @import("Zcu.zig");
9const RangeSet = @This();1const RangeSet = @This();
10const LazySrcLoc = Zcu.LazySrcLoc;
112
12zcu: *Zcu,3ranges: std.ArrayList(Range),
13ranges: std.array_list.Managed(Range),
144
15pub const Range = struct {5pub const Range = struct {
16 first: InternPool.Index,6 first: Value,
17 last: InternPool.Index,7 last: Value,
18 src: LazySrcLoc,8 src: LazySrcLoc,
19};9};
2010
21pub fn init(allocator: std.mem.Allocator, zcu: *Zcu) RangeSet {11pub const empty: RangeSet = .{ .ranges = .empty };
22 return .{
23 .zcu = zcu,
24 .ranges = std.array_list.Managed(Range).init(allocator),
25 };
26}
2712
28pub fn deinit(self: *RangeSet) void {13pub fn deinit(self: *RangeSet, allocator: Allocator) void {
29 self.ranges.deinit();14 self.ranges.deinit(allocator);
15 self.* = undefined;
30}16}
3117
32pub fn add(18pub fn ensureUnusedCapacity(self: *RangeSet, allocator: Allocator, additional_count: usize) Allocator.Error!void {
33 self: *RangeSet,19 return self.ranges.ensureUnusedCapacity(allocator, additional_count);
34 first: InternPool.Index,20}
35 last: InternPool.Index,
36 src: LazySrcLoc,
37) !?LazySrcLoc {
38 const zcu = self.zcu;
39 const ip = &zcu.intern_pool;
40
41 const ty = ip.typeOf(first);
42 assert(ty == ip.typeOf(last));
4321
44 for (self.ranges.items) |range| {22pub fn addAssumeCapacity(set: *RangeSet, new: Range, ty: Type, zcu: *Zcu) ?LazySrcLoc {
45 assert(ty == ip.typeOf(range.first));23 assert(new.first.typeOf(zcu).eql(ty, zcu));
46 assert(ty == ip.typeOf(range.last));24 assert(new.last.typeOf(zcu).eql(ty, zcu));
4725
48 if (Value.fromInterned(last).compareScalar(.gte, Value.fromInterned(range.first), Type.fromInterned(ty), zcu) and26 for (set.ranges.items) |range| {
49 Value.fromInterned(first).compareScalar(.lte, Value.fromInterned(range.last), Type.fromInterned(ty), zcu))27 if (new.last.compareScalar(.gte, range.first, ty, zcu) and
28 new.first.compareScalar(.lte, range.last, ty, zcu))
50 {29 {
51 return range.src; // They overlap.30 return range.src; // They overlap.
52 }31 }
53 }32 }
5433 set.ranges.appendAssumeCapacity(new);
55 try self.ranges.append(.{
56 .first = first,
57 .last = last,
58 .src = src,
59 });
60 return null;34 return null;
61}35}
6236
63/// Assumes a and b do not overlap37pub fn add(set: *RangeSet, allocator: Allocator, new: Range, ty: Type, zcu: *Zcu) Allocator.Error!?LazySrcLoc {
64fn lessThan(zcu: *Zcu, a: Range, b: Range) bool {38 try set.ensureUnusedCapacity(allocator, 1);
65 const ty = Type.fromInterned(zcu.intern_pool.typeOf(a.first));39 return set.addAssumeCapacity(new, ty, zcu);
66 return Value.fromInterned(a.first).compareScalar(.lt, Value.fromInterned(b.first), ty, zcu);
67}40}
6841
69pub fn spans(self: *RangeSet, first: InternPool.Index, last: InternPool.Index) !bool {42const SortCtx = struct {
70 const zcu = self.zcu;43 ty: Type,
71 const ip = &zcu.intern_pool;44 zcu: *Zcu,
72 assert(ip.typeOf(first) == ip.typeOf(last));45};
7346/// Assumes a and b do not overlap
74 if (self.ranges.items.len == 0)47fn lessThan(ctx: SortCtx, a: Range, b: Range) bool {
75 return false;48 return a.first.compareScalar(.lt, b.first, ctx.ty, ctx.zcu);
7649}
77 std.mem.sort(Range, self.ranges.items, zcu, lessThan);
7850
79 if (self.ranges.items[0].first != first or51pub fn spans(
80 self.ranges.items[self.ranges.items.len - 1].last != last)52 set: *RangeSet,
53 allocator: Allocator,
54 first: Value,
55 last: Value,
56 ty: Type,
57 zcu: *Zcu,
58) Allocator.Error!bool {
59 assert(first.typeOf(zcu).eql(ty, zcu));
60 assert(last.typeOf(zcu).eql(ty, zcu));
61 if (set.ranges.items.len == 0) return false;
62
63 std.mem.sort(Range, set.ranges.items, SortCtx{ .ty = ty, .zcu = zcu }, lessThan);
64
65 if (!set.ranges.items[0].first.eql(first, ty, zcu) or
66 !set.ranges.items[set.ranges.items.len - 1].last.eql(last, ty, zcu))
81 {67 {
82 return false;68 return false;
83 }69 }
8470
85 var space: InternPool.Key.Int.Storage.BigIntSpace = undefined;71 const limbs = try allocator.alloc(
72 std.math.big.Limb,
73 std.math.big.int.calcTwosCompLimbCount(ty.intInfo(zcu).bits),
74 );
75 defer allocator.free(limbs);
76 var counter: std.math.big.int.Mutable = .init(limbs, 0);
8677
87 var counter = try std.math.big.int.Managed.init(self.ranges.allocator);78 var space: InternPool.Key.Int.Storage.BigIntSpace = undefined;
88 defer counter.deinit();
8979
90 // look for gaps80 // look for gaps
91 for (self.ranges.items[1..], 0..) |cur, i| {81 for (set.ranges.items[1..], 0..) |cur, i| {
92 // i starts counting from the second item.82 // i starts counting from the second item.
93 const prev = self.ranges.items[i];83 const prev = set.ranges.items[i];
9484
95 // prev.last + 1 == cur.first85 // prev.last + 1 == cur.first
96 try counter.copy(Value.fromInterned(prev.last).toBigInt(&space, zcu));86 counter.copy(prev.last.toBigInt(&space, zcu));
97 try counter.addScalar(&counter, 1);87 counter.addScalar(counter.toConst(), 1);
9888
99 const cur_start_int = Value.fromInterned(cur.first).toBigInt(&space, zcu);89 const cur_start_int = cur.first.toBigInt(&space, zcu);
100 if (!cur_start_int.eql(counter.toConst())) {90 if (!cur_start_int.eql(counter.toConst())) {
101 return false;91 return false;
102 }92 }
...@@ -104,3 +94,13 @@ pub fn spans(self: *RangeSet, first: InternPool.Index, last: InternPool.Index) !...@@ -104,3 +94,13 @@ pub fn spans(self: *RangeSet, first: InternPool.Index, last: InternPool.Index) !
10494
105 return true;95 return true;
106}96}
97
98const std = @import("std");
99const assert = std.debug.assert;
100const Allocator = std.mem.Allocator;
101
102const InternPool = @import("InternPool.zig");
103const Type = @import("Type.zig");
104const Value = @import("Value.zig");
105const Zcu = @import("Zcu.zig");
106const LazySrcLoc = Zcu.LazySrcLoc;
src/Sema.zig+2491-2950
...@@ -509,7 +509,7 @@ pub const Block = struct {...@@ -509,7 +509,7 @@ pub const Block = struct {
509 .parent = parent,509 .parent = parent,
510 .sema = parent.sema,510 .sema = parent.sema,
511 .namespace = parent.namespace,511 .namespace = parent.namespace,
512 .instructions = .{},512 .instructions = .empty,
513 .label = null,513 .label = null,
514 .inlining = parent.inlining,514 .inlining = parent.inlining,
515 .comptime_reason = parent.comptime_reason,515 .comptime_reason = parent.comptime_reason,
...@@ -6496,26 +6496,23 @@ fn zirSwitchContinue(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) Com...@@ -6496,26 +6496,23 @@ fn zirSwitchContinue(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) Com
64966496
6497 switch (sema.code.instructions.items(.tag)[@intFromEnum(switch_inst)]) {6497 switch (sema.code.instructions.items(.tag)[@intFromEnum(switch_inst)]) {
6498 .switch_block, .switch_block_ref => {},6498 .switch_block, .switch_block_ref => {},
6499 .switch_block_err_union => unreachable, // wrong code path!
6499 else => unreachable, // assertion failure6500 else => unreachable, // assertion failure
6500 }6501 }
65016502
6502 const switch_payload_index = sema.code.instructions.items(.data)[@intFromEnum(switch_inst)].pl_node.payload_index;6503 const operand_ty = (try sema.resolveInst(switch_inst.toRef())).toType();
6503 const switch_operand_ref = sema.code.extraData(Zir.Inst.SwitchBlock, switch_payload_index).data.operand;6504 const operand = try sema.coerce(start_block, operand_ty, uncoerced_operand, operand_src);
6504 const switch_operand_ty = sema.typeOf(try sema.resolveInst(switch_operand_ref));
6505
6506 const operand = try sema.coerce(start_block, switch_operand_ty, uncoerced_operand, operand_src);
6507
6508 try sema.validateRuntimeValue(start_block, operand_src, operand);6505 try sema.validateRuntimeValue(start_block, operand_src, operand);
65096506
6510 // We want to generate a `switch_dispatch` instruction with the switch condition,6507 // We want to generate a `switch_dispatch` instruction with the switch condition,
6511 // possibly preceded by a store to the stack alloc containing the raw operand.6508 // possibly preceded by a store to the stack alloc containing the raw operand.
6512 // However, to avoid too much special-case state in Sema, this is handled by the6509 // However, to avoid too much special-case state in Sema, this is handled by the
6513 // `switch` lowering logic. As such, we will find the `Block` corresponding to the6510 // `switch` lowering logic. As such, we will find the `Block` corresponding to
6514 // parent `switch_block[_ref]` instruction, create a dummy `br`, and add a merge6511 // the parent `switch_block[_ref]` instruction, create a dummy `br`, and add a
6515 // to signal to the switch logic to rewrite this into an appropriate dispatch.6512 // merge to signal to the switch logic to rewrite this into an appropriate dispatch.
65166513
6517 var block = start_block;6514 var block = start_block;
6518 while (true) {6515 while (true) : (block = block.parent.?) {
6519 if (block.label) |label| {6516 if (block.label) |label| {
6520 if (label.zir_block == switch_inst) {6517 if (label.zir_block == switch_inst) {
6521 const br_ref = try start_block.addBr(label.merges.block_inst, operand);6518 const br_ref = try start_block.addBr(label.merges.block_inst, operand);
...@@ -6529,7 +6526,6 @@ fn zirSwitchContinue(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) Com...@@ -6529,7 +6526,6 @@ fn zirSwitchContinue(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) Com
6529 return;6526 return;
6530 }6527 }
6531 }6528 }
6532 block = block.parent.?;
6533 }6529 }
6534}6530}
65356531
...@@ -8483,8 +8479,20 @@ fn zirDeclLiteral(sema: *Sema, block: *Block, inst: Zir.Inst.Index, do_coerce: b...@@ -8483,8 +8479,20 @@ fn zirDeclLiteral(sema: *Sema, block: *Block, inst: Zir.Inst.Index, do_coerce: b
8483 sema.code.nullTerminatedString(extra.field_name_start),8479 sema.code.nullTerminatedString(extra.field_name_start),
8484 .no_embedded_nulls,8480 .no_embedded_nulls,
8485 );8481 );
8486
8487 const orig_ty: Type = try sema.resolveTypeOrPoison(block, src, extra.lhs) orelse .generic_poison;8482 const orig_ty: Type = try sema.resolveTypeOrPoison(block, src, extra.lhs) orelse .generic_poison;
8483 return sema.analyzeDeclLiteral(block, src, name, orig_ty, do_coerce);
8484}
8485
8486fn analyzeDeclLiteral(
8487 sema: *Sema,
8488 block: *Block,
8489 src: LazySrcLoc,
8490 name: InternPool.NullTerminatedString,
8491 orig_ty: Type,
8492 do_coerce: bool,
8493) CompileError!Air.Inst.Ref {
8494 const pt = sema.pt;
8495 const zcu = pt.zcu;
84888496
8489 const uncoerced_result = res: {8497 const uncoerced_result = res: {
8490 if (orig_ty.toIntern() == .generic_poison_type) {8498 if (orig_ty.toIntern() == .generic_poison_type) {
...@@ -10518,1284 +10526,1572 @@ fn zirSliceSentinelTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE...@@ -10518,1284 +10526,1572 @@ fn zirSliceSentinelTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
10518 return Air.internedToRef(sentinel_ty.toIntern());10526 return Air.internedToRef(sentinel_ty.toIntern());
10519}10527}
1052010528
10521/// Holds common data used when analyzing or resolving switch prong bodies,10529fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
10522/// including setting up captures.10530 const tracy = trace(@src());
10523const SwitchProngAnalysis = struct {10531 defer tracy.end();
10524 sema: *Sema,
10525 /// The block containing the `switch_block` itself.
10526 parent_block: *Block,
10527 operand: Operand,
10528 /// If this switch is on an error set, this is the type to assign to the
10529 /// `else` prong. If `null`, the prong should be unreachable.
10530 else_error_ty: ?Type,
10531 /// The index of the `switch_block` instruction itself.
10532 switch_block_inst: Zir.Inst.Index,
10533 /// The dummy index into which inline tag captures should be placed. May be
10534 /// undefined if no prong has a tag capture.
10535 tag_capture_inst: Zir.Inst.Index,
10536
10537 const Operand = union(enum) {
10538 /// This switch will be dispatched only once, with the given operand.
10539 simple: struct {
10540 /// The raw switch operand value. Always defined.
10541 by_val: Air.Inst.Ref,
10542 /// The switch operand *pointer*. Defined only if there is a prong
10543 /// with a by-ref capture.
10544 by_ref: Air.Inst.Ref,
10545 /// The switch condition value. For unions, `operand` is the union
10546 /// and `cond` is its enum tag value.
10547 cond: Air.Inst.Ref,
10548 },
10549 /// This switch may be dispatched multiple times with `continue` syntax.
10550 /// As such, the operand is stored in an alloc if needed.
10551 loop: struct {
10552 /// The `alloc` containing the `switch` operand for the active dispatch.
10553 /// Each prong must load from this `alloc` to get captures.
10554 /// If there are no captures, this may be undefined.
10555 operand_alloc: Air.Inst.Ref,
10556 /// Whether `operand_alloc` contains a by-val operand or a by-ref
10557 /// operand.
10558 operand_is_ref: bool,
10559 /// The switch condition value for the *initial* dispatch. For
10560 /// unions, this is the enum tag value.
10561 init_cond: Air.Inst.Ref,
10562 },
10563 };
10564
10565 /// Resolve a switch prong which is determined at comptime to have no peers.
10566 /// Uses `resolveBlockBody`. Sets up captures as needed.
10567 fn resolveProngComptime(
10568 spa: SwitchProngAnalysis,
10569 child_block: *Block,
10570 prong_type: enum { normal, special },
10571 prong_body: []const Zir.Inst.Index,
10572 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
10573 /// Must use the `switch_capture` field in `offset`.
10574 capture_src: LazySrcLoc,
10575 /// The set of all values which can reach this prong. May be undefined
10576 /// if the prong is special or contains ranges.
10577 case_vals: []const Air.Inst.Ref,
10578 /// The inline capture of this prong. If this is not an inline prong,
10579 /// this is `.none`.
10580 inline_case_capture: Air.Inst.Ref,
10581 /// Whether this prong has an inline tag capture. If `true`, then
10582 /// `inline_case_capture` cannot be `.none`.
10583 has_tag_capture: bool,
10584 merges: *Block.Merges,
10585 ) CompileError!Air.Inst.Ref {
10586 const sema = spa.sema;
10587 const src = spa.parent_block.nodeOffset(
10588 sema.code.instructions.items(.data)[@intFromEnum(spa.switch_block_inst)].pl_node.src_node,
10589 );
1059010532
10591 // We can propagate `.cold` hints from this branch since it's comptime-known10533 const pt = sema.pt;
10592 // to be taken from the parent branch.10534 const zcu = pt.zcu;
10593 const parent_hint = sema.branch_hint;10535 const gpa = sema.gpa;
10594 defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
1059510536
10596 if (has_tag_capture) {10537 const zir_switch = sema.code.getSwitchBlock(inst);
10597 const tag_ref = try spa.analyzeTagCapture(child_block, capture_src, inline_case_capture);10538 const src_node_offset = zir_switch.catch_or_if_src_node_offset.unwrap().?;
10598 sema.inst_map.putAssumeCapacity(spa.tag_capture_inst, tag_ref);10539 const src = block.src(.{ .node_offset_main_token = src_node_offset });
10599 }10540 const operand_src = block.src(.{ .node_offset_if_cond = src_node_offset });
10600 defer if (has_tag_capture) assert(sema.inst_map.remove(spa.tag_capture_inst));
1060110541
10602 switch (capture) {10542 assert(!zir_switch.has_continue); // wrong codepath!
10603 .none => {
10604 return sema.resolveBlockBody(spa.parent_block, src, child_block, prong_body, spa.switch_block_inst, merges);
10605 },
1060610543
10607 .by_val, .by_ref => {10544 const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
10608 const capture_ref = try spa.analyzeCapture(10545 try sema.air_instructions.append(gpa, .{
10609 child_block,10546 .tag = .block,
10610 capture == .by_ref,10547 .data = undefined,
10611 prong_type == .special,10548 });
10612 capture_src,10549 var label: Block.Label = .{
10613 case_vals,10550 .zir_block = inst,
10614 inline_case_capture,10551 .merges = .{
10615 );10552 .src_locs = .{},
10553 .results = .{},
10554 .br_list = .{},
10555 .block_inst = block_inst,
10556 },
10557 };
10558 var child_block = block.makeSubBlock();
10559 child_block.label = &label;
10560 const merges = &child_block.label.?.merges;
10561 defer child_block.instructions.deinit(gpa);
10562 defer merges.deinit(gpa);
1061610563
10617 if (sema.typeOf(capture_ref).isNoReturn(sema.pt.zcu)) {10564 const non_err_case = zir_switch.non_err_case.?;
10618 // This prong should be unreachable!10565
10619 return .unreachable_value;10566 var non_err_block: Block = child_block.makeSubBlock();
10620 }10567 non_err_block.runtime_loop = null;
10568 non_err_block.runtime_cond = operand_src;
10569 non_err_block.runtime_index.increment();
10570 non_err_block.need_debug_scope = null;
10571 defer non_err_block.instructions.deinit(gpa);
10572
10573 var switch_block: Block = child_block.makeSubBlock();
10574 switch_block.runtime_loop = null;
10575 switch_block.runtime_cond = operand_src;
10576 switch_block.runtime_index.increment();
10577 switch_block.need_debug_scope = null;
10578 defer switch_block.instructions.deinit(gpa);
10579
10580 // We begin with unwrapping the error union we're switching on as necessary.
10581 // Then we analyze the non-error prong if it's not comptime-unreachable.
10582 // Lastly, we analyze the error prong(s) as a regular switch.
10583
10584 const raw_switch_operand, const non_err_reachable, const non_err_cond, const non_err_hint, const err_set_empty = non_err: {
10585 const eu_maybe_ptr = try sema.resolveInst(zir_switch.main_operand);
10586 const err_union_ty: Type = err_union_ty: {
10587 const raw_operand_ty = sema.typeOf(eu_maybe_ptr);
10588 if (!non_err_case.operand_is_ref) break :err_union_ty raw_operand_ty;
10589 try sema.checkPtrOperand(block, operand_src, raw_operand_ty);
10590 break :err_union_ty raw_operand_ty.childType(zcu);
10591 };
10592 if (err_union_ty.zigTypeTag(zcu) != .error_union) {
10593 return sema.fail(block, src, "expected error union type, found '{f}'", .{
10594 err_union_ty.fmt(pt),
10595 });
10596 }
1062110597
10622 sema.inst_map.putAssumeCapacity(spa.switch_block_inst, capture_ref);10598 const non_err_cond = if (non_err_case.operand_is_ref)
10623 defer assert(sema.inst_map.remove(spa.switch_block_inst));10599 try sema.analyzePtrIsNonErr(block, operand_src, eu_maybe_ptr)
10600 else
10601 try sema.analyzeIsNonErr(block, operand_src, eu_maybe_ptr);
1062410602
10625 return sema.resolveBlockBody(spa.parent_block, src, child_block, prong_body, spa.switch_block_inst, merges);10603 const is_non_err = try sema.resolveDefinedValue(block, operand_src, non_err_cond);
10626 },10604 const non_err_reachable = if (is_non_err) |val| val.toBool() else true;
10627 }
10628 }
1062910605
10630 /// Analyze a switch prong which may have peers at runtime.10606 const err_set_empty = err_set_empty: {
10631 /// Uses `analyzeBodyRuntimeBreak`. Sets up captures as needed.10607 const err_set_ty = err_union_ty.errorUnionSet(zcu);
10632 /// Returns the `BranchHint` for the prong.10608 break :err_set_empty err_set_ty.errorSetIsEmpty(zcu);
10633 fn analyzeProngRuntime(10609 };
10634 spa: SwitchProngAnalysis,
10635 case_block: *Block,
10636 prong_type: enum { normal, special },
10637 prong_body: []const Zir.Inst.Index,
10638 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
10639 /// Must use the `switch_capture` field in `offset`.
10640 capture_src: LazySrcLoc,
10641 /// The set of all values which can reach this prong. May be undefined
10642 /// if the prong is special or contains ranges.
10643 case_vals: []const Air.Inst.Ref,
10644 /// The inline capture of this prong. If this is not an inline prong,
10645 /// this is `.none`.
10646 inline_case_capture: Air.Inst.Ref,
10647 /// Whether this prong has an inline tag capture. If `true`, then
10648 /// `inline_case_capture` cannot be `.none`.
10649 has_tag_capture: bool,
10650 ) CompileError!std.builtin.BranchHint {
10651 const sema = spa.sema;
1065210610
10653 if (has_tag_capture) {10611 const non_err_hint: std.builtin.BranchHint = hint: {
10654 const tag_ref = try spa.analyzeTagCapture(case_block, capture_src, inline_case_capture);10612 // don't analyze the non-error body if it's unreachable
10655 sema.inst_map.putAssumeCapacity(spa.tag_capture_inst, tag_ref);10613 if (!non_err_reachable) {
10656 }10614 break :hint undefined;
10657 defer if (has_tag_capture) assert(sema.inst_map.remove(spa.tag_capture_inst));10615 }
1065810616
10659 switch (capture) {10617 const eu_payload: Air.Inst.Ref = switch (non_err_case.capture) {
10660 .none => {10618 .by_val => try sema.analyzeErrUnionPayload(&non_err_block, src, err_union_ty, eu_maybe_ptr, operand_src, false),
10661 return sema.analyzeBodyRuntimeBreak(case_block, prong_body);10619 .by_ref => try sema.analyzeErrUnionPayloadPtr(&non_err_block, src, eu_maybe_ptr, false, false),
10662 },10620 .none => undefined,
10621 };
10622 if (non_err_case.capture != .none) sema.inst_map.putAssumeCapacity(inst, eu_payload);
10623 defer if (non_err_case.capture != .none) assert(sema.inst_map.remove(inst));
1066310624
10664 .by_val, .by_ref => {10625 const always_non_err = if (is_non_err) |val| val.toBool() else err_set_empty;
10665 const capture_ref = try spa.analyzeCapture(10626 if (always_non_err) {
10666 case_block,10627 // Early return; we don't analyze the switch as it's unreachable.
10667 capture == .by_ref,10628 return sema.resolveBlockBody(block, src, &non_err_block, non_err_case.body, inst, merges);
10668 prong_type == .special,10629 }
10669 capture_src,10630 break :hint try sema.analyzeBodyRuntimeBreak(&non_err_block, non_err_case.body);
10670 case_vals,10631 };
10671 inline_case_capture,
10672 );
1067310632
10674 if (sema.typeOf(capture_ref).isNoReturn(sema.pt.zcu)) {10633 // Emit this into the switch block as it's our error case!
10675 // No need to analyze any further, the prong is unreachable10634 const eu_code = if (non_err_case.operand_is_ref)
10676 return .none;10635 try sema.analyzeErrUnionCodePtr(&switch_block, operand_src, eu_maybe_ptr)
10677 }10636 else
10637 try sema.analyzeErrUnionCode(&switch_block, operand_src, eu_maybe_ptr);
1067810638
10679 sema.inst_map.putAssumeCapacity(spa.switch_block_inst, capture_ref);10639 break :non_err .{ eu_code, non_err_reachable, non_err_cond, non_err_hint, err_set_empty };
10680 defer assert(sema.inst_map.remove(spa.switch_block_inst));10640 };
1068110641
10682 return sema.analyzeBodyRuntimeBreak(case_block, prong_body);10642 const validated_switch = try sema.validateSwitchBlock(block, raw_switch_operand, false, inst, &zir_switch);
10683 },
10684 }
10685 }
1068610643
10687 fn analyzeTagCapture(10644 const maybe_switch_ref: ?Air.Inst.Ref = ref: {
10688 spa: SwitchProngAnalysis,10645 if (err_set_empty) break :ref .unreachable_value;
10689 block: *Block,10646 // make err capture (i.e. switch operand) available to switch prong bodies
10690 capture_src: LazySrcLoc,10647 sema.inst_map.putAssumeCapacityNoClobber(inst, raw_switch_operand);
10691 inline_case_capture: Air.Inst.Ref,10648 defer assert(sema.inst_map.remove(inst));
10692 ) CompileError!Air.Inst.Ref {10649 break :ref try sema.analyzeSwitchBlock(block, &switch_block, raw_switch_operand, false, merges, inst, &zir_switch, &validated_switch);
10693 const sema = spa.sema;10650 };
10694 const pt = sema.pt;10651
10695 const zcu = pt.zcu;10652 if (!non_err_reachable) {
10696 const operand_ty = switch (spa.operand) {10653 return maybe_switch_ref orelse {
10697 .simple => |s| sema.typeOf(s.by_val),10654 const switch_src = block.nodeOffset(zir_switch.switch_src_node_offset);
10698 .loop => |l| ty: {10655 return sema.resolveAnalyzedBlock(block, switch_src, &switch_block, merges, false);
10699 const alloc_ty = sema.typeOf(l.operand_alloc);
10700 const alloc_child = alloc_ty.childType(zcu);
10701 if (l.operand_is_ref) break :ty alloc_child.childType(zcu);
10702 break :ty alloc_child;
10703 },
10704 };10656 };
10705 if (operand_ty.zigTypeTag(zcu) != .@"union") {
10706 const tag_capture_src: LazySrcLoc = .{
10707 .base_node_inst = capture_src.base_node_inst,
10708 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
10709 };
10710 return sema.fail(block, tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
10711 operand_ty.fmt(pt),
10712 });
10713 }
10714 assert(inline_case_capture != .none);
10715 return inline_case_capture;
10716 }10657 }
1071710658
10718 fn analyzeCapture(10659 if (maybe_switch_ref) |switch_ref| {
10719 spa: SwitchProngAnalysis,10660 if (sema.typeOf(switch_ref).isNoReturn(zcu)) {
10720 block: *Block,10661 _ = try switch_block.addNoOp(.unreach);
10721 capture_byref: bool,10662 } else {
10722 is_special_prong: bool,10663 const br_ref = try switch_block.addBr(merges.block_inst, switch_ref);
10723 capture_src: LazySrcLoc,10664 try merges.results.append(gpa, switch_ref);
10724 case_vals: []const Air.Inst.Ref,10665 try merges.br_list.append(gpa, br_ref.toIndex().?);
10725 inline_case_capture: Air.Inst.Ref,10666 try merges.src_locs.append(gpa, null);
10726 ) CompileError!Air.Inst.Ref {10667 }
10727 const sema = spa.sema;10668 }
10728 const pt = sema.pt;
10729 const zcu = pt.zcu;
10730 const ip = &zcu.intern_pool;
1073110669
10732 const zir_datas = sema.code.instructions.items(.data);10670 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).@"struct".fields.len +
10733 const switch_node_offset = zir_datas[@intFromEnum(spa.switch_block_inst)].pl_node.src_node;10671 non_err_block.instructions.items.len + switch_block.instructions.items.len);
10672 const cond_br_payload = sema.addExtraAssumeCapacity(Air.CondBr{
10673 .then_body_len = @intCast(non_err_block.instructions.items.len),
10674 .else_body_len = @intCast(switch_block.instructions.items.len),
10675 .branch_hints = .{
10676 .true = non_err_hint,
10677 .false = .unlikely, // errors are unlikely
10678 // Code coverage is desired for error handling.
10679 .then_cov = .poi,
10680 .else_cov = .poi,
10681 },
10682 });
10683 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(non_err_block.instructions.items));
10684 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(switch_block.instructions.items));
1073410685
10735 const operand_src = block.src(.{ .node_offset_switch_operand = switch_node_offset });10686 _ = try child_block.addInst(.{ .tag = .cond_br, .data = .{ .pl_op = .{
10687 .operand = non_err_cond,
10688 .payload = cond_br_payload,
10689 } } });
1073610690
10737 const operand_val, const operand_ptr = switch (spa.operand) {10691 return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false);
10738 .simple => |s| .{ s.by_val, s.by_ref },10692}
10739 .loop => |l| op: {
10740 const loaded = try sema.analyzeLoad(block, operand_src, l.operand_alloc, operand_src);
10741 if (l.operand_is_ref) {
10742 const by_val = try sema.analyzeLoad(block, operand_src, loaded, operand_src);
10743 break :op .{ by_val, loaded };
10744 } else {
10745 break :op .{ loaded, undefined };
10746 }
10747 },
10748 };
1074910693
10750 const operand_ty = sema.typeOf(operand_val);10694fn zirSwitchBlock(
10751 const operand_ptr_ty = if (capture_byref) sema.typeOf(operand_ptr) else undefined;10695 sema: *Sema,
10696 block: *Block,
10697 inst: Zir.Inst.Index,
10698 operand_is_ref: bool,
10699) CompileError!Air.Inst.Ref {
10700 const tracy = trace(@src());
10701 defer tracy.end();
10702 const zir_switch = sema.code.getSwitchBlock(inst);
1075210703
10753 if (inline_case_capture != .none) {10704 const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
10754 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inline_case_capture, undefined) catch unreachable;10705 try sema.air_instructions.append(sema.gpa, .{
10755 if (operand_ty.zigTypeTag(zcu) == .@"union") {10706 .tag = .block,
10756 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);10707 .data = undefined,
10757 const union_obj = zcu.typeToUnion(operand_ty).?;10708 });
10758 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);10709 var label: Block.Label = .{
10759 if (capture_byref) {10710 .zir_block = inst,
10760 const ptr_field_ty = try pt.ptrTypeSema(.{10711 .merges = .{
10761 .child = field_ty.toIntern(),10712 .src_locs = .{},
10762 .flags = .{10713 .results = .{},
10763 .is_const = !operand_ptr_ty.ptrIsMutable(zcu),10714 .br_list = .{},
10764 .is_volatile = operand_ptr_ty.isVolatilePtr(zcu),10715 .block_inst = block_inst,
10765 .address_space = operand_ptr_ty.ptrAddressSpace(zcu),10716 },
10766 },10717 };
10767 });10718 var child_block = block.makeSubBlock();
10768 if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |union_ptr| {10719 child_block.label = &label;
10769 return Air.internedToRef((try union_ptr.ptrField(field_index, pt)).toIntern());10720 const merges = &child_block.label.?.merges;
10770 }10721 defer child_block.instructions.deinit(sema.gpa);
10771 return block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);10722 defer merges.deinit(sema.gpa);
10772 } else {
10773 if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |union_val| {
10774 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
10775 return Air.internedToRef(tag_and_val.val);
10776 }
10777 return block.addStructFieldVal(operand_val, field_index, field_ty);
10778 }
10779 } else if (capture_byref) {
10780 return sema.uavRef(item_val.toIntern());
10781 } else {
10782 return inline_case_capture;
10783 }
10784 }
1078510723
10786 if (is_special_prong) {10724 const raw_operand = try sema.resolveInst(zir_switch.main_operand);
10787 if (capture_byref) {10725 const validated_switch = try sema.validateSwitchBlock(block, raw_operand, operand_is_ref, inst, &zir_switch);
10788 return operand_ptr;10726 const maybe_ref = try sema.analyzeSwitchBlock(block, &child_block, raw_operand, operand_is_ref, merges, inst, &zir_switch, &validated_switch);
10789 }10727 return maybe_ref orelse {
10728 const src = block.nodeOffset(zir_switch.switch_src_node_offset);
10729 return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false);
10730 };
10731}
1079010732
10791 switch (operand_ty.zigTypeTag(zcu)) {10733/// If the switch can be resolved to a value at comptime, this will return a `Ref`
10792 .error_set => if (spa.else_error_ty) |ty| {10734/// that's never `.none`.
10793 return sema.bitCast(block, ty, operand_val, operand_src, null);10735/// If not, this will return `null` and emit its instructions into `child_block`.
10794 } else {10736fn analyzeSwitchBlock(
10795 try sema.analyzeUnreachable(block, operand_src, false);10737 sema: *Sema,
10796 return .unreachable_value;10738 block: *Block,
10797 },10739 child_block: *Block,
10798 else => return operand_val,10740 raw_operand: Air.Inst.Ref,
10799 }10741 operand_is_ref: bool,
10800 }10742 merges: *Block.Merges,
10743 switch_inst: Zir.Inst.Index,
10744 zir_switch: *const Zir.UnwrappedSwitchBlock,
10745 validated_switch: *const ValidatedSwitchBlock,
10746) CompileError!?Air.Inst.Ref {
10747 const pt = sema.pt;
10748 const zcu = pt.zcu;
10749 const gpa = sema.gpa;
1080110750
10802 switch (operand_ty.zigTypeTag(zcu)) {10751 const src_node_offset = zir_switch.switch_src_node_offset;
10803 .@"union" => {10752 const src = block.nodeOffset(src_node_offset);
10804 const union_obj = zcu.typeToUnion(operand_ty).?;10753 const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });
10805 const first_item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, case_vals[0], undefined) catch unreachable;
1080610754
10807 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;10755 const has_else = zir_switch.else_case != null;
10808 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);10756 const has_under = zir_switch.under_case != .none;
1080910757
10810 const field_indices = try sema.arena.alloc(u32, case_vals.len);10758 const else_case = validated_switch.else_case;
10811 for (case_vals, field_indices) |item, *field_idx| {10759 const under_case = validated_switch.under_case;
10812 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
10813 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
10814 }
1081510760
10816 // Fast path: if all the operands are the same type already, we don't need to hit10761 const operand: SwitchOperand, const operand_ty: Type, const maybe_operand_opv: ?Value, const item_ty: Type = operand: {
10817 // PTR! This will also allow us to emit simpler code.10762 const val, const ref = if (operand_is_ref)
10818 const same_types = for (field_indices[1..]) |field_idx| {10763 .{ try sema.analyzeLoad(block, src, raw_operand, operand_src), raw_operand }
10819 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);10764 else
10820 if (!field_ty.eql(first_field_ty, zcu)) break false;10765 .{ raw_operand, undefined };
10821 } else true;
1082210766
10823 const capture_ty = if (same_types) first_field_ty else capture_ty: {10767 const operand_ty = sema.typeOf(val);
10824 // We need values to run PTR on, so make a bunch of undef constants.10768 const maybe_operand_opv = try sema.typeHasOnePossibleValue(operand_ty);
10825 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);10769 const init_cond: Air.Inst.Ref, const item_ty: Type = switch (operand_ty.zigTypeTag(zcu)) {
10826 for (dummy_captures, field_indices) |*dummy, field_idx| {10770 .@"union" => tag: {
10827 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);10771 const tag_ty = operand_ty.unionTagType(zcu).?;
10828 dummy.* = try pt.undefRef(field_ty);10772 const tag_val = try sema.unionToTag(block, tag_ty, val, operand_src);
10829 }10773 break :tag .{ tag_val, tag_ty };
10774 },
10775 else => .{
10776 if (maybe_operand_opv) |operand_opv| .fromValue(operand_opv) else val,
10777 operand_ty,
10778 },
10779 };
1083010780
10831 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);10781 if (zir_switch.has_continue and !block.isComptime()) {
10832 for (case_srcs, 0..) |*case_src, i| {10782 const operand_alloc: Air.Inst.Ref = if (zir_switch.any_maybe_runtime_capture and
10833 case_src.* = .{10783 maybe_operand_opv == null)
10834 .base_node_inst = capture_src.base_node_inst,10784 alloc: {
10835 .offset = .{ .switch_case_item = .{10785 const operand_ptr_ty = try pt.singleMutPtrType(sema.typeOf(raw_operand));
10836 .switch_node_offset = switch_node_offset,10786 const operand_alloc = try block.addTy(.alloc, operand_ptr_ty);
10837 .case_idx = capture_src.offset.switch_capture.case_idx,10787 _ = try block.addBinOp(.store, operand_alloc, raw_operand);
10838 .item_idx = .{ .kind = .single, .index = @intCast(i) },10788 break :alloc operand_alloc;
10839 } },10789 } else undefined;
10840 };10790 break :operand .{ .{ .loop = .{
10841 }10791 .operand_alloc = operand_alloc,
10792 .operand_is_ref = operand_is_ref,
10793 .init_cond = init_cond,
10794 } }, operand_ty, maybe_operand_opv, item_ty };
10795 } else {
10796 // We always use `simple` in the comptime/OPV case, because as far as the
10797 // dispatching logic is concerned, it really is dispatching a single prong.
10798 break :operand .{ .{ .simple = .{
10799 .by_val = val,
10800 .by_ref = ref,
10801 .cond = init_cond,
10802 } }, operand_ty, maybe_operand_opv, item_ty };
10803 }
10804 };
1084210805
10843 break :capture_ty sema.resolvePeerTypes(block, capture_src, dummy_captures, .{ .override = case_srcs }) catch |err| switch (err) {10806 const raw_operand_ty = sema.typeOf(raw_operand);
10844 error.AnalysisFail => {
10845 const msg = sema.err orelse return error.AnalysisFail;
10846 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
10847 return error.AnalysisFail;
10848 },
10849 else => |e| return e,
10850 };
10851 };
1085210807
10853 // By-reference captures have some further restrictions which make them easier to emit10808 const union_originally = operand_ty.zigTypeTag(zcu) == .@"union";
10854 if (capture_byref) {10809 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
10855 const operand_ptr_info = operand_ptr_ty.ptrInfo(zcu);
10856 const capture_ptr_ty = resolve: {
10857 // By-ref captures of hetereogeneous types are only allowed if all field
10858 // pointer types are peer resolvable to each other.
10859 // We need values to run PTR on, so make a bunch of undef constants.
10860 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
10861 for (field_indices, dummy_captures) |field_idx, *dummy| {
10862 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
10863 const field_ptr_ty = try pt.ptrTypeSema(.{
10864 .child = field_ty.toIntern(),
10865 .flags = .{
10866 .is_const = operand_ptr_info.flags.is_const,
10867 .is_volatile = operand_ptr_info.flags.is_volatile,
10868 .address_space = operand_ptr_info.flags.address_space,
10869 .alignment = union_obj.fieldAlign(ip, field_idx),
10870 },
10871 });
10872 dummy.* = try pt.undefRef(field_ptr_ty);
10873 }
10874 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
10875 for (case_srcs, 0..) |*case_src, i| {
10876 case_src.* = .{
10877 .base_node_inst = capture_src.base_node_inst,
10878 .offset = .{ .switch_case_item = .{
10879 .switch_node_offset = switch_node_offset,
10880 .case_idx = capture_src.offset.switch_capture.case_idx,
10881 .item_idx = .{ .kind = .single, .index = @intCast(i) },
10882 } },
10883 };
10884 }
1088510810
10886 break :resolve sema.resolvePeerTypes(block, capture_src, dummy_captures, .{ .override = case_srcs }) catch |err| switch (err) {10811 if (item_ty.zigTypeTag(zcu) == .@"enum" and
10887 error.AnalysisFail => {10812 validated_switch.seen_enum_fields.len == 0 and
10888 const msg = sema.err orelse return error.AnalysisFail;10813 !operand_ty.isNonexhaustiveEnum(zcu))
10889 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});10814 {
10890 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});10815 return .void_value; // switch on empty enum/union
10891 return error.AnalysisFail;10816 }
10892 },
10893 else => |e| return e,
10894 };
10895 };
1089610817
10897 if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |op_ptr_val| {10818 const cond_ref = switch (operand) {
10898 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);10819 .simple => |s| s.cond,
10899 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);10820 .loop => |l| l.init_cond,
10900 return Air.internedToRef((try pt.getCoerced(field_ptr_val, capture_ptr_ty)).toIntern());10821 };
10901 }
1090210822
10903 try sema.requireRuntimeBlock(block, operand_src, null);10823 // We treat `else` and `_` the same, except if both are present.
10904 return block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);10824 const else_is_named_only = has_else and has_under;
10905 }10825 const catch_all_case: CatchAllSwitchCase =
10826 if (has_under) .under else if (has_else) .@"else" else .none;
1090610827
10907 if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |operand_val_val| {10828 resolve_at_comptime: {
10908 if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);10829 // always runtime; evaluation in comptime scope uses `simple`
10909 const union_val = ip.indexToKey(operand_val_val.toIntern()).un;10830 if (operand == .loop) break :resolve_at_comptime;
10910 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
10911 const uncoerced = Air.internedToRef(union_val.val);
10912 return sema.coerce(block, capture_ty, uncoerced, operand_src);
10913 }
1091410831
10915 try sema.requireRuntimeBlock(block, operand_src, null);10832 var cur_cond_val = try sema.resolveDefinedValue(child_block, src, cond_ref) orelse {
10833 break :resolve_at_comptime;
10834 };
10835 var cur_operand = operand;
1091610836
10917 if (same_types) {10837 while (true) {
10918 return block.addStructFieldVal(operand_val, first_field_index, capture_ty);10838 if (sema.resolveSwitchBlock(
10919 }10839 block,
10840 child_block,
10841 cur_operand,
10842 raw_operand_ty,
10843 cur_cond_val,
10844 catch_all_case,
10845 else_is_named_only,
10846 merges,
10847 switch_inst,
10848 zir_switch,
10849 validated_switch,
10850 )) |result| {
10851 return result;
10852 } else |err| switch (err) {
10853 error.ComptimeBreak => {
10854 const break_inst = sema.code.instructions.get(@intFromEnum(sema.comptime_break_inst));
10855 if (break_inst.tag != .switch_continue) return error.ComptimeBreak;
10856 const extra = sema.code.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data;
10857 if (extra.block_inst != switch_inst) return error.ComptimeBreak;
10858 // This is a `switch_continue` targeting this block. Change the operand and start over.
10859 const new_operand_src = child_block.nodeOffset(extra.operand_src_node.unwrap().?);
10860 const new_operand_uncoerced = try sema.resolveInst(break_inst.data.@"break".operand);
10861 const new_operand = try sema.coerce(child_block, raw_operand_ty, new_operand_uncoerced, new_operand_src);
10862
10863 try sema.emitBackwardBranch(child_block, src);
10864
10865 const new_val, const new_ref = if (operand_is_ref)
10866 .{ try sema.analyzeLoad(child_block, src, new_operand, new_operand_src), new_operand }
10867 else
10868 .{ new_operand, undefined };
1092010869
10921 // We may have to emit a switch block which coerces the operand to the capture type.10870 const new_cond_ref = if (union_originally)
10922 // If we can, try to avoid that using in-memory coercions.10871 try sema.unionToTag(child_block, item_ty, new_val, src)
10923 const first_non_imc = in_mem: {10872 else
10924 for (field_indices, 0..) |field_idx, i| {10873 new_val;
10925 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
10926 if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, zcu.getTarget(), LazySrcLoc.unneeded, LazySrcLoc.unneeded, null)) {
10927 break :in_mem i;
10928 }
10929 }
10930 // All fields are in-memory coercible to the resolved type!
10931 // Just take the first field and bitcast the result.
10932 const uncoerced = try block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
10933 return block.addBitCast(capture_ty, uncoerced);
10934 };
1093510874
10936 // By-val capture with heterogeneous types which are not all in-memory coercible to10875 cur_cond_val = try sema.resolveConstDefinedValue(child_block, src, new_cond_ref, null);
10937 // the resolved capture type. We finally have to fall back to the ugly method.10876 cur_operand = .{ .simple = .{
10877 .by_val = new_val,
10878 .by_ref = new_ref,
10879 .cond = new_cond_ref,
10880 } };
10881 },
10882 else => |e| return e,
10883 }
10884 }
10885 }
1093810886
10939 // However, let's first track which operands are in-memory coercible. There may well10887 if (child_block.isComptime()) {
10940 // be several, and we can squash all of these cases into the same switch prong using10888 _ = try sema.resolveConstDefinedValue(child_block, operand_src, operand.simple.cond, null);
10941 // a simple bitcast. We'll make this the 'else' prong.10889 unreachable;
10890 }
1094210891
10943 var in_mem_coercible = try std.DynamicBitSet.initFull(sema.arena, field_indices.len);10892 if (try sema.typeHasOnePossibleValue(item_ty)) |item_opv| {
10944 in_mem_coercible.unset(first_non_imc);10893 // We simplify conditions with OPV to either a `loop` or a `block` since
10945 {10894 // we cannot switch on a value which doesn't exist at runtime.
10946 const next = first_non_imc + 1;10895 assert(operand == .loop); // `simple` should have already been comptime-resolved above!
10947 for (field_indices[next..], next..) |field_idx, i| {10896
10948 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);10897 var case_block = child_block.makeSubBlock();
10949 if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, zcu.getTarget(), LazySrcLoc.unneeded, LazySrcLoc.unneeded, null)) {10898 case_block.runtime_loop = null;
10950 in_mem_coercible.unset(i);10899 case_block.runtime_cond = operand_src;
10900 case_block.runtime_index.increment();
10901 case_block.need_debug_scope = null; // this body is emitted regardless
10902 defer case_block.instructions.deinit(gpa);
10903
10904 const case_vals = validated_switch.case_vals;
10905
10906 const index, const body, const capture, const has_tag_capture, const is_inline, const is_special = find_prong: {
10907 var case_val_idx: usize = 0;
10908 var case_it = zir_switch.iterateCases();
10909 var extra_index = zir_switch.end;
10910 while (case_it.next()) |case| {
10911 const prong_info = case.prong_info;
10912 const prong_body = sema.code.bodySlice(extra_index, prong_info.body_len);
10913 extra_index += prong_body.len;
10914 skip_case: {
10915 if (!err_set) break :skip_case;
10916 // This case might consist of errors which are not in the set
10917 // we're switching on. If so we have to skip it!
10918 const item_refs = case_vals[case_val_idx..][0..case.item_infos.len];
10919 case_val_idx += item_refs.len;
10920 assert(case.range_infos.len == 0);
10921 for (case.item_infos, item_refs) |item_info, item_ref| {
10922 if (item_info.bodyLen()) |body_len| extra_index += body_len;
10923 if (sema.wantSwitchProngBodyAnalysis(block, item_ref, operand_ty, false, true, prong_info.is_comptime_unreach)) {
10924 break :skip_case;
10951 }10925 }
10952 }10926 }
10927 continue;
10953 }10928 }
10929 break :find_prong .{ case.index, prong_body, prong_info.capture, prong_info.has_tag_capture, prong_info.is_inline, false };
10930 }
10931 if (has_else) {
10932 // This *has* to be checked after iterating all regular cases because
10933 // we allow simple noreturn else prongs when switching on error sets!
10934 break :find_prong .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline, true };
10935 }
10936 if (has_under) {
10937 break :find_prong .{ under_case.index, under_case.body, under_case.capture, under_case.has_tag_capture, false, true };
10938 }
10939 unreachable; // malformed validated switch
10940 };
1095410941
10955 const capture_block_inst = try block.addInstAsIndex(.{10942 const analyze_body = sema.wantSwitchProngBodyAnalysis(block, .fromValue(item_opv), operand_ty, union_originally, err_set, false);
10956 .tag = .block,10943 if (!analyze_body) return .unreachable_value;
10957 .data = .{
10958 .ty_pl = .{
10959 .ty = Air.internedToRef(capture_ty.toIntern()),
10960 .payload = undefined, // updated below
10961 },
10962 },
10963 });
10964
10965 const prong_count = field_indices.len - in_mem_coercible.count();
10966
10967 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
10968 var cases_extra = try std.array_list.Managed(u32).initCapacity(sema.gpa, estimated_extra);
10969 defer cases_extra.deinit();
10970
10971 {
10972 // All branch hints are `.none`, so just add zero elems.
10973 comptime assert(@intFromEnum(std.builtin.BranchHint.none) == 0);
10974 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
10975 try cases_extra.appendNTimes(0, need_elems);
10976 }
1097710944
10978 {10945 if (!(err_set and
10979 // Non-bitcast cases10946 try sema.maybeErrorUnwrap(&case_block, body, cond_ref, operand_src, true)))
10980 var it = in_mem_coercible.iterator(.{ .kind = .unset });10947 {
10981 while (it.next()) |idx| {10948 // Set up captures manually to avoid special cases in the main logic.
10982 var coerce_block = block.makeSubBlock();10949 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
10983 defer coerce_block.instructions.deinit(sema.gpa);10950 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
1098410951 const payload_ref: Air.Inst.Ref = payload_ref: {
10985 const case_src: LazySrcLoc = .{10952 const item_val: InternPool.Index = switch (operand_ty.zigTypeTag(zcu)) {
10986 .base_node_inst = capture_src.base_node_inst,10953 .@"union" => item_val: {
10987 .offset = .{ .switch_case_item = .{10954 if (maybe_operand_opv) |operand_opv| {
10988 .switch_node_offset = switch_node_offset,10955 break :item_val zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val;
10989 .case_idx = capture_src.offset.switch_capture.case_idx,10956 }
10990 .item_idx = .{ .kind = .single, .index = @intCast(idx) },10957 assert(union_originally); // operand type must be union, otherwise it would be an OPV type here
10991 } },10958 assert(zir_switch.any_maybe_runtime_capture); // there's a payload capture
10992 };10959 const operand_val, const operand_ref = switch (operand) {
10960 .simple => unreachable,
10961 .loop => |l| load_operand: {
10962 const loaded = try sema.analyzeLoad(block, src, l.operand_alloc, src);
10963 if (l.operand_is_ref) {
10964 const by_val = try sema.analyzeLoad(block, src, loaded, src);
10965 break :load_operand .{ by_val, loaded };
10966 } else {
10967 break :load_operand .{ loaded, undefined };
10968 }
10969 },
10970 };
10971 break :payload_ref try sema.analyzeSwitchPayloadCapture(
10972 &case_block,
10973 operand,
10974 operand_val,
10975 operand_ref,
10976 operand_ty,
10977 operand_src,
10978 block.src(.{ .switch_capture = .{
10979 .switch_node_offset = src_node_offset,
10980 .case_idx = index,
10981 } }),
10982 capture == .by_ref,
10983 is_special,
10984 if (!is_special) case_vals else undefined,
10985 if (is_inline) .fromValue(item_opv) else .none,
10986 validated_switch.else_err_ty,
10987 );
10988 },
10989 else => item_opv.toIntern(),
10990 };
10991 break :payload_ref switch (capture) {
10992 .by_val => .fromIntern(item_val),
10993 .by_ref => try sema.uavRef(item_val),
10994 .none => unreachable,
10995 };
10996 };
10997 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
10998 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
10999 break :inst payload_inst;
11000 } else undefined;
11001 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1099311002
10994 const field_idx = field_indices[idx];11003 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
10995 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);11004 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
10996 const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);11005 sema.inst_map.putAssumeCapacity(tag_inst, .fromValue(item_opv));
10997 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);11006 break :inst tag_inst;
10998 _ = try coerce_block.addBr(capture_block_inst, coerced);11007 } else undefined;
1099911008 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
11000 try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
11001 1 + // `item`, no ranges
11002 coerce_block.instructions.items.len);
11003 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
11004 .items_len = 1,
11005 .ranges_len = 0,
11006 .body_len = @intCast(coerce_block.instructions.items.len),
11007 }));
11008 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item
11009 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
11010 }
11011 }
11012 const else_body_len = len: {
11013 // 'else' prong uses a bitcast
11014 var coerce_block = block.makeSubBlock();
11015 defer coerce_block.instructions.deinit(sema.gpa);
1101611009
11017 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;11010 if (zir_switch.has_continue) sema.inst_map.putAssumeCapacity(switch_inst, .fromType(raw_operand_ty));
11018 const first_imc_field_idx = field_indices[first_imc_item_idx];11011 defer if (zir_switch.has_continue) assert(sema.inst_map.remove(switch_inst));
11019 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
11020 const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);
11021 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
11022 _ = try coerce_block.addBr(capture_block_inst, coerced);
1102311012
11024 try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));11013 _ = try sema.analyzeBodyRuntimeBreak(&case_block, body);
11025 break :len coerce_block.instructions.items.len;11014 }
11026 };
1102711015
11028 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +11016 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len +
11029 cases_extra.items.len +11017 case_block.instructions.items.len);
11030 @typeInfo(Air.Block).@"struct".fields.len +11018 const payload_index = sema.addExtraAssumeCapacity(Air.Block{
11031 1);11019 .body_len = @intCast(case_block.instructions.items.len),
1103211020 });
11033 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);11021 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
11034 try sema.air_instructions.append(sema.gpa, .{
11035 .tag = .switch_br,
11036 .data = .{
11037 .pl_op = .{
11038 .operand = undefined, // set by switch below
11039 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
11040 .cases_len = @intCast(prong_count),
11041 .else_body_len = @intCast(else_body_len),
11042 }),
11043 },
11044 },
11045 });
11046 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
11047
11048 // Set up block body
11049 switch (spa.operand) {
11050 .simple => |s| {
11051 const air_datas = sema.air_instructions.items(.data);
11052 air_datas[switch_br_inst].pl_op.operand = s.cond;
11053 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
11054 .body_len = 1,
11055 });
11056 sema.air_extra.appendAssumeCapacity(switch_br_inst);
11057 },
11058 .loop => {
11059 // The block must first extract the tag from the loaded union.
11060 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
11061 try sema.air_instructions.append(sema.gpa, .{
11062 .tag = .get_union_tag,
11063 .data = .{ .ty_op = .{
11064 .ty = Air.internedToRef(union_obj.enum_tag_ty),
11065 .operand = operand_val,
11066 } },
11067 });
11068 const air_datas = sema.air_instructions.items(.data);
11069 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
11070 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
11071 .body_len = 2,
11072 });
11073 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
11074 sema.air_extra.appendAssumeCapacity(switch_br_inst);
11075 },
11076 }
1107711022
11078 return capture_block_inst.toRef();11023 const air_tag: Air.Inst.Tag = if (merges.extra_insts.items.len > 0)
11079 },11024 .loop
11080 .error_set => {11025 else
11081 if (capture_byref) {11026 .block;
11082 return sema.fail(11027 const air_loop_ref = try child_block.addInst(.{
11083 block,11028 .tag = air_tag,
11084 capture_src,11029 .data = .{ .ty_pl = .{
11085 "error set cannot be captured by reference",11030 .ty = .noreturn_type,
11086 .{},11031 .payload = payload_index,
11087 );11032 } },
11088 }11033 });
11034 try sema.fixupSwitchContinues(
11035 block,
11036 src,
11037 air_loop_ref,
11038 operand,
11039 operand_is_ref,
11040 item_ty,
11041 .opv,
11042 zir_switch.any_maybe_runtime_capture,
11043 merges,
11044 );
11045 return null;
11046 }
1108911047
11090 if (case_vals.len == 1) {11048 assert(maybe_operand_opv == null); // `operand_ty` can only be an OPV type if `item_ty` is one too!
11091 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, case_vals[0], undefined) catch unreachable;
11092 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
11093 return sema.bitCast(block, item_ty, operand_val, operand_src, null);
11094 }
1109511049
11096 var names: InferredErrorSet.NameMap = .{};11050 try sema.finishSwitchBr(
11097 try names.ensureUnusedCapacity(sema.arena, case_vals.len);11051 block,
11098 for (case_vals) |err| {11052 child_block,
11099 const err_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, err, undefined) catch unreachable;11053 operand,
11100 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});11054 raw_operand_ty,
11101 }11055 operand_is_ref,
11102 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());11056 merges,
11103 return sema.bitCast(block, error_ty, operand_val, operand_src, null);11057 switch_inst,
11104 },11058 zir_switch,
11105 else => {11059 validated_switch,
11106 // In this case the capture value is just the passed-through value11060 );
11107 // of the switch condition.11061 return null;
11108 if (capture_byref) {11062}
11109 return operand_ptr;
11110 } else {
11111 return operand_val;
11112 }
11113 },
11114 }
11115 }
11116};
1111711063
11118fn switchCond(11064fn finishSwitchBr(
11119 sema: *Sema,11065 sema: *Sema,
11120 block: *Block,11066 block: *Block,
11121 src: LazySrcLoc,11067 child_block: *Block,
11122 operand: Air.Inst.Ref,11068 operand: SwitchOperand,
11123) CompileError!Air.Inst.Ref {11069 raw_operand_ty: Type,
11070 operand_is_ref: bool,
11071 merges: *Block.Merges,
11072 switch_inst: Zir.Inst.Index,
11073 zir_switch: *const Zir.UnwrappedSwitchBlock,
11074 validated_switch: *const ValidatedSwitchBlock,
11075) CompileError!void {
11124 const pt = sema.pt;11076 const pt = sema.pt;
11125 const zcu = pt.zcu;11077 const zcu = pt.zcu;
11126 const operand_ty = sema.typeOf(operand);11078 const ip = &zcu.intern_pool;
11127 switch (operand_ty.zigTypeTag(zcu)) {11079 const gpa = sema.gpa;
11128 .type,
11129 .void,
11130 .bool,
11131 .int,
11132 .float,
11133 .comptime_float,
11134 .comptime_int,
11135 .enum_literal,
11136 .pointer,
11137 .@"fn",
11138 .error_set,
11139 .@"enum",
11140 => {
11141 if (operand_ty.isSlice(zcu)) {
11142 return sema.fail(block, src, "switch on type '{f}'", .{operand_ty.fmt(pt)});
11143 }
11144 if ((try sema.typeHasOnePossibleValue(operand_ty))) |opv| {
11145 return Air.internedToRef(opv.toIntern());
11146 }
11147 return operand;
11148 },
1114911080
11150 .@"union" => {11081 const src_node_offset = zir_switch.switch_src_node_offset;
11151 try operand_ty.resolveFields(pt);11082 const src = block.nodeOffset(src_node_offset);
11152 const enum_ty = operand_ty.unionTagType(zcu) orelse {11083 const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });
11153 const msg = msg: {
11154 const msg = try sema.errMsg(src, "switch on union with no attached enum", .{});
11155 errdefer msg.destroy(sema.gpa);
11156 if (operand_ty.srcLocOrNull(zcu)) |union_src| {
11157 try sema.errNote(union_src, msg, "consider 'union(enum)' here", .{});
11158 }
11159 break :msg msg;
11160 };
11161 return sema.failWithOwnedErrorMsg(block, msg);
11162 };
11163 return sema.unionToTag(block, enum_ty, operand, src);
11164 },
1116511084
11166 .error_union,11085 const has_else = zir_switch.else_case != null;
11167 .noreturn,11086 const has_under = zir_switch.under_case != .none;
11168 .array,
11169 .@"struct",
11170 .undefined,
11171 .null,
11172 .optional,
11173 .@"opaque",
11174 .vector,
11175 .frame,
11176 .@"anyframe",
11177 => return sema.fail(block, src, "switch on type '{f}'", .{operand_ty.fmt(pt)}),
11178 }
11179}
1118011087
11181const SwitchErrorSet = std.AutoHashMap(InternPool.NullTerminatedString, LazySrcLoc);11088 const else_case = validated_switch.else_case;
11089 const under_case = validated_switch.under_case;
1118211090
11183fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {11091 const scalar_cases_len = zir_switch.scalarCasesLen();
11184 const tracy = trace(@src());11092 const multi_cases_len = zir_switch.multiCasesLen();
11185 defer tracy.end();
1118611093
11187 const pt = sema.pt;11094 const operand_ty = if (operand_is_ref)
11188 const zcu = pt.zcu;11095 raw_operand_ty.childType(zcu)
11189 const gpa = sema.gpa;11096 else
11190 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;11097 raw_operand_ty;
11191 const switch_src = block.nodeOffset(inst_data.src_node);
11192 const switch_src_node_offset = inst_data.src_node;
11193 const switch_operand_src = block.src(.{ .node_offset_switch_operand = switch_src_node_offset });
11194 const else_prong_src = block.src(.{ .node_offset_switch_else_prong = switch_src_node_offset });
11195 const extra = sema.code.extraData(Zir.Inst.SwitchBlockErrUnion, inst_data.payload_index);
11196 const main_operand_src = block.src(.{ .node_offset_if_cond = extra.data.main_src_node_offset });
11197 const main_src = block.src(.{ .node_offset_main_token = extra.data.main_src_node_offset });
1119811098
11199 const raw_operand_val = try sema.resolveInst(extra.data.operand);11099 const cond_ref = switch (operand) {
11100 .simple => |s| s.cond,
11101 .loop => |l| l.init_cond,
11102 };
1120011103
11201 // AstGen guarantees that the instruction immediately preceding11104 // AstGen guarantees that the instruction immediately preceding
11202 // switch_block_err_union is a dbg_stmt11105 // switch_block[_ref]/switch_block_err_union is a dbg_stmt.
11203 const cond_dbg_node_index: Zir.Inst.Index = @enumFromInt(@intFromEnum(inst) - 1);11106 const cond_dbg_node_index: Zir.Inst.Index = @enumFromInt(@intFromEnum(switch_inst) - 1);
11204
11205 var header_extra_index: usize = extra.end;
11206
11207 const scalar_cases_len = extra.data.bits.scalar_cases_len;
11208 const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
11209 const multi_cases_len = sema.code.extra[header_extra_index];
11210 header_extra_index += 1;
11211 break :blk multi_cases_len;
11212 } else 0;
11213
11214 const err_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_uses_err_capture) blk: {
11215 const err_capture_inst: Zir.Inst.Index = @enumFromInt(sema.code.extra[header_extra_index]);
11216 header_extra_index += 1;
11217 // SwitchProngAnalysis wants inst_map to have space for the tag capture.
11218 // Note that the normal capture is referred to via the switch block
11219 // index, which there is already necessarily space for.
11220 try sema.inst_map.ensureSpaceForInstructions(gpa, &.{err_capture_inst});
11221 break :blk err_capture_inst;
11222 } else undefined;
1122311107
11224 var case_vals = try std.ArrayList(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len);11108 const else_is_named_only = has_else and has_under;
11225 defer case_vals.deinit(gpa);11109 const catch_all_case: CatchAllSwitchCase =
11110 if (has_under) .under else if (has_else) .@"else" else .none;
1122611111
11227 const NonError = struct {11112 const item_ty = switch (operand_ty.zigTypeTag(zcu)) {
11228 body: []const Zir.Inst.Index,11113 .@"union" => operand_ty.unionTagType(zcu).?,
11229 end: usize,11114 else => operand_ty,
11230 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
11231 };11115 };
11116 const union_originally = operand_ty.zigTypeTag(zcu) == .@"union";
11117 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
1123211118
11233 const non_error_case: NonError = non_error: {11119 const estimated_cases_len: u32 = scalar_cases_len + multi_cases_len +
11234 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[header_extra_index]);11120 @intFromBool(has_else or has_under);
11235 const extra_body_start = header_extra_index + 1;
11236 break :non_error .{
11237 .body = sema.code.bodySlice(extra_body_start, info.body_len),
11238 .end = extra_body_start + info.body_len,
11239 .capture = info.capture,
11240 };
11241 };
1124211121
11243 const Else = struct {11122 var cases_extra: std.ArrayList(u32) = try .initCapacity(gpa, estimated_cases_len *
11244 body: []const Zir.Inst.Index,11123 @typeInfo(Air.SwitchBr.Case).@"struct".fields.len);
11245 end: usize,11124 defer cases_extra.deinit(gpa);
11246 is_inline: bool,11125 var branch_hints: Air.SwitchBr.BranchHints = try .initCapacity(gpa, estimated_cases_len);
11247 has_capture: bool,11126 defer branch_hints.deinit(gpa);
11248 };
11249
11250 const else_case: Else = if (!extra.data.bits.has_else) .{
11251 .body = &.{},
11252 .end = non_error_case.end,
11253 .is_inline = false,
11254 .has_capture = false,
11255 } else special: {
11256 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[non_error_case.end]);
11257 const extra_body_start = non_error_case.end + 1;
11258 assert(info.capture != .by_ref);
11259 assert(!info.has_tag_capture);
11260 break :special .{
11261 .body = sema.code.bodySlice(extra_body_start, info.body_len),
11262 .end = extra_body_start + info.body_len,
11263 .is_inline = info.is_inline,
11264 .has_capture = info.capture != .none,
11265 };
11266 };
1126711127
11268 var seen_errors = SwitchErrorSet.init(gpa);11128 // We will reuse this block for each case.
11269 defer seen_errors.deinit();11129 var case_block = child_block.makeSubBlock();
11130 case_block.runtime_loop = null;
11131 case_block.runtime_cond = operand_src;
11132 case_block.runtime_index.increment();
11133 case_block.need_debug_scope = null; // this body is emitted regardless
11134 defer case_block.instructions.deinit(gpa);
1127011135
11271 const operand_ty = sema.typeOf(raw_operand_val);11136 const case_vals = validated_switch.case_vals;
11272 const operand_err_set = if (extra.data.bits.payload_is_ref)11137 var case_val_idx: usize = 0;
11273 operand_ty.childType(zcu)11138 var case_it = zir_switch.iterateCases();
11274 else11139 var extra_index = zir_switch.end;
11275 operand_ty;
1127611140
11277 if (operand_err_set.zigTypeTag(zcu) != .error_union) {11141 var cases_len: u32 = 0;
11278 return sema.fail(block, switch_src, "expected error union type, found '{f}'", .{11142 while (case_it.next()) |case| {
11279 operand_ty.fmt(pt),11143 if (case.isUnder()) { // we'll deal with this later
11280 });11144 extra_index += case.prong_info.body_len;
11281 }11145 for (case.item_infos) |item_info| {
11146 if (item_info.bodyLen()) |body_len| extra_index += body_len;
11147 }
11148 for (case.range_infos) |range_info| {
11149 if (range_info[0].bodyLen()) |body_len| extra_index += body_len;
11150 if (range_info[1].bodyLen()) |body_len| extra_index += body_len;
11151 }
11152 continue;
11153 }
1128211154
11283 const operand_err_set_ty = operand_err_set.errorUnionSet(zcu);11155 const item_refs = case_vals[case_val_idx..][0..case.item_infos.len];
11156 case_val_idx += item_refs.len;
11157 const range_refs: []const [2]Air.Inst.Ref =
11158 @ptrCast(case_vals[case_val_idx..][0 .. 2 * case.range_infos.len]);
11159 case_val_idx += 2 * range_refs.len;
1128411160
11285 const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);11161 const prong_info = case.prong_info;
11286 try sema.air_instructions.append(gpa, .{11162 const prong_body = sema.code.bodySlice(extra_index, prong_info.body_len);
11287 .tag = .block,11163 extra_index += prong_body.len;
11288 .data = undefined,
11289 });
11290 var label: Block.Label = .{
11291 .zir_block = inst,
11292 .merges = .{
11293 .src_locs = .{},
11294 .results = .{},
11295 .br_list = .{},
11296 .block_inst = block_inst,
11297 },
11298 };
1129911164
11300 var child_block: Block = .{11165 // Enough capacity for inlining regular items, we can't really predict
11301 .parent = block,11166 // how many range items we will end up with (at least not in a safe and
11302 .sema = sema,11167 // cheap manner) so we allocate on demand for those.
11303 .namespace = block.namespace,11168 if (prong_info.is_inline) {
11304 .instructions = .{},11169 try branch_hints.ensureUnusedCapacity(gpa, @intCast(case.item_infos.len));
11305 .label = &label,11170 }
11306 .inlining = block.inlining,
11307 .comptime_reason = block.comptime_reason,
11308 .is_typeof = block.is_typeof,
11309 .c_import_buf = block.c_import_buf,
11310 .runtime_cond = block.runtime_cond,
11311 .runtime_loop = block.runtime_loop,
11312 .runtime_index = block.runtime_index,
11313 .error_return_trace_index = block.error_return_trace_index,
11314 .want_safety = block.want_safety,
11315 .src_base_inst = block.src_base_inst,
11316 .type_name_ctx = block.type_name_ctx,
11317 };
11318 const merges = &child_block.label.?.merges;
11319 defer child_block.instructions.deinit(gpa);
11320 defer merges.deinit(gpa);
1132111171
11322 const resolved_err_set = try sema.resolveInferredErrorSetTy(block, main_src, operand_err_set_ty.toIntern());11172 var emit_bb = false;
11323 if (Type.fromInterned(resolved_err_set).errorSetIsEmpty(zcu)) {11173 var any_analyze_body = false;
11324 return sema.resolveBlockBody(block, main_operand_src, &child_block, non_error_case.body, inst, merges);11174 for (case.item_infos, item_refs, 0..) |item_info, item_ref, item_i| {
11325 }11175 if (item_info.bodyLen()) |body_len| extra_index += body_len;
1132611176
11327 const else_error_ty: ?Type = try validateErrSetSwitch(11177 const analyze_body = sema.wantSwitchProngBodyAnalysis(block, item_ref, operand_ty, union_originally, err_set, prong_info.is_comptime_unreach);
11328 sema,11178 if (analyze_body) any_analyze_body = true;
11329 block,
11330 &seen_errors,
11331 &case_vals,
11332 operand_err_set_ty,
11333 inst_data,
11334 scalar_cases_len,
11335 multi_cases_len,
11336 .{ .body = else_case.body, .end = else_case.end, .src = else_prong_src },
11337 extra.data.bits.has_else,
11338 );
1133911179
11340 var spa: SwitchProngAnalysis = .{11180 if (prong_info.is_inline) {
11341 .sema = sema,11181 cases_len += 1;
11342 .parent_block = block,11182 case_block.instructions.clearRetainingCapacity();
11343 .operand = .{11183 case_block.error_return_trace_index = child_block.error_return_trace_index;
11344 .simple = .{
11345 .by_val = undefined, // must be set to the unwrapped error code before use
11346 .by_ref = undefined,
11347 .cond = raw_operand_val,
11348 },
11349 },
11350 .else_error_ty = else_error_ty,
11351 .switch_block_inst = inst,
11352 .tag_capture_inst = undefined,
11353 };
1135411184
11355 if (try sema.resolveDefinedValue(&child_block, main_src, raw_operand_val)) |ov| {11185 if (emit_bb) {
11356 const operand_val = if (extra.data.bits.payload_is_ref)11186 const bb_src = block.src(.{ .switch_case_item = .{
11357 (try sema.pointerDeref(&child_block, main_src, ov, operand_ty)).?11187 .switch_node_offset = src_node_offset,
11358 else11188 .case_idx = case.index,
11359 ov;11189 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
11190 } });
11191 try sema.emitBackwardBranch(block, bb_src);
11192 }
11193 emit_bb = true;
1136011194
11361 if (operand_val.errorUnionIsPayload(zcu)) {11195 const prong_hint: std.builtin.BranchHint = hint: {
11362 return sema.resolveBlockBody(block, main_operand_src, &child_block, non_error_case.body, inst, merges);11196 if (analyze_body) break :hint try sema.analyzeSwitchProng(
11363 } else {11197 &case_block,
11364 const err_val = Value.fromInterned(try pt.intern(.{11198 operand,
11365 .err = .{11199 operand_ty,
11366 .ty = operand_err_set_ty.toIntern(),11200 raw_operand_ty,
11367 .name = operand_val.getErrorName(zcu).unwrap().?,11201 prong_body,
11368 },11202 block.src(.{ .switch_capture = .{
11369 }));11203 .switch_node_offset = src_node_offset,
11370 spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref)11204 .case_idx = case.index,
11371 try sema.analyzeErrUnionCodePtr(block, switch_operand_src, raw_operand_val)11205 } }),
11372 else11206 prong_info.capture,
11373 try sema.analyzeErrUnionCode(block, switch_operand_src, raw_operand_val);11207 prong_info.has_tag_capture,
11208 item_ref,
11209 .{ .item_refs = &.{item_ref} },
11210 validated_switch.else_err_ty,
11211 switch_inst,
11212 zir_switch,
11213 );
11214 _ = try case_block.addNoOp(.unreach);
11215 break :hint .cold; // unreachable branches are cold
11216 };
11217 branch_hints.appendAssumeCapacity(prong_hint);
1137411218
11375 if (extra.data.bits.any_uses_err_capture) {11219 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
11376 sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val);11220 1 + // `item`, no ranges
11221 case_block.instructions.items.len);
11222 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
11223 .items_len = 1,
11224 .ranges_len = 0,
11225 .body_len = @intCast(case_block.instructions.items.len),
11226 }));
11227 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
11228 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
11377 }11229 }
11378 defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));
11379
11380 return resolveSwitchComptime(
11381 sema,
11382 spa,
11383 &child_block,
11384 try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val),
11385 err_val,
11386 operand_err_set_ty,
11387 switch_src_node_offset,
11388 null,
11389 .{
11390 .body = else_case.body,
11391 .end = else_case.end,
11392 .capture = if (else_case.has_capture) .by_val else .none,
11393 .is_inline = else_case.is_inline,
11394 .has_tag_capture = false,
11395 },
11396 false,
11397 case_vals,
11398 scalar_cases_len,
11399 multi_cases_len,
11400 true,
11401 false,
11402 );
11403 }11230 }
11404 }11231 for (case.range_infos, range_refs, 0..) |range_info, range_ref, range_i| {
11232 if (range_info[0].bodyLen()) |body_len| extra_index += body_len;
11233 if (range_info[1].bodyLen()) |body_len| extra_index += body_len;
1140511234
11406 if (scalar_cases_len + multi_cases_len == 0) {11235 any_analyze_body = true; // always an integer range, always needs analysis
11407 if (else_error_ty) |ty| if (ty.errorSetIsEmpty(zcu)) {
11408 return sema.resolveBlockBody(block, main_operand_src, &child_block, non_error_case.body, inst, merges);
11409 };
11410 }
1141111236
11412 if (child_block.isComptime()) {11237 if (prong_info.is_inline) {
11413 _ = try sema.resolveConstDefinedValue(&child_block, main_operand_src, raw_operand_val, null);11238 var item = sema.resolveConstDefinedValue(block, .unneeded, range_ref[0], undefined) catch unreachable;
11414 unreachable;11239 const item_last = sema.resolveConstDefinedValue(block, .unneeded, range_ref[1], undefined) catch unreachable;
11415 }
1141611240
11417 const cond = if (extra.data.bits.payload_is_ref) blk: {11241 if (try item.getUnsignedIntSema(pt)) |first_int| {
11418 try sema.checkErrorType(block, main_src, sema.typeOf(raw_operand_val).elemType2(zcu));11242 if (try item_last.getUnsignedIntSema(pt)) |last_int| {
11419 const loaded = try sema.analyzeLoad(block, main_src, raw_operand_val, main_src);11243 if (std.math.cast(u32, last_int - first_int)) |range_len| {
11420 break :blk try sema.analyzeIsNonErr(block, main_src, loaded);11244 try branch_hints.ensureUnusedCapacity(gpa, range_len);
11421 } else blk: {11245 }
11422 try sema.checkErrorType(block, main_src, sema.typeOf(raw_operand_val));11246 }
11423 break :blk try sema.analyzeIsNonErr(block, main_src, raw_operand_val);11247 }
11424 };
1142511248
11426 var sub_block = child_block.makeSubBlock();11249 var prev_result_overflowed = false;
11427 sub_block.runtime_loop = null;11250 while (item.compareScalar(.lte, item_last, operand_ty, zcu)) : ({
11428 sub_block.runtime_cond = main_operand_src;11251 // Previous validation has resolved any possible lazy values.
11429 sub_block.runtime_index.increment();11252 const int_val: Value, const int_ty: Type = switch (operand_ty.zigTypeTag(zcu)) {
11430 sub_block.need_debug_scope = null; // this body is emitted regardless11253 .int => .{ item, operand_ty },
11431 defer sub_block.instructions.deinit(gpa);11254 .@"enum" => b: {
11255 const int_val: Value = .fromInterned(ip.indexToKey(item.toIntern()).enum_tag.int);
11256 break :b .{ int_val, int_val.typeOf(zcu) };
11257 },
11258 else => unreachable,
11259 };
11260 assert(!prev_result_overflowed);
11261 const result = try arith.incrementDefinedInt(sema, int_ty, int_val);
11262 prev_result_overflowed = result.overflow;
11263 item = switch (operand_ty.zigTypeTag(zcu)) {
11264 .int => result.val,
11265 .@"enum" => .fromInterned(try pt.intern(.{ .enum_tag = .{
11266 .ty = operand_ty.toIntern(),
11267 .int = result.val.toIntern(),
11268 } })),
11269 else => unreachable,
11270 };
11271 }) {
11272 cases_len += 1;
11273 case_block.instructions.clearRetainingCapacity();
11274 case_block.error_return_trace_index = child_block.error_return_trace_index;
1143211275
11433 const non_error_hint = try sema.analyzeBodyRuntimeBreak(&sub_block, non_error_case.body);11276 const item_ref: Air.Inst.Ref = .fromValue(item);
11434 const true_instructions = try sub_block.instructions.toOwnedSlice(gpa);
11435 defer gpa.free(true_instructions);
1143611277
11437 spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref)11278 if (emit_bb) {
11438 try sema.analyzeErrUnionCodePtr(&sub_block, switch_operand_src, raw_operand_val)11279 const bb_src = block.src(.{ .switch_case_item = .{
11439 else11280 .switch_node_offset = src_node_offset,
11440 try sema.analyzeErrUnionCode(&sub_block, switch_operand_src, raw_operand_val);11281 .case_idx = case.index,
1144111282 .item_idx = .{ .kind = .range, .value = @intCast(range_i) },
11442 if (extra.data.bits.any_uses_err_capture) {11283 } });
11443 sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val);11284 try sema.emitBackwardBranch(block, bb_src);
11444 }11285 }
11445 defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));11286 emit_bb = true;
11446 _ = try sema.analyzeSwitchRuntimeBlock(
11447 spa,
11448 &sub_block,
11449 switch_src,
11450 try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val),
11451 operand_err_set_ty,
11452 switch_operand_src,
11453 case_vals,
11454 .{
11455 .body = else_case.body,
11456 .end = else_case.end,
11457 .capture = if (else_case.has_capture) .by_val else .none,
11458 .is_inline = else_case.is_inline,
11459 .has_tag_capture = false,
11460 },
11461 scalar_cases_len,
11462 multi_cases_len,
11463 false,
11464 undefined,
11465 true,
11466 switch_src_node_offset,
11467 else_prong_src,
11468 false,
11469 undefined,
11470 seen_errors,
11471 undefined,
11472 undefined,
11473 undefined,
11474 cond_dbg_node_index,
11475 true,
11476 null,
11477 undefined,
11478 &.{},
11479 &.{},
11480 );
1148111287
11482 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).@"struct".fields.len +11288 const prong_hint = try sema.analyzeSwitchProng(
11483 true_instructions.len + sub_block.instructions.items.len);11289 &case_block,
11290 operand,
11291 operand_ty,
11292 raw_operand_ty,
11293 prong_body,
11294 block.src(.{ .switch_capture = .{
11295 .switch_node_offset = src_node_offset,
11296 .case_idx = case.index,
11297 } }),
11298 prong_info.capture,
11299 prong_info.has_tag_capture,
11300 item_ref,
11301 .has_ranges,
11302 validated_switch.else_err_ty,
11303 switch_inst,
11304 zir_switch,
11305 );
11306 try branch_hints.append(gpa, prong_hint);
1148411307
11485 _ = try child_block.addInst(.{11308 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
11486 .tag = .cond_br,11309 1 + // `item`, no ranges
11487 .data = .{11310 case_block.instructions.items.len);
11488 .pl_op = .{11311 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
11489 .operand = cond,11312 .items_len = 1,
11490 .payload = sema.addExtraAssumeCapacity(Air.CondBr{11313 .ranges_len = 0,
11491 .then_body_len = @intCast(true_instructions.len),11314 .body_len = @intCast(case_block.instructions.items.len),
11492 .else_body_len = @intCast(sub_block.instructions.items.len),11315 }));
11493 .branch_hints = .{11316 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
11494 .true = non_error_hint,11317 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
11495 .false = .none,11318 }
11496 // Code coverage is desired for error handling.11319 }
11497 .then_cov = .poi,11320 }
11498 .else_cov = .poi,
11499 },
11500 }),
11501 },
11502 },
11503 });
11504 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions));
11505 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));
1150611321
11507 return sema.resolveAnalyzedBlock(block, main_src, &child_block, merges, false);11322 if (!prong_info.is_inline) {
11508}11323 cases_len += 1;
11324 case_block.instructions.clearRetainingCapacity();
11325 case_block.error_return_trace_index = child_block.error_return_trace_index;
1150911326
11510fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_ref: bool) CompileError!Air.Inst.Ref {11327 const prong_hint: std.builtin.BranchHint = hint: {
11511 const tracy = trace(@src());11328 if (any_analyze_body) break :hint try sema.analyzeSwitchProng(
11512 defer tracy.end();11329 &case_block,
11330 operand,
11331 operand_ty,
11332 raw_operand_ty,
11333 prong_body,
11334 block.src(.{ .switch_capture = .{
11335 .switch_node_offset = src_node_offset,
11336 .case_idx = case.index,
11337 } }),
11338 prong_info.capture,
11339 prong_info.has_tag_capture,
11340 .none,
11341 .{ .item_refs = item_refs },
11342 validated_switch.else_err_ty,
11343 switch_inst,
11344 zir_switch,
11345 );
11346 _ = try case_block.addNoOp(.unreach);
11347 break :hint .cold; // unreachable branches are cold
11348 };
11349 try branch_hints.append(gpa, prong_hint);
11350
11351 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
11352 item_refs.len +
11353 2 * range_refs.len +
11354 case_block.instructions.items.len);
11355 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
11356 .items_len = @intCast(item_refs.len),
11357 .ranges_len = @intCast(range_refs.len),
11358 .body_len = @intCast(case_block.instructions.items.len),
11359 }));
11360 cases_extra.appendSliceAssumeCapacity(@ptrCast(item_refs));
11361 cases_extra.appendSliceAssumeCapacity(@ptrCast(range_refs));
11362 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
11363 }
11364 }
1151311365
11514 const pt = sema.pt;11366 const catch_all_extra: []const u32 = catch_all_extra: {
11515 const zcu = pt.zcu;11367 if (catch_all_case == .none and !case_block.wantSafety()) {
11516 const ip = &zcu.intern_pool;11368 try branch_hints.append(gpa, .none);
11517 const gpa = sema.gpa;11369 break :catch_all_extra &.{};
11518 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;11370 }
11519 const src = block.nodeOffset(inst_data.src_node);11371 var emit_bb = false;
11520 const src_node_offset = inst_data.src_node;11372 if (has_else and else_case.is_inline) {
11521 const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });11373 const else_prong_src = block.src(.{ .node_offset_switch_else_prong = src_node_offset });
11522 const else_prong_src = block.src(.{ .node_offset_switch_else_prong = src_node_offset });11374 var error_names: InternPool.NullTerminatedString.Slice = undefined;
11523 const under_prong_src = block.src(.{ .node_offset_switch_under_prong = src_node_offset });11375 var min_int: Value = undefined;
11524 const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);11376 check_enumerable: {
11377 switch (item_ty.zigTypeTag(zcu)) {
11378 .@"union" => unreachable,
11379 .@"enum" => if (else_is_named_only or
11380 !item_ty.isNonexhaustiveEnum(zcu) or union_originally)
11381 {
11382 try branch_hints.ensureUnusedCapacity(gpa, @intCast(validated_switch.seen_enum_fields.len));
11383 break :check_enumerable;
11384 },
11385 .error_set => if (!operand_ty.isAnyError(zcu)) {
11386 error_names = item_ty.errorSetNames(zcu);
11387 try branch_hints.ensureUnusedCapacity(gpa, error_names.len);
11388 break :check_enumerable;
11389 },
11390 .int => {
11391 min_int = try item_ty.minInt(pt, item_ty);
11392 break :check_enumerable;
11393 },
11394 .bool, .void => break :check_enumerable,
11395 else => {},
11396 }
11397 return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{
11398 item_ty.fmt(pt),
11399 });
11400 }
11401 var unhandled_it = validated_switch.iterateUnhandledItems(error_names, min_int);
11402 while (try unhandled_it.next(sema, item_ty)) |item_val| {
11403 cases_len += 1;
11404 case_block.instructions.clearRetainingCapacity();
11405 case_block.error_return_trace_index = child_block.error_return_trace_index;
1152511406
11526 const operand: SwitchProngAnalysis.Operand, const raw_operand_ty: Type = op: {11407 const item_ref: Air.Inst.Ref = .fromValue(item_val);
11527 const maybe_ptr = try sema.resolveInst(extra.data.operand);
11528 const val, const ref = if (operand_is_ref)
11529 .{ try sema.analyzeLoad(block, src, maybe_ptr, operand_src), maybe_ptr }
11530 else
11531 .{ maybe_ptr, undefined };
1153211408
11533 const init_cond = try sema.switchCond(block, operand_src, val);11409 const analyze_body = sema.wantSwitchProngBodyAnalysis(block, item_ref, operand_ty, union_originally, err_set, false);
1153411410
11535 const operand_ty = sema.typeOf(val);11411 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
11412 emit_bb = true;
1153611413
11537 if (extra.data.bits.has_continue and !block.isComptime()) {11414 const prong_hint: std.builtin.BranchHint = hint: {
11538 // Even if the operand is comptime-known, this `switch` is runtime.11415 if (analyze_body) break :hint try sema.analyzeSwitchProng(
11539 if (try operand_ty.comptimeOnlySema(pt)) {11416 &case_block,
11540 return sema.failWithOwnedErrorMsg(block, msg: {11417 operand,
11541 const msg = try sema.errMsg(operand_src, "operand of switch loop has comptime-only type '{f}'", .{operand_ty.fmt(pt)});11418 operand_ty,
11542 errdefer msg.destroy(gpa);11419 raw_operand_ty,
11543 try sema.errNote(operand_src, msg, "switch loops are evaluated at runtime outside of comptime scopes", .{});11420 else_case.body,
11544 break :msg msg;11421 block.src(.{ .switch_capture = .{
11545 });11422 .switch_node_offset = src_node_offset,
11423 .case_idx = else_case.index,
11424 } }),
11425 else_case.capture,
11426 else_case.has_tag_capture,
11427 item_ref,
11428 .special,
11429 validated_switch.else_err_ty,
11430 switch_inst,
11431 zir_switch,
11432 );
11433 _ = try case_block.addNoOp(.unreach);
11434 break :hint .cold; // unreachable branches are cold
11435 };
11436 try branch_hints.append(gpa, prong_hint);
11437
11438 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
11439 1 + // `item`, no ranges
11440 case_block.instructions.items.len);
11441 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
11442 .items_len = 1,
11443 .ranges_len = 0,
11444 .body_len = @intCast(case_block.instructions.items.len),
11445 }));
11446 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
11447 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
11546 }11448 }
11547 try sema.validateRuntimeValue(block, operand_src, maybe_ptr);
11548 const operand_alloc = if (extra.data.bits.any_non_inline_capture or
11549 extra.data.bits.any_has_tag_capture)
11550 a: {
11551 const operand_ptr_ty = try pt.singleMutPtrType(sema.typeOf(maybe_ptr));
11552 const operand_alloc = try block.addTy(.alloc, operand_ptr_ty);
11553 _ = try block.addBinOp(.store, operand_alloc, maybe_ptr);
11554 break :a operand_alloc;
11555 } else undefined;
11556 break :op .{
11557 .{ .loop = .{
11558 .operand_alloc = operand_alloc,
11559 .operand_is_ref = operand_is_ref,
11560 .init_cond = init_cond,
11561 } },
11562 operand_ty,
11563 };
11564 }11449 }
1156511450
11566 // We always use `simple` in the comptime case, because as far as the dispatching logic11451 case_block.instructions.clearRetainingCapacity();
11567 // is concerned, it really is dispatching a single prong. `resolveSwitchComptime` will11452 case_block.error_return_trace_index = child_block.error_return_trace_index;
11568 // be resposible for recursively resolving different prongs as needed.
11569 break :op .{
11570 .{ .simple = .{
11571 .by_val = val,
11572 .by_ref = ref,
11573 .cond = init_cond,
11574 } },
11575 operand_ty,
11576 };
11577 };
11578
11579 const union_originally = raw_operand_ty.zigTypeTag(zcu) == .@"union";
11580 const err_set = raw_operand_ty.zigTypeTag(zcu) == .error_set;
11581 const cond_ty = switch (raw_operand_ty.zigTypeTag(zcu)) {
11582 .@"union" => raw_operand_ty.unionTagType(zcu).?, // validated by `switchCond` above
11583 else => raw_operand_ty,
11584 };
11585
11586 // AstGen guarantees that the instruction immediately preceding
11587 // switch_block(_ref) is a dbg_stmt
11588 const cond_dbg_node_index: Zir.Inst.Index = @enumFromInt(@intFromEnum(inst) - 1);
1158911453
11590 var header_extra_index: usize = extra.end;11454 if (zcu.backendSupportsFeature(.is_named_enum_value) and
11455 catch_all_case != .none and block.wantSafety() and
11456 item_ty.zigTypeTag(zcu) == .@"enum" and
11457 (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally))
11458 {
11459 try sema.zirDbgStmt(&case_block, cond_dbg_node_index);
11460 const ok = try case_block.addUnOp(.is_named_enum_value, cond_ref);
11461 if (else_is_named_only) {} else {
11462 try sema.addSafetyCheck(&case_block, src, ok, .corrupt_switch);
11463 }
11464 }
1159111465
11592 const scalar_cases_len = extra.data.bits.scalar_cases_len;11466 if (else_is_named_only and !else_case.is_inline) {
11593 const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {11467 // If we have both an `else` and an `_` prong, all named values go
11594 const multi_cases_len = sema.code.extra[header_extra_index];11468 // into the `else` prong and all unnamed ones go into the `_` prong.
11595 header_extra_index += 1;11469 // We will manually enumerate all named values which haven't been
11596 break :blk multi_cases_len;11470 // encountered yet and create an extra prong for them, which will
11597 } else 0;11471 // evaulate to the `else` body.
1159811472
11599 const tag_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_has_tag_capture) blk: {11473 assert(operand_ty.isNonexhaustiveEnum(zcu));
11600 const tag_capture_inst: Zir.Inst.Index = @enumFromInt(sema.code.extra[header_extra_index]);
11601 header_extra_index += 1;
11602 // SwitchProngAnalysis wants inst_map to have space for the tag capture.
11603 // Note that the normal capture is referred to via the switch block
11604 // index, which there is already necessarily space for.
11605 try sema.inst_map.ensureSpaceForInstructions(gpa, &.{tag_capture_inst});
11606 break :blk tag_capture_inst;
11607 } else undefined;
1160811474
11609 var case_vals = try std.ArrayList(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len);11475 cases_len += 1;
11610 defer case_vals.deinit(gpa);
11611
11612 var single_absorbed_item: Zir.Inst.Ref = .none;
11613 var absorbed_items: []const Zir.Inst.Ref = &.{};
11614 var absorbed_ranges: []const Zir.Inst.Ref = &.{};
11615
11616 const special_prongs = extra.data.bits.special_prongs;
11617 const has_else = special_prongs.hasElse();
11618 const has_under = special_prongs.hasUnder();
11619 const special_else: SpecialProng = if (has_else) blk: {
11620 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[header_extra_index]);
11621 const extra_body_start = header_extra_index + 1;
11622 break :blk .{
11623 .body = sema.code.bodySlice(extra_body_start, info.body_len),
11624 .end = extra_body_start + info.body_len,
11625 .capture = info.capture,
11626 .is_inline = info.is_inline,
11627 .has_tag_capture = info.has_tag_capture,
11628 };
11629 } else .{
11630 .body = &.{},
11631 .end = header_extra_index,
11632 .capture = .none,
11633 .is_inline = false,
11634 .has_tag_capture = false,
11635 };
11636 const special_under: SpecialProng = if (has_under) blk: {
11637 var extra_index = special_else.end;
11638 var trailing_items_len: usize = 0;
11639 if (special_prongs.hasOneAdditionalItem()) {
11640 single_absorbed_item = @enumFromInt(sema.code.extra[extra_index]);
11641 extra_index += 1;
11642 absorbed_items = @ptrCast(&single_absorbed_item);
11643 } else if (special_prongs.hasManyAdditionalItems()) {
11644 const items_len = sema.code.extra[extra_index];
11645 extra_index += 1;
11646 const ranges_len = sema.code.extra[extra_index];
11647 extra_index += 1;
11648 absorbed_items = sema.code.refSlice(extra_index + 1, items_len);
11649 absorbed_ranges = sema.code.refSlice(extra_index + 1 + items_len, ranges_len * 2);
11650 trailing_items_len = items_len + ranges_len * 2;
11651 }
11652 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11653 extra_index += 1 + trailing_items_len;
11654 break :blk .{
11655 .body = sema.code.bodySlice(extra_index, info.body_len),
11656 .end = extra_index + info.body_len,
11657 .capture = info.capture,
11658 .is_inline = info.is_inline,
11659 .has_tag_capture = info.has_tag_capture,
11660 };
11661 } else .{
11662 .body = &.{},
11663 .end = special_else.end,
11664 .capture = .none,
11665 .is_inline = false,
11666 .has_tag_capture = false,
11667 };
11668 const special_end = special_under.end;
1166911476
11670 // Duplicate checking variables later also used for `inline else`.11477 const prong_hint: std.builtin.BranchHint = hint: {
11671 var seen_enum_fields: []?LazySrcLoc = &.{};11478 if (!else_case.is_inline) break :hint try sema.analyzeSwitchProng(
11672 var seen_errors = SwitchErrorSet.init(gpa);11479 &case_block,
11673 var range_set = RangeSet.init(gpa, zcu);11480 operand,
11674 var true_count: u8 = 0;11481 operand_ty,
11675 var false_count: u8 = 0;11482 raw_operand_ty,
11483 else_case.body,
11484 block.src(.{ .switch_capture = .{
11485 .switch_node_offset = src_node_offset,
11486 .case_idx = else_case.index,
11487 } }),
11488 else_case.capture,
11489 else_case.has_tag_capture,
11490 .none,
11491 .special,
11492 validated_switch.else_err_ty,
11493 switch_inst,
11494 zir_switch,
11495 );
11496 _ = try case_block.addNoOp(.unreach);
11497 break :hint .cold; // unreachable branches are cold
11498 };
11499 try branch_hints.append(gpa, prong_hint);
1167611500
11677 defer {11501 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
11678 range_set.deinit();11502 (validated_switch.seen_enum_fields.len - zir_switch.totalItemsLen()) +
11679 gpa.free(seen_enum_fields);11503 case_block.instructions.items.len);
11680 seen_errors.deinit();11504 const extra_case = cases_extra.addManyAsArrayAssumeCapacity(
11681 }11505 @typeInfo(Air.SwitchBr.Case).@"struct".fields.len,
11506 );
11507 var items_len: u32 = 0;
11508 for (validated_switch.seen_enum_fields, 0..) |seen_field, field_i| {
11509 if (seen_field != null) continue;
11510 const item_val = try pt.enumValueFieldIndex(item_ty, @intCast(field_i));
11511 const item_ref: Air.Inst.Ref = .fromValue(item_val);
11512 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
11513 items_len += 1;
11514 }
11515 assert(items_len > 0); // `else` must be reachable at this point
11516 extra_case.* = payloadToExtraItems(Air.SwitchBr.Case{
11517 .items_len = items_len,
11518 .ranges_len = 0,
11519 .body_len = @intCast(case_block.instructions.items.len),
11520 });
11521 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
1168211522
11683 var empty_enum = false;11523 // We fall through to the regular catch-all prong generation.
1168411524
11685 var else_error_ty: ?Type = null;11525 case_block.instructions.clearRetainingCapacity();
11526 case_block.error_return_trace_index = child_block.error_return_trace_index;
11527 }
1168611528
11687 // Validate usage of '_' prongs.11529 const analyze_catch_all_body = analyze_body: {
11688 if (has_under and !raw_operand_ty.isNonexhaustiveEnum(zcu)) {11530 switch (catch_all_case) {
11689 const msg = msg: {11531 .none => break :analyze_body false, // we still may want a safety check!
11690 const msg = try sema.errMsg(11532 .under => break :analyze_body true, // can't be a union anyway
11691 src,11533 .@"else" => if (else_case.is_inline) break :analyze_body false,
11692 "'_' prong only allowed when switching on non-exhaustive enums",11534 }
11693 .{},11535 if (union_originally) {
11694 );11536 const union_obj = zcu.typeToUnion(operand_ty).?;
11695 errdefer msg.destroy(gpa);11537 for (validated_switch.seen_enum_fields, 0..) |seen_field, field_i| {
11696 try sema.errNote(11538 if (seen_field != null) continue;
11697 under_prong_src,11539 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_i]);
11698 msg,11540 if (!field_ty.isNoReturn(zcu)) break :analyze_body true;
11699 "'_' prong here",11541 }
11700 .{},11542 break :analyze_body false;
11701 );11543 }
11702 try sema.errNote(11544 if (err_set) {
11703 src,11545 const else_err_ty = validated_switch.else_err_ty orelse {
11704 msg,11546 assert(else_case.is_simple_noreturn);
11705 "consider using 'else'",11547 break :analyze_body false;
11706 .{},11548 };
11707 );11549 if (else_err_ty.errorSetIsEmpty(zcu)) break :analyze_body false;
11708 break :msg msg;11550 }
11551 break :analyze_body true;
11709 };11552 };
11710 return sema.failWithOwnedErrorMsg(block, msg);
11711 }
1171211553
11713 // Validate for duplicate items, missing else prong, and invalid range.11554 const catch_all_hint = hint: {
11714 switch (cond_ty.zigTypeTag(zcu)) {11555 if (analyze_catch_all_body) {
11715 .@"union" => unreachable, // handled in `switchCond`11556 const index, const body, const capture, const has_tag_capture = switch (catch_all_case) {
11716 .@"enum" => {11557 .@"else" => .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture },
11717 seen_enum_fields = try gpa.alloc(?LazySrcLoc, cond_ty.enumFieldCount(zcu));11558 .under => .{ under_case.index, under_case.body, under_case.capture, under_case.has_tag_capture },
11718 empty_enum = seen_enum_fields.len == 0 and !cond_ty.isNonexhaustiveEnum(zcu);11559 .none => unreachable,
11719 @memset(seen_enum_fields, null);11560 };
11720 // `range_set` is used for non-exhaustive enum values that do not correspond to any tags.11561 break :hint try sema.analyzeSwitchProng(
1172111562 &case_block,
11722 for (absorbed_items, 0..) |item_ref, item_i| {11563 operand,
11723 _ = try sema.validateSwitchItemEnum(11564 operand_ty,
11724 block,11565 raw_operand_ty,
11725 seen_enum_fields,11566 body,
11726 &range_set,11567 block.src(.{ .switch_capture = .{
11727 item_ref,
11728 cond_ty,
11729 block.src(.{ .switch_case_item = .{
11730 .switch_node_offset = src_node_offset,11568 .switch_node_offset = src_node_offset,
11731 .case_idx = .special_under,11569 .case_idx = index,
11732 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
11733 } }),11570 } }),
11571 capture,
11572 has_tag_capture,
11573 .none,
11574 .special,
11575 validated_switch.else_err_ty,
11576 switch_inst,
11577 zir_switch,
11734 );11578 );
11735 }11579 }
11736 try sema.validateSwitchNoRange(block, @intCast(absorbed_ranges.len), cond_ty, src_node_offset);11580 // We still need a terminator in this block, but we have proven
1173711581 // that it is unreachable.
11738 var extra_index: usize = special_end;11582 if (case_block.wantSafety()) {
11739 {11583 try sema.zirDbgStmt(&case_block, cond_dbg_node_index);
11740 var scalar_i: u32 = 0;11584 try sema.safetyPanic(&case_block, src, .corrupt_switch);
11741 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {11585 } else {
11742 const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);11586 _ = try case_block.addNoOp(.unreach);
11743 extra_index += 1;
11744 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11745 extra_index += 1 + info.body_len;
11746
11747 case_vals.appendAssumeCapacity(try sema.validateSwitchItemEnum(
11748 block,
11749 seen_enum_fields,
11750 &range_set,
11751 item_ref,
11752 cond_ty,
11753 block.src(.{ .switch_case_item = .{
11754 .switch_node_offset = src_node_offset,
11755 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
11756 .item_idx = .{ .kind = .single, .index = 0 },
11757 } }),
11758 ));
11759 }
11760 }11587 }
11761 {11588 break :hint .cold; // Safety check / unreachable branches are cold.
11762 var multi_i: u32 = 0;11589 };
11763 while (multi_i < multi_cases_len) : (multi_i += 1) {11590 try branch_hints.append(gpa, catch_all_hint);
11764 const items_len = sema.code.extra[extra_index];11591 break :catch_all_extra @ptrCast(case_block.instructions.items);
11765 extra_index += 1;11592 };
11766 const ranges_len = sema.code.extra[extra_index];
11767 extra_index += 1;
11768 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11769 extra_index += 1;
11770 const items = sema.code.refSlice(extra_index, items_len);
11771 extra_index += items_len + info.body_len;
11772
11773 try case_vals.ensureUnusedCapacity(gpa, items.len);
11774 for (items, 0..) |item_ref, item_i| {
11775 case_vals.appendAssumeCapacity(try sema.validateSwitchItemEnum(
11776 block,
11777 seen_enum_fields,
11778 &range_set,
11779 item_ref,
11780 cond_ty,
11781 block.src(.{ .switch_case_item = .{
11782 .switch_node_offset = src_node_offset,
11783 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
11784 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
11785 } }),
11786 ));
11787 }
1178811593
11789 try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);11594 assert(branch_hints.count == cases_len + 1); // +1 for catch-all hint
11790 }11595
11596 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +
11597 branch_hints.bags.items.len +
11598 cases_extra.items.len +
11599 catch_all_extra.len);
11600 const payload_index = sema.addExtraAssumeCapacity(Air.SwitchBr{
11601 .cases_len = @intCast(cases_len),
11602 .else_body_len = @intCast(catch_all_extra.len),
11603 });
11604 sema.air_extra.appendSliceAssumeCapacity(branch_hints.bags.items);
11605 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
11606 sema.air_extra.appendSliceAssumeCapacity(catch_all_extra);
11607
11608 const air_tag: Air.Inst.Tag = if (operand == .loop and merges.extra_insts.items.len > 0)
11609 .loop_switch_br
11610 else
11611 .switch_br;
11612 const air_switch_ref = try child_block.addInst(.{
11613 .tag = air_tag,
11614 .data = .{ .pl_op = .{
11615 .operand = cond_ref,
11616 .payload = payload_index,
11617 } },
11618 });
11619 try sema.fixupSwitchContinues(
11620 block,
11621 src,
11622 air_switch_ref,
11623 operand,
11624 operand_is_ref,
11625 item_ty,
11626 .normal,
11627 zir_switch.any_maybe_runtime_capture,
11628 merges,
11629 );
11630}
11631
11632/// This is the counterpart to `zirSwitchContinue`; replaces placeholder `br` insts
11633/// with their respective finalized inst pointing back at `switch_ref`.
11634fn fixupSwitchContinues(
11635 sema: *Sema,
11636 block: *Block,
11637 switch_src: LazySrcLoc,
11638 switch_ref: Air.Inst.Ref,
11639 operand: SwitchOperand,
11640 operand_is_ref: bool,
11641 item_ty: Type,
11642 mode: enum { normal, opv },
11643 any_non_inline_capture: bool,
11644 merges: *const Block.Merges,
11645) CompileError!void {
11646 const pt = sema.pt;
11647 const zcu = pt.zcu;
11648 const gpa = sema.gpa;
11649
11650 const air_tag = sema.air_instructions.items(.tag)[@intFromEnum(switch_ref.toIndex().?)];
11651 switch (air_tag) {
11652 .loop_switch_br, .switch_br => assert(mode == .normal),
11653 .loop, .block => assert(mode == .opv),
11654 else => unreachable,
11655 }
11656 switch (air_tag) {
11657 .loop_switch_br, .loop => assert(merges.extra_insts.items.len > 0),
11658 .switch_br, .block => assert(merges.extra_insts.items.len == 0),
11659 else => unreachable,
11660 }
11661
11662 for (merges.extra_insts.items, merges.extra_src_locs.items) |placeholder_inst, dispatch_src| {
11663 var replacement_block = block.makeSubBlock();
11664 defer replacement_block.instructions.deinit(gpa);
11665
11666 assert(sema.air_instructions.items(.tag)[@intFromEnum(placeholder_inst)] == .br);
11667 const new_operand_maybe_ref = sema.air_instructions.items(.data)[@intFromEnum(placeholder_inst)].br.operand;
11668
11669 if (any_non_inline_capture and mode != .opv) {
11670 _ = try replacement_block.addBinOp(.store, operand.loop.operand_alloc, new_operand_maybe_ref);
11671 }
11672
11673 const new_operand_val = if (operand_is_ref)
11674 try sema.analyzeLoad(&replacement_block, dispatch_src, new_operand_maybe_ref, dispatch_src)
11675 else
11676 new_operand_maybe_ref;
11677
11678 const new_cond = try sema.coerce(&replacement_block, item_ty, new_operand_val, dispatch_src);
11679
11680 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
11681 item_ty.zigTypeTag(zcu) == .@"enum" and !item_ty.isNonexhaustiveEnum(zcu) and
11682 mode == .normal and !try sema.isComptimeKnown(new_cond))
11683 {
11684 const ok = try replacement_block.addUnOp(.is_named_enum_value, new_cond);
11685 try sema.addSafetyCheck(&replacement_block, switch_src, ok, .corrupt_switch);
11686 }
11687
11688 switch (mode) {
11689 .normal => {
11690 _ = try replacement_block.addInst(.{
11691 .tag = .switch_dispatch,
11692 .data = .{ .br = .{
11693 .block_inst = switch_ref.toIndex().?,
11694 .operand = new_cond,
11695 } },
11696 });
11697 },
11698 .opv => {
11699 _ = try replacement_block.addInst(.{
11700 .tag = .repeat,
11701 .data = .{ .repeat = .{
11702 .loop_inst = switch_ref.toIndex().?,
11703 } },
11704 });
11705 },
11706 }
11707
11708 if (replacement_block.instructions.items.len == 1) {
11709 // Optimization: we don't need a block!
11710 sema.air_instructions.set(
11711 @intFromEnum(placeholder_inst),
11712 sema.air_instructions.get(@intFromEnum(replacement_block.instructions.items[0])),
11713 );
11714 continue;
11715 }
11716
11717 // Replace placeholder with a block.
11718 // No `br` is needed as the block is a switch dispatch so necessarily `noreturn`.
11719 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len +
11720 replacement_block.instructions.items.len);
11721 sema.air_instructions.set(@intFromEnum(placeholder_inst), .{
11722 .tag = .block,
11723 .data = .{ .ty_pl = .{
11724 .ty = .noreturn_type,
11725 .payload = sema.addExtraAssumeCapacity(Air.Block{
11726 .body_len = @intCast(replacement_block.instructions.items.len),
11727 }),
11728 } },
11729 });
11730 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(replacement_block.instructions.items));
11731 }
11732}
11733
11734const ValidatedSwitchBlock = struct {
11735 seen_enum_fields: []const ?LazySrcLoc,
11736 seen_errors: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc),
11737 seen_sparse_values: std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc),
11738 seen_ranges: []const RangeSet.Range,
11739 true_src: ?LazySrcLoc,
11740 false_src: ?LazySrcLoc,
11741 void_src: ?LazySrcLoc,
11742
11743 case_vals: []const Air.Inst.Ref,
11744 else_case: Zir.UnwrappedSwitchBlock.Case.Else,
11745 under_case: Zir.UnwrappedSwitchBlock.Case.Under.Resolved,
11746 else_err_ty: ?Type,
11747
11748 fn iterateUnhandledItems(
11749 validated_switch: *const ValidatedSwitchBlock,
11750 /// May be `undefined` if `item_ty` isn't an `error_set`.
11751 error_names: InternPool.NullTerminatedString.Slice,
11752 /// May be `undefined` if `item_ty` isn't an `int`.
11753 min_int: Value,
11754 ) UnhandledIterator {
11755 return .{
11756 .next_idx = 0,
11757 .next_val = min_int,
11758 .error_names = error_names,
11759 .seen_enum_fields = validated_switch.seen_enum_fields,
11760 .seen_errors = &validated_switch.seen_errors,
11761 .seen_ranges = validated_switch.seen_ranges,
11762 .seen_true = validated_switch.true_src != null,
11763 .seen_false = validated_switch.false_src != null,
11764 .seen_void = validated_switch.void_src != null,
11765 };
11766 }
11767
11768 const UnhandledIterator = struct {
11769 next_idx: u32,
11770 next_val: ?Value,
11771 error_names: InternPool.NullTerminatedString.Slice,
11772 seen_enum_fields: []const ?LazySrcLoc,
11773 seen_errors: *const std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc),
11774 seen_ranges: []const RangeSet.Range,
11775 seen_true: bool,
11776 seen_false: bool,
11777 seen_void: bool,
11778
11779 fn next(it: *UnhandledIterator, sema: *Sema, item_ty: Type) CompileError!?Value {
11780 const pt = sema.pt;
11781 const zcu = pt.zcu;
11782 const ip = &zcu.intern_pool;
11783 switch (item_ty.zigTypeTag(zcu)) {
11784 .@"union" => unreachable,
11785 .@"enum" => {
11786 for (it.seen_enum_fields[it.next_idx..], it.next_idx..) |seen_field, field_i| {
11787 if (seen_field != null) continue;
11788 it.next_idx = @intCast(field_i + 1);
11789 return try pt.enumValueFieldIndex(item_ty, @intCast(field_i));
11790 }
11791 return null;
11792 },
11793 .error_set => {
11794 for (it.error_names.get(ip)[it.next_idx..], it.next_idx..) |err_name, name_i| {
11795 if (it.seen_errors.contains(err_name)) continue;
11796 it.next_idx = @intCast(name_i + 1);
11797 return .fromInterned(try pt.intern(.{ .err = .{
11798 .ty = item_ty.toIntern(),
11799 .name = err_name,
11800 } }));
11801 }
11802 return null;
11803 },
11804 .int => {
11805 var cur = it.next_val orelse return null;
11806 while (it.next_idx < it.seen_ranges.len and
11807 cur.eql(it.seen_ranges[it.next_idx].first, item_ty, zcu))
11808 {
11809 defer it.next_idx += 1;
11810 const incr = try arith.incrementDefinedInt(
11811 sema,
11812 item_ty,
11813 it.seen_ranges[it.next_idx].last,
11814 );
11815 if (incr.overflow) {
11816 it.next_val = null;
11817 return null;
11818 }
11819 cur = incr.val;
11820 }
11821 const incr = try arith.incrementDefinedInt(sema, item_ty, cur);
11822 it.next_val = if (incr.overflow) null else incr.val;
11823 return cur;
11824 },
11825 .bool => {
11826 if (!it.seen_true) {
11827 it.seen_true = true;
11828 return .true;
11829 }
11830 if (!it.seen_false) {
11831 it.seen_false = true;
11832 return .false;
11833 }
11834 return null;
11835 },
11836 .void => {
11837 if (!it.seen_void) {
11838 it.seen_void = true;
11839 return .void;
11840 }
11841 return null;
11842 },
11843 else => unreachable, // item type is not enumerable
11844 }
11845 }
11846 };
11847};
11848
11849/// Validates operand type and `else`/`_` prong usage, resolves all prong items
11850/// and checks them for duplicates/invalid ranges. Does not emit into `block`.
11851/// Reserves inst map space for all placeholders associated with `zir_switch`.
11852/// Contents of returned `ValidatedSwitchBlock` belong to `sema.arena`.
11853fn validateSwitchBlock(
11854 sema: *Sema,
11855 block: *Block,
11856 raw_operand: Air.Inst.Ref,
11857 operand_is_ref: bool,
11858 switch_inst: Zir.Inst.Index,
11859 zir_switch: *const Zir.UnwrappedSwitchBlock,
11860) CompileError!ValidatedSwitchBlock {
11861 const pt = sema.pt;
11862 const zcu = pt.zcu;
11863 const ip = &zcu.intern_pool;
11864 const gpa = sema.gpa;
11865 const arena = sema.arena;
11866
11867 const src_node_offset = zir_switch.switch_src_node_offset;
11868 const src = block.nodeOffset(src_node_offset);
11869 const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });
11870 const else_prong_src = block.src(.{ .node_offset_switch_else_prong = src_node_offset });
11871 const under_prong_src = block.src(.{ .node_offset_switch_under_prong = src_node_offset });
11872 var extra_index = zir_switch.end;
11873
11874 // We want to map values to our placeholders later on.
11875 if (zir_switch.payload_capture_placeholder.unwrap()) |payload_capture_inst| {
11876 assert(payload_capture_inst != switch_inst); // malformed zir
11877 try sema.inst_map.ensureSpaceForInstructions(gpa, &.{payload_capture_inst});
11878 }
11879 if (zir_switch.tag_capture_placeholder.unwrap()) |tag_capture_inst| {
11880 assert(tag_capture_inst != switch_inst); // malformed zir
11881 try sema.inst_map.ensureSpaceForInstructions(gpa, &.{tag_capture_inst});
11882 }
11883
11884 const operand_ty: Type, const item_ty: Type = check_operand: {
11885 const operand_ty = operand_ty: {
11886 const raw_operand_ty = sema.typeOf(raw_operand);
11887 if (operand_is_ref) {
11888 try sema.checkPtrType(block, operand_src, raw_operand_ty, false);
11889 break :operand_ty raw_operand_ty.childType(zcu);
11890 }
11891 break :operand_ty raw_operand_ty;
11892 };
11893
11894 const item_ty: Type = item_ty: {
11895 switch (operand_ty.zigTypeTag(zcu)) {
11896 .@"enum",
11897 .error_set,
11898 .int,
11899 .comptime_int,
11900 .type,
11901 .enum_literal,
11902 .@"fn",
11903 .bool,
11904 .void,
11905 => break :item_ty operand_ty,
11906
11907 .@"union" => {
11908 try operand_ty.resolveFields(pt);
11909 const enum_ty = operand_ty.unionTagType(zcu) orelse {
11910 return sema.failWithOwnedErrorMsg(block, msg: {
11911 const msg = try sema.errMsg(operand_src, "switch on union with no attached enum", .{});
11912 errdefer msg.destroy(sema.gpa);
11913 if (operand_ty.srcLocOrNull(zcu)) |union_src| {
11914 try sema.errNote(union_src, msg, "consider 'union(enum)' here", .{});
11915 }
11916 break :msg msg;
11917 });
11918 };
11919 break :item_ty enum_ty;
11920 },
11921
11922 .pointer => {
11923 if (!operand_ty.isSlice(zcu)) {
11924 break :item_ty operand_ty;
11925 }
11926 },
11927
11928 else => {},
11929 }
11930 return sema.fail(block, operand_src, "switch on type '{f}'", .{operand_ty.fmt(pt)});
11931 };
11932
11933 if (zir_switch.has_continue and !block.isComptime()) {
11934 if (try operand_ty.comptimeOnlySema(pt)) {
11935 // Even if the operand is comptime-known, this `switch` is runtime.
11936 return sema.failWithOwnedErrorMsg(block, msg: {
11937 const msg = try sema.errMsg(operand_src, "operand of switch loop has comptime-only type '{f}'", .{operand_ty.fmt(pt)});
11938 errdefer msg.destroy(gpa);
11939 try sema.errNote(operand_src, msg, "switch loops are evaluated at runtime outside of comptime scopes", .{});
11940 try sema.explainWhyTypeIsComptime(msg, operand_src, operand_ty);
11941 break :msg msg;
11942 });
11791 }11943 }
11944 try sema.validateRuntimeValue(block, operand_src, raw_operand);
11945 }
11946
11947 break :check_operand .{ operand_ty, item_ty };
11948 };
11949
11950 const has_else = zir_switch.else_case != null;
11951 const has_under = zir_switch.under_case != .none;
11952
11953 // Validate usage of '_' prongs.
11954 if (has_under and !operand_ty.isNonexhaustiveEnum(zcu)) {
11955 const msg = msg: {
11956 const msg = try sema.errMsg(
11957 src,
11958 "'_' prong only allowed when switching on non-exhaustive enums",
11959 .{},
11960 );
11961 errdefer msg.destroy(gpa);
11962 try sema.errNote(
11963 under_prong_src,
11964 msg,
11965 "'_' prong here",
11966 .{},
11967 );
11968 try sema.errNote(
11969 src,
11970 msg,
11971 "consider using 'else'",
11972 .{},
11973 );
11974 break :msg msg;
11975 };
11976 return sema.failWithOwnedErrorMsg(block, msg);
11977 }
11978
11979 var case_vals: std.ArrayList(Air.Inst.Ref) = .empty;
11980 try case_vals.ensureUnusedCapacity(arena, zir_switch.item_infos.len);
11981
11982 // Duplicate checking variables later also used for `inline else`.
11983 var seen_enum_fields: []?LazySrcLoc = &.{};
11984 var seen_errors: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc) = .empty;
11985 var seen_sparse_values: std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc) = .empty;
11986 var range_set: RangeSet = .empty;
11987 var true_src: ?LazySrcLoc = null;
11988 var false_src: ?LazySrcLoc = null;
11989 var void_src: ?LazySrcLoc = null;
11990
11991 var else_err_ty: ?Type = null;
11992
11993 const else_case = zir_switch.else_case orelse undefined;
11994 var under_case = zir_switch.under_case.resolve() orelse undefined;
11995
11996 switch (item_ty.zigTypeTag(zcu)) {
11997 .@"union" => unreachable,
11998 .@"enum" => {
11999 seen_enum_fields = try arena.alloc(?LazySrcLoc, item_ty.enumFieldCount(zcu));
12000 @memset(seen_enum_fields, null);
12001 // `range_set` is used for non-exhaustive enum values that do not
12002 // correspond to any tags. Since this is rare, we only allocate on
12003 // demand in `validateSwitchItem`.
12004 },
12005 .error_set => {
12006 try seen_errors.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
12007 },
12008 .int, .comptime_int => {
12009 try range_set.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
12010 },
12011 .enum_literal, .@"fn", .pointer, .type => {
12012 try seen_sparse_values.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
12013 },
12014 .bool, .void => {},
12015
12016 else => unreachable,
12017 }
12018
12019 // Validate for duplicate items and invalid ranges.
12020 var case_it = zir_switch.iterateCases();
12021 while (case_it.next()) |case| {
12022 const prong_info = case.prong_info;
12023 const is_under = case.isUnder();
12024 if (is_under) {
12025 assert(!prong_info.is_inline);
12026 under_case = .{
12027 .index = case.index,
12028 .body = sema.code.bodySlice(extra_index, prong_info.body_len),
12029 .capture = prong_info.capture,
12030 .has_tag_capture = prong_info.has_tag_capture,
12031 };
12032 }
12033 extra_index += prong_info.body_len;
12034 for (case.item_infos, 0..) |item_info, item_i| {
12035 const item_src = block.src(.{ .switch_case_item = .{
12036 .switch_node_offset = src_node_offset,
12037 .case_idx = case.index,
12038 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
12039 } });
12040 const item, extra_index = try sema.resolveSwitchItem(block, item_src, item_ty, item_info, extra_index, switch_inst, prong_info.is_comptime_unreach, prong_info.is_inline);
12041 try sema.validateSwitchItemOrRange(block, item_src, item.val, null, item_ty, seen_enum_fields, &seen_errors, &seen_sparse_values, &range_set, &true_src, &false_src, &void_src);
12042 if (!is_under) case_vals.appendAssumeCapacity(item.ref);
12043 }
12044 for (case.range_infos, 0..) |range_info, range_i| {
12045 const range_offset: LazySrcLoc.Offset.SwitchItem = .{
12046 .switch_node_offset = src_node_offset,
12047 .case_idx = case.index,
12048 .item_idx = .{ .kind = .range, .value = @intCast(range_i) },
12049 };
12050 const range_src = block.src(.{ .switch_case_item = range_offset });
12051 const first_src = block.src(.{ .switch_case_item_range_first = range_offset });
12052 const last_src = block.src(.{ .switch_case_item_range_last = range_offset });
12053 const first_item, extra_index = try sema.resolveSwitchItem(block, first_src, item_ty, range_info[0], extra_index, switch_inst, prong_info.is_comptime_unreach, prong_info.is_inline);
12054 const last_item, extra_index = try sema.resolveSwitchItem(block, last_src, item_ty, range_info[1], extra_index, switch_inst, prong_info.is_comptime_unreach, prong_info.is_inline);
12055 try sema.validateSwitchItemOrRange(block, range_src, first_item.val, last_item.val, item_ty, seen_enum_fields, &seen_errors, &seen_sparse_values, &range_set, &true_src, &false_src, &void_src);
12056 if (!is_under) case_vals.appendSliceAssumeCapacity(&.{ first_item.ref, last_item.ref });
12057 }
12058 }
12059
12060 switch (item_ty.zigTypeTag(zcu)) {
12061 .@"union" => unreachable,
12062 .int, .comptime_int => {},
12063 else => if (zir_switch.anyRanges()) {
12064 const range_src = block.src(.{ .node_offset_switch_range = src_node_offset });
12065 const msg = msg: {
12066 const msg = try sema.errMsg(
12067 operand_src,
12068 "ranges not allowed when switching on type '{f}'",
12069 .{operand_ty.fmt(sema.pt)},
12070 );
12071 errdefer msg.destroy(sema.gpa);
12072 try sema.errNote(
12073 range_src,
12074 msg,
12075 "range here",
12076 .{},
12077 );
12078 break :msg msg;
12079 };
12080 return sema.failWithOwnedErrorMsg(block, msg);
12081 },
12082 }
12083
12084 // Validate for missing special prongs.
12085 switch (item_ty.zigTypeTag(zcu)) {
12086 .@"union" => unreachable,
12087 .@"enum" => {
11792 const all_tags_handled = for (seen_enum_fields) |seen_src| {12088 const all_tags_handled = for (seen_enum_fields) |seen_src| {
11793 if (seen_src == null) break false;12089 if (seen_src == null) break false;
11794 } else true;12090 } else true;
1179512091
11796 if (has_else) {12092 if (has_else) {
11797 if (all_tags_handled) {12093 if (all_tags_handled) {
11798 if (cond_ty.isNonexhaustiveEnum(zcu)) {12094 if (item_ty.isNonexhaustiveEnum(zcu)) {
11799 if (has_under) return sema.fail(12095 if (has_under) return sema.fail(
11800 block,12096 block,
11801 else_prong_src,12097 else_prong_src,
...@@ -11820,9 +12116,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11820,9 +12116,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11820 for (seen_enum_fields, 0..) |seen_src, i| {12116 for (seen_enum_fields, 0..) |seen_src, i| {
11821 if (seen_src != null) continue;12117 if (seen_src != null) continue;
1182212118
11823 const field_name = cond_ty.enumFieldName(i, zcu);12119 const field_name = item_ty.enumFieldName(i, zcu);
11824 try sema.addFieldErrNote(12120 try sema.addFieldErrNote(
11825 cond_ty,12121 item_ty,
11826 i,12122 i,
11827 msg,12123 msg,
11828 "unhandled enumeration value: '{f}'",12124 "unhandled enumeration value: '{f}'",
...@@ -11830,15 +12126,17 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11830,15 +12126,17 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11830 );12126 );
11831 }12127 }
11832 try sema.errNote(12128 try sema.errNote(
11833 cond_ty.srcLoc(zcu),12129 item_ty.srcLoc(zcu),
11834 msg,12130 msg,
11835 "enum '{f}' declared here",12131 "enum '{f}' declared here",
11836 .{cond_ty.fmt(pt)},12132 .{item_ty.fmt(pt)},
11837 );12133 );
11838 break :msg msg;12134 break :msg msg;
11839 };12135 };
11840 return sema.failWithOwnedErrorMsg(block, msg);12136 return sema.failWithOwnedErrorMsg(block, msg);
11841 } else if (special_prongs == .none and cond_ty.isNonexhaustiveEnum(zcu) and !union_originally) {12137 } else if (!has_else and !has_under and
12138 item_ty.isNonexhaustiveEnum(zcu) and operand_ty.zigTypeTag(zcu) != .@"union")
12139 {
11842 return sema.fail(12140 return sema.fail(
11843 block,12141 block,
11844 src,12142 src,
...@@ -11847,101 +12145,83 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11847,101 +12145,83 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11847 );12145 );
11848 }12146 }
11849 },12147 },
11850 .error_set => else_error_ty = try validateErrSetSwitch(12148 .error_set => {
11851 sema,12149 else_err_ty = ty: switch (try sema.resolveInferredErrorSetTy(block, src, item_ty.toIntern())) {
11852 block,12150 .anyerror_type => {
11853 &seen_errors,12151 if (!has_else) {
11854 &case_vals,12152 return sema.fail(
11855 cond_ty,
11856 inst_data,
11857 scalar_cases_len,
11858 multi_cases_len,
11859 .{ .body = special_else.body, .end = special_else.end, .src = else_prong_src },
11860 has_else,
11861 ),
11862 .int, .comptime_int => {
11863 var extra_index: usize = special_end;
11864 {
11865 var scalar_i: u32 = 0;
11866 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
11867 const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
11868 extra_index += 1;
11869 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11870 extra_index += 1 + info.body_len;
11871
11872 case_vals.appendAssumeCapacity(try sema.validateSwitchItemInt(
11873 block,
11874 &range_set,
11875 item_ref,
11876 cond_ty,
11877 block.src(.{ .switch_case_item = .{
11878 .switch_node_offset = src_node_offset,
11879 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
11880 .item_idx = .{ .kind = .single, .index = 0 },
11881 } }),
11882 ));
11883 }
11884 }
11885 {
11886 var multi_i: u32 = 0;
11887 while (multi_i < multi_cases_len) : (multi_i += 1) {
11888 const items_len = sema.code.extra[extra_index];
11889 extra_index += 1;
11890 const ranges_len = sema.code.extra[extra_index];
11891 extra_index += 1;
11892 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11893 extra_index += 1;
11894 const items = sema.code.refSlice(extra_index, items_len);
11895 extra_index += items_len;
11896
11897 try case_vals.ensureUnusedCapacity(gpa, items.len);
11898 for (items, 0..) |item_ref, item_i| {
11899 case_vals.appendAssumeCapacity(try sema.validateSwitchItemInt(
11900 block,12153 block,
11901 &range_set,12154 src,
11902 item_ref,12155 "else prong required when switching on type 'anyerror'",
11903 cond_ty,12156 .{},
11904 block.src(.{ .switch_case_item = .{12157 );
11905 .switch_node_offset = src_node_offset,
11906 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
11907 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
11908 } }),
11909 ));
11910 }12158 }
1191112159 break :ty .anyerror;
11912 try case_vals.ensureUnusedCapacity(gpa, 2 * ranges_len);12160 },
11913 var range_i: u32 = 0;12161 else => |err_set_ty_index| {
11914 while (range_i < ranges_len) : (range_i += 1) {12162 const error_names = ip.indexToKey(err_set_ty_index).error_set_type.names;
11915 const item_first: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);12163 var maybe_msg: ?*Zcu.ErrorMsg = null;
11916 extra_index += 1;12164 errdefer if (maybe_msg) |msg| msg.destroy(sema.gpa);
11917 const item_last: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);12165
11918 extra_index += 1;12166 var seen_errors_from_set: u32 = 0;
1191912167 for (error_names.get(ip)) |error_name| {
11920 const vals = try sema.validateSwitchRange(12168 if (seen_errors.contains(error_name)) {
12169 seen_errors_from_set += 1;
12170 } else if (!has_else) {
12171 const msg = maybe_msg orelse blk: {
12172 maybe_msg = try sema.errMsg(
12173 src,
12174 "switch must handle all possibilities",
12175 .{},
12176 );
12177 break :blk maybe_msg.?;
12178 };
12179
12180 try sema.errNote(
12181 src,
12182 msg,
12183 "unhandled error value: 'error.{f}'",
12184 .{error_name.fmt(ip)},
12185 );
12186 }
12187 }
12188
12189 if (maybe_msg) |msg| {
12190 maybe_msg = null;
12191 try sema.addDeclaredHereNote(msg, operand_ty);
12192 return sema.failWithOwnedErrorMsg(block, msg);
12193 }
12194
12195 if (has_else and seen_errors_from_set == error_names.len) {
12196 // This prong is unreachable anyway so we don't need its
12197 // error set type, but we still allow it to exist.
12198 if (else_case.is_simple_noreturn) break :ty null;
12199 return sema.fail(
11921 block,12200 block,
11922 &range_set,12201 else_prong_src,
11923 item_first,12202 "unreachable else prong; all cases already handled",
11924 item_last,12203 .{},
11925 cond_ty,
11926 block.src(.{ .switch_case_item = .{
11927 .switch_node_offset = src_node_offset,
11928 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
11929 .item_idx = .{ .kind = .range, .index = @intCast(range_i) },
11930 } }),
11931 );12204 );
11932 case_vals.appendAssumeCapacity(vals[0]);
11933 case_vals.appendAssumeCapacity(vals[1]);
11934 }12205 }
1193512206
11936 extra_index += info.body_len;12207 var names: InferredErrorSet.NameMap = .{};
11937 }12208 try names.ensureUnusedCapacity(sema.arena, error_names.len);
11938 }12209 for (error_names.get(ip)) |error_name| {
1193912210 if (seen_errors.contains(error_name)) continue;
12211 names.putAssumeCapacityNoClobber(error_name, {});
12212 }
12213 // No need to keep the hash map metadata correct; here we
12214 // extract the (sorted) keys only.
12215 break :ty try pt.errorSetFromUnsortedNames(names.keys());
12216 },
12217 };
12218 },
12219 .int, .comptime_int => |type_tag| {
11940 check_range: {12220 check_range: {
11941 if (cond_ty.zigTypeTag(zcu) == .int) {12221 if (type_tag == .int) {
11942 const min_int = try cond_ty.minInt(pt, cond_ty);12222 const min_int = try item_ty.minInt(pt, item_ty);
11943 const max_int = try cond_ty.maxInt(pt, cond_ty);12223 const max_int = try item_ty.maxInt(pt, item_ty);
11944 if (try range_set.spans(min_int.toIntern(), max_int.toIntern())) {12224 if (try range_set.spans(arena, min_int, max_int, item_ty, zcu)) {
11945 if (has_else) {12225 if (has_else) {
11946 return sema.fail(12226 return sema.fail(
11947 block,12227 block,
...@@ -11953,7 +12233,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11953,7 +12233,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11953 break :check_range;12233 break :check_range;
11954 }12234 }
11955 }12235 }
11956 if (special_prongs == .none) {12236 if (!has_else) {
11957 return sema.fail(12237 return sema.fail(
11958 block,12238 block,
11959 src,12239 src,
...@@ -11963,61 +12243,24 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11963,61 +12243,24 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11963 }12243 }
11964 }12244 }
11965 },12245 },
11966 .bool => {12246 .enum_literal, .@"fn", .pointer, .type => {
11967 var extra_index: usize = special_end;12247 if (!has_else) {
11968 {12248 return sema.fail(
11969 var scalar_i: u32 = 0;12249 block,
11970 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {12250 src,
11971 const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);12251 "else prong required when switching on type '{f}'",
11972 extra_index += 1;12252 .{item_ty.fmt(pt)},
11973 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);12253 );
11974 extra_index += 1 + info.body_len;
11975
11976 case_vals.appendAssumeCapacity(try sema.validateSwitchItemBool(
11977 block,
11978 &true_count,
11979 &false_count,
11980 item_ref,
11981 block.src(.{ .switch_case_item = .{
11982 .switch_node_offset = src_node_offset,
11983 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
11984 .item_idx = .{ .kind = .single, .index = 0 },
11985 } }),
11986 ));
11987 }
11988 }
11989 {
11990 var multi_i: u32 = 0;
11991 while (multi_i < multi_cases_len) : (multi_i += 1) {
11992 const items_len = sema.code.extra[extra_index];
11993 extra_index += 1;
11994 const ranges_len = sema.code.extra[extra_index];
11995 extra_index += 1;
11996 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11997 extra_index += 1;
11998 const items = sema.code.refSlice(extra_index, items_len);
11999 extra_index += items_len + info.body_len;
12000
12001 try case_vals.ensureUnusedCapacity(gpa, items.len);
12002 for (items, 0..) |item_ref, item_i| {
12003 case_vals.appendAssumeCapacity(try sema.validateSwitchItemBool(
12004 block,
12005 &true_count,
12006 &false_count,
12007 item_ref,
12008 block.src(.{ .switch_case_item = .{
12009 .switch_node_offset = src_node_offset,
12010 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12011 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
12012 } }),
12013 ));
12014 }
12015
12016 try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
12017 }
12018 }12254 }
12255 },
12256 .bool, .void => |type_tag| {
12257 const all_values_handled = switch (type_tag) {
12258 .bool => true_src != null and false_src != null,
12259 .void => void_src != null,
12260 else => unreachable,
12261 };
12019 if (has_else) {12262 if (has_else) {
12020 if (true_count + false_count == 2) {12263 if (all_values_handled) {
12021 return sema.fail(12264 return sema.fail(
12022 block,12265 block,
12023 else_prong_src,12266 else_prong_src,
...@@ -12026,7 +12269,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12026,7 +12269,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12026 );12269 );
12027 }12270 }
12028 } else {12271 } else {
12029 if (true_count + false_count < 2) {12272 if (!all_values_handled) {
12030 return sema.fail(12273 return sema.fail(
12031 block,12274 block,
12032 src,12275 src,
...@@ -12036,1775 +12279,1073 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12036,1775 +12279,1073 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12036 }12279 }
12037 }12280 }
12038 },12281 },
12039 .enum_literal, .void, .@"fn", .pointer, .type => {12282 else => unreachable,
12040 if (!has_else) {
12041 return sema.fail(
12042 block,
12043 src,
12044 "else prong required when switching on type '{f}'",
12045 .{cond_ty.fmt(pt)},
12046 );
12047 }
12048
12049 var seen_values = ValueSrcMap{};
12050 defer seen_values.deinit(gpa);
12051
12052 var extra_index: usize = special_end;
12053 {
12054 var scalar_i: u32 = 0;
12055 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
12056 const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
12057 extra_index += 1;
12058 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
12059 extra_index += 1;
12060 extra_index += info.body_len;
12061
12062 case_vals.appendAssumeCapacity(try sema.validateSwitchItemSparse(
12063 block,
12064 &seen_values,
12065 item_ref,
12066 cond_ty,
12067 block.src(.{ .switch_case_item = .{
12068 .switch_node_offset = src_node_offset,
12069 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
12070 .item_idx = .{ .kind = .single, .index = 0 },
12071 } }),
12072 ));
12073 }
12074 }
12075 {
12076 var multi_i: u32 = 0;
12077 while (multi_i < multi_cases_len) : (multi_i += 1) {
12078 const items_len = sema.code.extra[extra_index];
12079 extra_index += 1;
12080 const ranges_len = sema.code.extra[extra_index];
12081 extra_index += 1;
12082 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
12083 extra_index += 1;
12084 const items = sema.code.refSlice(extra_index, items_len);
12085 extra_index += items_len + info.body_len;
12086
12087 try case_vals.ensureUnusedCapacity(gpa, items.len);
12088 for (items, 0..) |item_ref, item_i| {
12089 case_vals.appendAssumeCapacity(try sema.validateSwitchItemSparse(
12090 block,
12091 &seen_values,
12092 item_ref,
12093 cond_ty,
12094 block.src(.{ .switch_case_item = .{
12095 .switch_node_offset = src_node_offset,
12096 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12097 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
12098 } }),
12099 ));
12100 }
12101
12102 try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
12103 }
12104 }
12105 },
12106
12107 .error_union,
12108 .noreturn,
12109 .array,
12110 .@"struct",
12111 .undefined,
12112 .null,
12113 .optional,
12114 .@"opaque",
12115 .vector,
12116 .frame,
12117 .@"anyframe",
12118 .comptime_float,
12119 .float,
12120 => return sema.fail(block, operand_src, "invalid switch operand type '{f}'", .{
12121 raw_operand_ty.fmt(pt),
12122 }),
12123 }12283 }
1212412284
12125 var special_members_only: ?SpecialProng = null;12285 return .{
12126 var special_members_only_src: LazySrcLoc = undefined;12286 .seen_enum_fields = seen_enum_fields,
12127 const special_generic, const special_generic_src = if (has_under) b: {12287 .seen_errors = seen_errors,
12128 if (has_else) {12288 .seen_sparse_values = seen_sparse_values,
12129 special_members_only = special_else;12289 .seen_ranges = range_set.ranges.items,
12130 special_members_only_src = else_prong_src;12290 .true_src = true_src,
12131 }12291 .false_src = false_src,
12132 break :b .{ special_under, under_prong_src };12292 .void_src = void_src,
12133 } else .{ special_else, else_prong_src };
1213412293
12135 const spa: SwitchProngAnalysis = .{12294 .case_vals = case_vals.items,
12136 .sema = sema,12295 .else_case = else_case,
12137 .parent_block = block,12296 .under_case = under_case,
12138 .operand = operand,12297 .else_err_ty = else_err_ty,
12139 .else_error_ty = else_error_ty,
12140 .switch_block_inst = inst,
12141 .tag_capture_inst = tag_capture_inst,
12142 };12298 };
12299}
1214312300
12144 const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);12301fn resolveSwitchBlock(
12145 try sema.air_instructions.append(gpa, .{12302 sema: *Sema,
12146 .tag = .block,12303 block: *Block,
12147 .data = undefined,12304 child_block: *Block,
12148 });12305 operand: SwitchOperand,
12149 var label: Block.Label = .{12306 raw_operand_ty: Type,
12150 .zir_block = inst,12307 maybe_lazy_cond_val: Value,
12151 .merges = .{12308 catch_all_case: CatchAllSwitchCase,
12152 .src_locs = .{},12309 else_is_named_only: bool,
12153 .results = .{},12310 merges: *Block.Merges,
12154 .br_list = .{},12311 switch_inst: Zir.Inst.Index,
12155 .block_inst = block_inst,12312 zir_switch: *const Zir.UnwrappedSwitchBlock,
12156 },12313 validated_switch: *const ValidatedSwitchBlock,
12157 };12314) CompileError!Air.Inst.Ref {
12315 const pt = sema.pt;
12316 const zcu = pt.zcu;
1215812317
12159 var child_block: Block = .{12318 const switch_node_offset = zir_switch.switch_src_node_offset;
12160 .parent = block,12319
12161 .sema = sema,12320 const operand_ty = sema.typeOf(operand.simple.by_val);
12162 .namespace = block.namespace,12321 const item_ty = switch (operand_ty.zigTypeTag(zcu)) {
12163 .instructions = .{},12322 .@"union" => operand_ty.unionTagType(zcu).?,
12164 .label = &label,12323 else => operand_ty,
12165 .inlining = block.inlining,
12166 .comptime_reason = block.comptime_reason,
12167 .is_typeof = block.is_typeof,
12168 .c_import_buf = block.c_import_buf,
12169 .runtime_cond = block.runtime_cond,
12170 .runtime_loop = block.runtime_loop,
12171 .runtime_index = block.runtime_index,
12172 .want_safety = block.want_safety,
12173 .error_return_trace_index = block.error_return_trace_index,
12174 .src_base_inst = block.src_base_inst,
12175 .type_name_ctx = block.type_name_ctx,
12176 };12324 };
12177 const merges = &child_block.label.?.merges;12325 const union_originally = operand_ty.zigTypeTag(zcu) == .@"union";
12178 defer child_block.instructions.deinit(gpa);12326 const err_set = item_ty.zigTypeTag(zcu) == .error_set;
12179 defer merges.deinit(gpa);
1218012327
12181 if (scalar_cases_len + multi_cases_len == 0 and12328 const cond_ref = operand.simple.cond;
12182 special_members_only == null and12329 // We have to resolve lazy values to ensure that comparisons with switch
12183 !special_generic.is_inline)12330 // prong items don't produce false negatives.
12184 {12331 const cond_val = try sema.resolveLazyValue(maybe_lazy_cond_val);
12185 if (empty_enum) {12332
12186 return .void_value;12333 const case_vals = validated_switch.case_vals;
12187 }12334 var case_val_idx: usize = 0;
12188 if (special_prongs == .none) {12335 var extra_index = zir_switch.end;
12189 return sema.fail(block, src, "switch must handle all possibilities", .{});12336 var case_it = zir_switch.iterateCases();
12337 while (case_it.next()) |case| {
12338 if (case.isUnder()) { // we'll deal with this later
12339 extra_index += case.prong_info.body_len;
12340 for (case.item_infos) |item_info| {
12341 if (item_info.bodyLen()) |body_len| extra_index += body_len;
12342 }
12343 for (case.range_infos) |range_info| {
12344 if (range_info[0].bodyLen()) |body_len| extra_index += body_len;
12345 if (range_info[1].bodyLen()) |body_len| extra_index += body_len;
12346 }
12347 continue;
12190 }12348 }
12191 const init_cond = switch (operand) {12349
12192 .simple => |s| s.cond,12350 const prong_info = case.prong_info;
12193 .loop => |l| l.init_cond,12351 const prong_body = sema.code.bodySlice(extra_index, prong_info.body_len);
12194 };12352 extra_index += prong_body.len;
12195 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and12353 for (case.item_infos) |item_info| {
12196 raw_operand_ty.zigTypeTag(zcu) == .@"enum" and !raw_operand_ty.isNonexhaustiveEnum(zcu))12354 if (item_info.bodyLen()) |body_len| extra_index += body_len;
12197 {
12198 try sema.zirDbgStmt(block, cond_dbg_node_index);
12199 const ok = try block.addUnOp(.is_named_enum_value, init_cond);
12200 try sema.addSafetyCheck(block, src, ok, .corrupt_switch);
12201 }12355 }
12202 if (err_set and try sema.maybeErrorUnwrap(block, special_generic.body, init_cond, operand_src, false)) {12356 for (case.range_infos) |range_info| {
12203 return .unreachable_value;12357 if (range_info[0].bodyLen()) |body_len| extra_index += body_len;
12358 if (range_info[1].bodyLen()) |body_len| extra_index += body_len;
12204 }12359 }
12205 }
1220612360
12207 switch (operand) {12361 const item_refs = case_vals[case_val_idx..][0..case.item_infos.len];
12208 .loop => {}, // always runtime; evaluation in comptime scope uses `simple`12362 case_val_idx += item_refs.len;
12209 .simple => |s| {12363 const range_refs: []const [2]Air.Inst.Ref = @ptrCast(case_vals[case_val_idx..][0 .. 2 * case.range_infos.len]);
12210 if (try sema.resolveDefinedValue(&child_block, src, s.cond)) |cond_val| {12364 case_val_idx += 2 * range_refs.len;
12211 return resolveSwitchComptimeLoop(12365 for (item_refs) |item_ref| {
12212 sema,12366 const item_val = sema.resolveConstDefinedValue(child_block, .unneeded, item_ref, undefined) catch unreachable;
12213 spa,12367 if (cond_val.eql(item_val, item_ty, zcu)) {
12214 &child_block,12368 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, prong_body, cond_ref);
12215 if (operand_is_ref)12369 if (union_originally and operand_ty.unionFieldType(item_val, zcu).?.isNoReturn(zcu)) {
12216 sema.typeOf(s.by_ref)12370 // This prong should be unreachable!
12217 else12371 return .unreachable_value;
12218 raw_operand_ty,12372 }
12219 cond_ty,12373 return sema.resolveSwitchProng(
12220 cond_val,12374 block,
12221 src_node_offset,12375 child_block,
12222 special_members_only,12376 operand,
12223 special_generic,12377 raw_operand_ty,
12224 has_under,12378 prong_body,
12225 case_vals,12379 block.src(.{ .switch_capture = .{
12226 scalar_cases_len,12380 .switch_node_offset = switch_node_offset,
12227 multi_cases_len,12381 .case_idx = case.index,
12228 err_set,12382 } }),
12229 empty_enum,12383 prong_info.capture,
12230 operand_is_ref,12384 prong_info.has_tag_capture,
12385 if (prong_info.is_inline) cond_ref else .none,
12386 .{ .item_refs = item_refs },
12387 validated_switch.else_err_ty,
12388 merges,
12389 switch_inst,
12390 zir_switch,
12231 );12391 );
12232 }12392 }
1223312393 }
12234 if (scalar_cases_len + multi_cases_len == 0 and12394 for (range_refs) |range_ref| {
12235 special_members_only == null and12395 const first_val = sema.resolveConstDefinedValue(child_block, .unneeded, range_ref[0], undefined) catch unreachable;
12236 !special_generic.is_inline and12396 const last_val = sema.resolveConstDefinedValue(child_block, .unneeded, range_ref[1], undefined) catch unreachable;
12237 !extra.data.bits.has_continue)12397 if ((try sema.compareAll(cond_val, .gte, first_val, item_ty)) and
12398 (try sema.compareAll(cond_val, .lte, last_val, item_ty)))
12238 {12399 {
12239 return spa.resolveProngComptime(12400 return sema.resolveSwitchProng(
12240 &child_block,12401 block,
12241 .special,12402 child_block,
12242 special_generic.body,12403 operand,
12243 special_generic.capture,12404 raw_operand_ty,
12405 prong_body,
12244 block.src(.{ .switch_capture = .{12406 block.src(.{ .switch_capture = .{
12245 .switch_node_offset = src_node_offset,12407 .switch_node_offset = switch_node_offset,
12246 .case_idx = if (has_under) .special_under else .special_else,12408 .case_idx = case.index,
12247 } }),12409 } }),
12248 undefined, // case_vals may be undefined for special prongs12410 prong_info.capture,
12249 .none,12411 prong_info.has_tag_capture,
12250 false,12412 if (prong_info.is_inline) cond_ref else .none,
12413 .has_ranges,
12414 validated_switch.else_err_ty,
12251 merges,12415 merges,
12416 switch_inst,
12417 zir_switch,
12252 );12418 );
12253 }12419 }
12254 },
12255 }
12256
12257 if (child_block.isComptime()) {
12258 _ = try sema.resolveConstDefinedValue(&child_block, operand_src, operand.simple.cond, null);
12259 unreachable;
12260 }
12261
12262 var extra_case_vals: struct {
12263 items: std.ArrayList(Air.Inst.Ref),
12264 ranges: std.ArrayList([2]Air.Inst.Ref),
12265 } = .{ .items = .empty, .ranges = .empty };
12266 defer {
12267 extra_case_vals.items.deinit(gpa);
12268 extra_case_vals.ranges.deinit(gpa);
12269 }
12270
12271 // Runtime switch, if we have a special_members_only prong we need to unroll
12272 // it to a prong with explicit items.
12273 // Although this is potentially the same as `inline else` it does not count
12274 // towards the backward branch quota because it's an implementation detail.
12275 if (special_members_only != null) gen: {
12276 assert(cond_ty.isNonexhaustiveEnum(zcu));
12277
12278 var min_i: usize = math.maxInt(usize);
12279 var max_i: usize = 0;
12280 var seen_field_count: usize = 0;
12281 for (seen_enum_fields, 0..) |seen, enum_i| {
12282 if (seen != null) {
12283 seen_field_count += 1;
12284 } else {
12285 min_i = @min(min_i, enum_i);
12286 max_i = @max(max_i, enum_i);
12287 }
12288 }
12289 if (min_i == max_i) {
12290 seen_enum_fields[min_i] = special_members_only_src;
12291 const item_val = try pt.enumValueFieldIndex(cond_ty, @intCast(min_i));
12292 const item_ref = Air.internedToRef(item_val.toIntern());
12293 try extra_case_vals.items.append(gpa, item_ref);
12294 break :gen;
12295 }
12296 const missing_field_count = seen_enum_fields.len - seen_field_count;
12297
12298 extra_case_vals.items = try .initCapacity(gpa, missing_field_count / 2);
12299 extra_case_vals.ranges = try .initCapacity(gpa, missing_field_count / 4);
12300 const int_ty = cond_ty.intTagType(zcu);
12301
12302 var last_val = try pt.enumValueFieldIndex(cond_ty, @intCast(min_i));
12303 var first_ref = Air.internedToRef(last_val.toIntern());
12304 seen_enum_fields[min_i] = special_members_only_src;
12305 for (seen_enum_fields[(min_i + 1)..(max_i + 1)], (min_i + 1)..) |seen, enum_i| {
12306 if (seen != null) continue;
12307 seen_enum_fields[enum_i] = special_members_only_src;
12308
12309 const item_val = try pt.enumValueFieldIndex(cond_ty, @intCast(enum_i));
12310 const item_ref = Air.internedToRef(item_val.toIntern());
12311
12312 const is_next = is_next: {
12313 const prev_int = ip.indexToKey(last_val.toIntern()).enum_tag.int;
12314
12315 const result = try arith.incrementDefinedInt(sema, int_ty, .fromInterned(prev_int));
12316 if (result.overflow) break :is_next false;
12317
12318 const item_int = ip.indexToKey(item_val.toIntern()).enum_tag.int;
12319 break :is_next try sema.valuesEqual(.fromInterned(item_int), result.val, int_ty);
12320 };
12321
12322 if (is_next) {
12323 last_val = item_val;
12324 } else {
12325 const last_ref = Air.internedToRef(last_val.toIntern());
12326 if (first_ref == last_ref) {
12327 try extra_case_vals.items.append(gpa, first_ref);
12328 } else {
12329 try extra_case_vals.ranges.append(gpa, .{ first_ref, last_ref });
12330 }
12331 first_ref = item_ref;
12332 last_val = item_val;
12333 }
12334 }
12335 const last_ref = Air.internedToRef(last_val.toIntern());
12336 if (first_ref == last_ref) {
12337 try extra_case_vals.items.append(gpa, first_ref);
12338 } else {
12339 try extra_case_vals.ranges.append(gpa, .{ first_ref, last_ref });
12340 }12420 }
12341 }12421 }
1234212422
12343 const air_switch_ref = try sema.analyzeSwitchRuntimeBlock(12423 const else_case = validated_switch.else_case;
12344 spa,12424 const under_case = validated_switch.under_case;
12345 &child_block,
12346 src,
12347 switch (operand) {
12348 .simple => |s| s.cond,
12349 .loop => |l| l.init_cond,
12350 },
12351 cond_ty,
12352 operand_src,
12353 case_vals,
12354 special_generic,
12355 scalar_cases_len,
12356 multi_cases_len,
12357 union_originally,
12358 raw_operand_ty,
12359 err_set,
12360 src_node_offset,
12361 special_generic_src,
12362 has_under,
12363 seen_enum_fields,
12364 seen_errors,
12365 range_set,
12366 true_count,
12367 false_count,
12368 cond_dbg_node_index,
12369 false,
12370 special_members_only,
12371 special_members_only_src,
12372 extra_case_vals.items.items,
12373 extra_case_vals.ranges.items,
12374 );
12375
12376 for (merges.extra_insts.items, merges.extra_src_locs.items) |placeholder_inst, dispatch_src| {
12377 var replacement_block = block.makeSubBlock();
12378 defer replacement_block.instructions.deinit(gpa);
12379
12380 assert(sema.air_instructions.items(.tag)[@intFromEnum(placeholder_inst)] == .br);
12381 const new_operand_maybe_ref = sema.air_instructions.items(.data)[@intFromEnum(placeholder_inst)].br.operand;
12382
12383 if (extra.data.bits.any_non_inline_capture) {
12384 _ = try replacement_block.addBinOp(.store, operand.loop.operand_alloc, new_operand_maybe_ref);
12385 }
1238612425
12387 const new_operand_val = if (operand_is_ref)12426 // named-only prong
12388 try sema.analyzeLoad(&replacement_block, dispatch_src, new_operand_maybe_ref, dispatch_src)
12389 else
12390 new_operand_maybe_ref;
12391
12392 const new_cond = try sema.switchCond(&replacement_block, dispatch_src, new_operand_val);
12393
12394 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
12395 cond_ty.zigTypeTag(zcu) == .@"enum" and !cond_ty.isNonexhaustiveEnum(zcu) and
12396 !try sema.isComptimeKnown(new_cond))
12397 {
12398 const ok = try replacement_block.addUnOp(.is_named_enum_value, new_cond);
12399 try sema.addSafetyCheck(&replacement_block, src, ok, .corrupt_switch);
12400 }
12401
12402 _ = try replacement_block.addInst(.{
12403 .tag = .switch_dispatch,
12404 .data = .{ .br = .{
12405 .block_inst = air_switch_ref.toIndex().?,
12406 .operand = new_cond,
12407 } },
12408 });
1240912427
12410 if (replacement_block.instructions.items.len == 1) {12428 if (else_is_named_only and item_ty.enumTagFieldIndex(cond_val, zcu) != null) {
12411 // Optimization: we don't need a block!12429 assert(item_ty.isNonexhaustiveEnum(zcu));
12412 sema.air_instructions.set(12430 return sema.resolveSwitchProng(
12413 @intFromEnum(placeholder_inst),12431 block,
12414 sema.air_instructions.get(@intFromEnum(replacement_block.instructions.items[0])),12432 child_block,
12415 );12433 operand,
12416 continue;12434 raw_operand_ty,
12417 }12435 else_case.body,
1241812436 block.src(.{ .switch_capture = .{
12419 // Replace placeholder with a block.12437 .switch_node_offset = switch_node_offset,
12420 // No `br` is needed as the block is a switch dispatch so necessarily `noreturn`.12438 .case_idx = else_case.index,
12421 try sema.air_extra.ensureUnusedCapacity(12439 } }),
12422 gpa,12440 else_case.capture,
12423 @typeInfo(Air.Block).@"struct".fields.len + replacement_block.instructions.items.len,12441 else_case.has_tag_capture,
12442 if (else_case.is_inline) cond_ref else .none,
12443 .special,
12444 validated_switch.else_err_ty,
12445 merges,
12446 switch_inst,
12447 zir_switch,
12424 );12448 );
12425 sema.air_instructions.set(@intFromEnum(placeholder_inst), .{
12426 .tag = .block,
12427 .data = .{ .ty_pl = .{
12428 .ty = .noreturn_type,
12429 .payload = sema.addExtraAssumeCapacity(Air.Block{
12430 .body_len = @intCast(replacement_block.instructions.items.len),
12431 }),
12432 } },
12433 });
12434 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(replacement_block.instructions.items));
12435 }12449 }
1243612450
12437 return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false);12451 // catch-all prong
12438}
12439
12440const SpecialProng = struct {
12441 body: []const Zir.Inst.Index,
12442 end: usize,
12443 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
12444 is_inline: bool,
12445 has_tag_capture: bool,
12446};
12447
12448fn analyzeSwitchRuntimeBlock(
12449 sema: *Sema,
12450 spa: SwitchProngAnalysis,
12451 child_block: *Block,
12452 src: LazySrcLoc,
12453 operand: Air.Inst.Ref,
12454 operand_ty: Type,
12455 operand_src: LazySrcLoc,
12456 case_vals: std.ArrayList(Air.Inst.Ref),
12457 else_prong: SpecialProng,
12458 scalar_cases_len: usize,
12459 multi_cases_len: usize,
12460 union_originally: bool,
12461 maybe_union_ty: Type,
12462 err_set: bool,
12463 switch_node_offset: std.zig.Ast.Node.Offset,
12464 else_prong_src: LazySrcLoc,
12465 else_prong_is_underscore: bool,
12466 seen_enum_fields: []?LazySrcLoc,
12467 seen_errors: SwitchErrorSet,
12468 range_set: RangeSet,
12469 true_count: u8,
12470 false_count: u8,
12471 cond_dbg_node_index: Zir.Inst.Index,
12472 allow_err_code_unwrap: bool,
12473 extra_prong: ?SpecialProng,
12474 /// May be `undefined` if `extra_prong` is `null`
12475 extra_prong_src: LazySrcLoc,
12476 extra_prong_items: []const Air.Inst.Ref,
12477 extra_prong_ranges: []const [2]Air.Inst.Ref,
12478) CompileError!Air.Inst.Ref {
12479 const pt = sema.pt;
12480 const zcu = pt.zcu;
12481 const gpa = sema.gpa;
12482 const ip = &zcu.intern_pool;
12483
12484 const block = child_block.parent.?;
12485
12486 const estimated_cases_extra = (scalar_cases_len + multi_cases_len) *
12487 @typeInfo(Air.SwitchBr.Case).@"struct".fields.len + 2;
12488 var cases_extra = try std.ArrayList(u32).initCapacity(gpa, estimated_cases_extra);
12489 defer cases_extra.deinit(gpa);
12490
12491 var branch_hints = try std.ArrayList(std.builtin.BranchHint).initCapacity(gpa, scalar_cases_len);
12492 defer branch_hints.deinit(gpa);
12493
12494 var case_block = child_block.makeSubBlock();
12495 case_block.runtime_loop = null;
12496 case_block.runtime_cond = operand_src;
12497 case_block.runtime_index.increment();
12498 case_block.need_debug_scope = null; // this body is emitted regardless
12499 defer case_block.instructions.deinit(gpa);
12500
12501 var extra_index: usize = else_prong.end;
12502
12503 var scalar_i: usize = 0;
12504 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
12505 extra_index += 1;
12506 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
12507 extra_index += 1;
12508 const body = sema.code.bodySlice(extra_index, info.body_len);
12509 extra_index += info.body_len;
12510
12511 case_block.instructions.shrinkRetainingCapacity(0);
12512 case_block.error_return_trace_index = child_block.error_return_trace_index;
1251312452
12514 const item = case_vals.items[scalar_i];12453 const index, const body, const capture, const has_tag_capture, const is_inline = switch (catch_all_case) {
12515 // `item` is already guaranteed to be constant known.12454 .@"else" => .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline },
1251612455 .under => .{ under_case.index, under_case.body, under_case.capture, under_case.has_tag_capture, false },
12517 const analyze_body = if (union_originally) blk: {12456 .none => unreachable,
12518 const unresolved_item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;12457 };
12519 const item_val = sema.resolveLazyValue(unresolved_item_val) catch unreachable;12458 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_ref);
12520 const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;12459 if (union_originally) {
12521 break :blk field_ty.zigTypeTag(zcu) != .noreturn;12460 for (validated_switch.seen_enum_fields, 0..) |maybe_seen, field_i| {
12522 } else true;12461 if (maybe_seen != null) continue;
1252312462 if (!operand_ty.unionFieldTypeByIndex(field_i, zcu).isNoReturn(zcu)) break;
12524 const prong_hint: std.builtin.BranchHint = if (err_set and
12525 try sema.maybeErrorUnwrap(&case_block, body, operand, operand_src, allow_err_code_unwrap))
12526 h: {
12527 // nothing to do here. weight against error branch
12528 break :h .unlikely;
12529 } else if (analyze_body) h: {
12530 break :h try spa.analyzeProngRuntime(
12531 &case_block,
12532 .normal,
12533 body,
12534 info.capture,
12535 child_block.src(.{ .switch_capture = .{
12536 .switch_node_offset = switch_node_offset,
12537 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
12538 } }),
12539 &.{item},
12540 if (info.is_inline) item else .none,
12541 info.has_tag_capture,
12542 );
12543 } else h: {
12544 _ = try case_block.addNoOp(.unreach);
12545 break :h .none;
12546 };
12547
12548 try branch_hints.append(gpa, prong_hint);
12549 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12550 1 + // `item`, no ranges
12551 case_block.instructions.items.len);
12552 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12553 .items_len = 1,
12554 .ranges_len = 0,
12555 .body_len = @intCast(case_block.instructions.items.len),
12556 }));
12557 cases_extra.appendAssumeCapacity(@intFromEnum(item));
12558 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12559 }
12560
12561 var cases_len = scalar_cases_len;
12562 var case_val_idx: usize = scalar_cases_len;
12563 const multi_cases_len_with_extra_prong = multi_cases_len + @intFromBool(extra_prong != null);
12564 var multi_i: u32 = 0;
12565 while (multi_i < multi_cases_len_with_extra_prong) : (multi_i += 1) {
12566 const is_extra_prong = multi_i == multi_cases_len;
12567 var items: []const Air.Inst.Ref = undefined;
12568 var info: Zir.Inst.SwitchBlock.ProngInfo = undefined;
12569 var ranges: []const [2]Air.Inst.Ref = undefined;
12570 var body: []const Zir.Inst.Index = undefined;
12571 if (is_extra_prong) {
12572 const prong = extra_prong.?;
12573 items = extra_prong_items;
12574 ranges = extra_prong_ranges;
12575 body = prong.body;
12576 info = .{
12577 .body_len = undefined,
12578 .capture = prong.capture,
12579 .is_inline = prong.is_inline,
12580 .has_tag_capture = prong.has_tag_capture,
12581 };
12582 } else {12463 } else {
12583 @branchHint(.likely);12464 // This prong should be unreachable!
12584 const items_len = sema.code.extra[extra_index];12465 return .unreachable_value;
12585 extra_index += 1;
12586 const ranges_len = sema.code.extra[extra_index];
12587 extra_index += 1;
12588 info = @bitCast(sema.code.extra[extra_index]);
12589 extra_index += 1 + items_len + ranges_len * 2;
12590
12591 items = case_vals.items[case_val_idx..][0..items_len];
12592 case_val_idx += items_len;
12593 ranges = @ptrCast(case_vals.items[case_val_idx..][0 .. ranges_len * 2]);
12594 case_val_idx += ranges_len * 2;
12595
12596 body = sema.code.bodySlice(extra_index, info.body_len);
12597 extra_index += info.body_len;
12598 }
12599
12600 case_block.instructions.shrinkRetainingCapacity(0);
12601 case_block.error_return_trace_index = child_block.error_return_trace_index;
12602
12603 // Generate all possible cases as scalar prongs.
12604 if (info.is_inline) {
12605 var emit_bb = false;
12606
12607 for (ranges, 0..) |range_items, range_i| {
12608 var item = sema.resolveConstDefinedValue(block, .unneeded, range_items[0], undefined) catch unreachable;
12609 const item_last = sema.resolveConstDefinedValue(block, .unneeded, range_items[1], undefined) catch unreachable;
12610
12611 while (item.compareScalar(.lte, item_last, operand_ty, zcu)) : ({
12612 // Previous validation has resolved any possible lazy values.
12613 const int_val: Value, const int_ty: Type = switch (operand_ty.zigTypeTag(zcu)) {
12614 .int => .{ item, operand_ty },
12615 .@"enum" => b: {
12616 const int_val = Value.fromInterned(ip.indexToKey(item.toIntern()).enum_tag.int);
12617 break :b .{ int_val, int_val.typeOf(zcu) };
12618 },
12619 else => unreachable,
12620 };
12621 const result = try arith.incrementDefinedInt(sema, int_ty, int_val);
12622 assert(!result.overflow);
12623 item = switch (operand_ty.zigTypeTag(zcu)) {
12624 .int => result.val,
12625 .@"enum" => .fromInterned(try pt.intern(.{ .enum_tag = .{
12626 .ty = operand_ty.toIntern(),
12627 .int = result.val.toIntern(),
12628 } })),
12629 else => unreachable,
12630 };
12631 }) {
12632 cases_len += 1;
12633
12634 const item_ref = Air.internedToRef(item.toIntern());
12635
12636 case_block.instructions.shrinkRetainingCapacity(0);
12637 case_block.error_return_trace_index = child_block.error_return_trace_index;
12638
12639 if (emit_bb) {
12640 const bb_src = if (is_extra_prong) extra_prong_src else block.src(.{ .switch_case_item = .{
12641 .switch_node_offset = switch_node_offset,
12642 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12643 .item_idx = .{ .kind = .range, .index = @intCast(range_i) },
12644 } });
12645 try sema.emitBackwardBranch(block, bb_src);
12646 }
12647 emit_bb = true;
12648
12649 const prong_hint = try spa.analyzeProngRuntime(
12650 &case_block,
12651 .normal,
12652 body,
12653 info.capture,
12654 child_block.src(.{ .switch_capture = .{
12655 .switch_node_offset = switch_node_offset,
12656 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12657 } }),
12658 undefined, // case_vals may be undefined for ranges
12659 item_ref,
12660 info.has_tag_capture,
12661 );
12662 try branch_hints.append(gpa, prong_hint);
12663
12664 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12665 1 + // `item`, no ranges
12666 case_block.instructions.items.len);
12667 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12668 .items_len = 1,
12669 .ranges_len = 0,
12670 .body_len = @intCast(case_block.instructions.items.len),
12671 }));
12672 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
12673 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12674
12675 if (item.compareScalar(.eq, item_last, operand_ty, zcu)) break;
12676 }
12677 }
12678
12679 for (items, 0..) |item, item_i| {
12680 cases_len += 1;
12681
12682 case_block.instructions.shrinkRetainingCapacity(0);
12683 case_block.error_return_trace_index = child_block.error_return_trace_index;
12684
12685 const analyze_body = if (union_originally) blk: {
12686 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
12687 const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
12688 break :blk field_ty.zigTypeTag(zcu) != .noreturn;
12689 } else true;
12690
12691 if (emit_bb) {
12692 const bb_src = if (is_extra_prong) extra_prong_src else block.src(.{ .switch_case_item = .{
12693 .switch_node_offset = switch_node_offset,
12694 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12695 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
12696 } });
12697 try sema.emitBackwardBranch(block, bb_src);
12698 }
12699 emit_bb = true;
12700
12701 const prong_hint: std.builtin.BranchHint = if (analyze_body) h: {
12702 break :h try spa.analyzeProngRuntime(
12703 &case_block,
12704 .normal,
12705 body,
12706 info.capture,
12707 child_block.src(.{ .switch_capture = .{
12708 .switch_node_offset = switch_node_offset,
12709 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12710 } }),
12711 &.{item},
12712 item,
12713 info.has_tag_capture,
12714 );
12715 } else h: {
12716 _ = try case_block.addNoOp(.unreach);
12717 break :h .none;
12718 };
12719 try branch_hints.append(gpa, prong_hint);
12720
12721 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12722 1 + // `item`, no ranges
12723 case_block.instructions.items.len);
12724 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12725 .items_len = 1,
12726 .ranges_len = 0,
12727 .body_len = @intCast(case_block.instructions.items.len),
12728 }));
12729 cases_extra.appendAssumeCapacity(@intFromEnum(item));
12730 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12731 }
12732
12733 continue;
12734 }
12735
12736 cases_len += 1;
12737
12738 const analyze_body = if (union_originally)
12739 for (items) |item| {
12740 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
12741 const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
12742 if (field_ty.zigTypeTag(zcu) != .noreturn) break true;
12743 } else false
12744 else
12745 true;
12746
12747 const prong_hint: std.builtin.BranchHint = if (err_set and
12748 try sema.maybeErrorUnwrap(&case_block, body, operand, operand_src, allow_err_code_unwrap))
12749 h: {
12750 // nothing to do here. weight against error branch
12751 break :h .unlikely;
12752 } else if (analyze_body) h: {
12753 break :h try spa.analyzeProngRuntime(
12754 &case_block,
12755 .normal,
12756 body,
12757 info.capture,
12758 child_block.src(.{ .switch_capture = .{
12759 .switch_node_offset = switch_node_offset,
12760 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12761 } }),
12762 items,
12763 .none,
12764 false,
12765 );
12766 } else h: {
12767 _ = try case_block.addNoOp(.unreach);
12768 break :h .none;
12769 };
12770
12771 try branch_hints.append(gpa, prong_hint);
12772
12773 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12774 items.len + ranges.len * 2 +
12775 case_block.instructions.items.len);
12776 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12777 .items_len = @intCast(items.len),
12778 .ranges_len = @intCast(ranges.len),
12779 .body_len = @intCast(case_block.instructions.items.len),
12780 }));
12781
12782 for (items) |item| {
12783 cases_extra.appendAssumeCapacity(@intFromEnum(item));
12784 }
12785 for (ranges) |range| {
12786 cases_extra.appendSliceAssumeCapacity(&.{
12787 @intFromEnum(range[0]),
12788 @intFromEnum(range[1]),
12789 });
12790 }12466 }
12791
12792 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12793 }12467 }
12468 return sema.resolveSwitchProng(
12469 block,
12470 child_block,
12471 operand,
12472 raw_operand_ty,
12473 body,
12474 block.src(.{ .switch_capture = .{
12475 .switch_node_offset = switch_node_offset,
12476 .case_idx = index,
12477 } }),
12478 capture,
12479 has_tag_capture,
12480 if (is_inline) cond_ref else .none,
12481 .special,
12482 validated_switch.else_err_ty,
12483 merges,
12484 switch_inst,
12485 zir_switch,
12486 );
12487}
1279412488
12795 const else_body: []const Air.Inst.Index = if (else_prong.body.len != 0 or case_block.wantSafety()) else_body: {12489const SwitchOperand = union(enum) {
12796 var emit_bb = false;12490 /// This switch will be dispatched only once, with the given operand.
12797 // If this is true we must have a 'true' else prong and not an underscore because12491 simple: struct {
12798 // underscore prongs can never be inlined. We've already checked for this.12492 /// The raw switch operand value. Always defined.
12799 if (else_prong.is_inline) switch (operand_ty.zigTypeTag(zcu)) {12493 by_val: Air.Inst.Ref,
12800 .@"enum" => {12494 /// The switch operand *pointer*. Defined only if there is a prong
12801 if (operand_ty.isNonexhaustiveEnum(zcu) and !union_originally) {12495 /// with a by-ref capture.
12802 return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{12496 by_ref: Air.Inst.Ref,
12803 operand_ty.fmt(pt),12497 /// The switch condition value. For unions, `operand` is the union
12804 });12498 /// and `cond` is its enum tag value.
12805 }12499 cond: Air.Inst.Ref,
12806 for (seen_enum_fields, 0..) |f, i| {12500 },
12807 if (f != null) continue;12501 /// This switch may be dispatched multiple times with `continue` syntax.
12808 cases_len += 1;12502 /// As such, the operand is stored in an alloc if needed.
1280912503 loop: struct {
12810 const item_val = try pt.enumValueFieldIndex(operand_ty, @intCast(i));12504 /// The `alloc` containing the `switch` operand for the active dispatch.
12811 const item_ref = Air.internedToRef(item_val.toIntern());12505 /// Each prong must load from this `alloc` to get captures.
1281212506 /// If there are no captures, this may be undefined.
12813 case_block.instructions.shrinkRetainingCapacity(0);12507 operand_alloc: Air.Inst.Ref,
12814 case_block.error_return_trace_index = child_block.error_return_trace_index;12508 /// Whether `operand_alloc` contains a by-val operand or a by-ref
1281512509 /// operand.
12816 const analyze_body = if (union_originally) blk: {12510 operand_is_ref: bool,
12817 const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;12511 /// The switch condition value for the *initial* dispatch. For
12818 break :blk field_ty.zigTypeTag(zcu) != .noreturn;12512 /// unions, this is the enum tag value.
12819 } else true;12513 init_cond: Air.Inst.Ref,
1282012514 },
12821 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);12515};
12822 emit_bb = true;
12823
12824 const prong_hint: std.builtin.BranchHint = if (analyze_body) h: {
12825 break :h try spa.analyzeProngRuntime(
12826 &case_block,
12827 .special,
12828 else_prong.body,
12829 else_prong.capture,
12830 child_block.src(.{ .switch_capture = .{
12831 .switch_node_offset = switch_node_offset,
12832 .case_idx = .special_else,
12833 } }),
12834 &.{item_ref},
12835 item_ref,
12836 else_prong.has_tag_capture,
12837 );
12838 } else h: {
12839 _ = try case_block.addNoOp(.unreach);
12840 break :h .none;
12841 };
12842 try branch_hints.append(gpa, prong_hint);
12843
12844 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12845 1 + // `item`, no ranges
12846 case_block.instructions.items.len);
12847 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12848 .items_len = 1,
12849 .ranges_len = 0,
12850 .body_len = @intCast(case_block.instructions.items.len),
12851 }));
12852 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
12853 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12854 }
12855 },
12856 .error_set => {
12857 if (operand_ty.isAnyError(zcu)) {
12858 return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{
12859 operand_ty.fmt(pt),
12860 });
12861 }
12862 const error_names = operand_ty.errorSetNames(zcu);
12863 for (0..error_names.len) |name_index| {
12864 const error_name = error_names.get(ip)[name_index];
12865 if (seen_errors.contains(error_name)) continue;
12866 cases_len += 1;
12867
12868 const item_val = try pt.intern(.{ .err = .{
12869 .ty = operand_ty.toIntern(),
12870 .name = error_name,
12871 } });
12872 const item_ref = Air.internedToRef(item_val);
12873
12874 case_block.instructions.shrinkRetainingCapacity(0);
12875 case_block.error_return_trace_index = child_block.error_return_trace_index;
12876
12877 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
12878 emit_bb = true;
12879
12880 const prong_hint = try spa.analyzeProngRuntime(
12881 &case_block,
12882 .special,
12883 else_prong.body,
12884 else_prong.capture,
12885 child_block.src(.{ .switch_capture = .{
12886 .switch_node_offset = switch_node_offset,
12887 .case_idx = .special_else,
12888 } }),
12889 &.{item_ref},
12890 item_ref,
12891 else_prong.has_tag_capture,
12892 );
12893 try branch_hints.append(gpa, prong_hint);
12894
12895 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12896 1 + // `item`, no ranges
12897 case_block.instructions.items.len);
12898 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12899 .items_len = 1,
12900 .ranges_len = 0,
12901 .body_len = @intCast(case_block.instructions.items.len),
12902 }));
12903 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
12904 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12905 }
12906 },
12907 .int => {
12908 var it = try RangeSetUnhandledIterator.init(sema, operand_ty, range_set);
12909 while (try it.next()) |cur| {
12910 cases_len += 1;
12911
12912 const item_ref = Air.internedToRef(cur);
12913
12914 case_block.instructions.shrinkRetainingCapacity(0);
12915 case_block.error_return_trace_index = child_block.error_return_trace_index;
12916
12917 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
12918 emit_bb = true;
12919
12920 const prong_hint = try spa.analyzeProngRuntime(
12921 &case_block,
12922 .special,
12923 else_prong.body,
12924 else_prong.capture,
12925 child_block.src(.{ .switch_capture = .{
12926 .switch_node_offset = switch_node_offset,
12927 .case_idx = .special_else,
12928 } }),
12929 &.{item_ref},
12930 item_ref,
12931 else_prong.has_tag_capture,
12932 );
12933 try branch_hints.append(gpa, prong_hint);
12934
12935 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12936 1 + // `item`, no ranges
12937 case_block.instructions.items.len);
12938 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12939 .items_len = 1,
12940 .ranges_len = 0,
12941 .body_len = @intCast(case_block.instructions.items.len),
12942 }));
12943 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
12944 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12945 }
12946 },
12947 .bool => {
12948 if (true_count == 0) {
12949 cases_len += 1;
12950
12951 case_block.instructions.shrinkRetainingCapacity(0);
12952 case_block.error_return_trace_index = child_block.error_return_trace_index;
1295312516
12954 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);12517const CatchAllSwitchCase = enum { none, @"else", under };
12955 emit_bb = true;
1295612518
12957 const prong_hint = try spa.analyzeProngRuntime(12519const SwitchProngKind = union(enum) {
12958 &case_block,12520 item_refs: []const Air.Inst.Ref,
12959 .special,12521 has_ranges,
12960 else_prong.body,12522 special,
12961 else_prong.capture,12523};
12962 child_block.src(.{ .switch_capture = .{
12963 .switch_node_offset = switch_node_offset,
12964 .case_idx = .special_else,
12965 } }),
12966 &.{.bool_true},
12967 .bool_true,
12968 else_prong.has_tag_capture,
12969 );
12970 try branch_hints.append(gpa, prong_hint);
1297112524
12972 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +12525/// Resolve a switch prong which is determined at comptime to have no peers.
12973 1 + // `item`, no ranges12526/// Sets up captures as needed. Uses `analyzeBodyRuntimeBreak`.
12974 case_block.instructions.items.len);12527fn resolveSwitchProng(
12975 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{12528 sema: *Sema,
12976 .items_len = 1,12529 block: *Block,
12977 .ranges_len = 0,12530 child_block: *Block,
12978 .body_len = @intCast(case_block.instructions.items.len),12531 operand: SwitchOperand,
12979 }));12532 raw_operand_ty: Type,
12980 cases_extra.appendAssumeCapacity(@intFromEnum(Air.Inst.Ref.bool_true));12533 prong_body: []const Zir.Inst.Index,
12981 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));12534 /// Must use the `switch_capture` field in `offset`.
12982 }12535 capture_src: LazySrcLoc,
12983 if (false_count == 0) {12536 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
12984 cases_len += 1;12537 has_tag_capture: bool,
12538 inline_case_capture: Air.Inst.Ref,
12539 kind: SwitchProngKind,
12540 else_err_ty: ?Type,
12541 merges: *Block.Merges,
12542 switch_inst: Zir.Inst.Index,
12543 zir_switch: *const Zir.UnwrappedSwitchBlock,
12544) CompileError!Air.Inst.Ref {
12545 const src_node_offset = zir_switch.switch_src_node_offset;
12546 const src = block.nodeOffset(src_node_offset);
12547 const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });
1298512548
12986 case_block.instructions.shrinkRetainingCapacity(0);12549 // We can propagate `.cold` hints from this branch since it's comptime-known
12987 case_block.error_return_trace_index = child_block.error_return_trace_index;12550 // to be taken from the parent branch.
12551 const parent_hint = sema.branch_hint;
12552 defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
1298812553
12989 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);12554 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
12990 emit_bb = true;12555 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
12556 const payload_ref = try sema.analyzeSwitchPayloadCapture(
12557 child_block,
12558 operand,
12559 operand.simple.by_val,
12560 operand.simple.by_ref,
12561 sema.typeOf(operand.simple.by_val),
12562 operand_src,
12563 capture_src,
12564 capture == .by_ref,
12565 kind == .special,
12566 switch (kind) {
12567 .item_refs => |item_refs| item_refs,
12568 .has_ranges, .special => undefined,
12569 },
12570 inline_case_capture,
12571 else_err_ty,
12572 );
12573 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
12574 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
12575 break :inst payload_inst;
12576 } else undefined;
12577 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1299112578
12992 const prong_hint = try spa.analyzeProngRuntime(12579 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
12993 &case_block,12580 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
12994 .special,12581 const tag_ref = try sema.analyzeSwitchTagCapture(
12995 else_prong.body,12582 child_block,
12996 else_prong.capture,12583 operand.simple.by_val,
12997 child_block.src(.{ .switch_capture = .{12584 sema.typeOf(operand.simple.by_val),
12998 .switch_node_offset = switch_node_offset,12585 capture_src,
12999 .case_idx = .special_else,12586 inline_case_capture,
13000 } }),12587 );
13001 &.{.bool_false},12588 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
13002 .bool_false,12589 break :inst tag_inst;
13003 else_prong.has_tag_capture,12590 } else undefined;
13004 );12591 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
13005 try branch_hints.append(gpa, prong_hint);
1300612592
13007 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +12593 if (zir_switch.has_continue) sema.inst_map.putAssumeCapacity(switch_inst, .fromType(raw_operand_ty));
13008 1 + // `item`, no ranges12594 defer if (zir_switch.has_continue) assert(sema.inst_map.remove(switch_inst));
13009 case_block.instructions.items.len);
13010 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
13011 .items_len = 1,
13012 .ranges_len = 0,
13013 .body_len = @intCast(case_block.instructions.items.len),
13014 }));
13015 cases_extra.appendAssumeCapacity(@intFromEnum(Air.Inst.Ref.bool_false));
13016 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
13017 }
13018 },
13019 else => return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{
13020 operand_ty.fmt(pt),
13021 }),
13022 };
1302312595
13024 case_block.instructions.shrinkRetainingCapacity(0);12596 return sema.resolveBlockBody(block, src, child_block, prong_body, switch_inst, merges);
13025 case_block.error_return_trace_index = child_block.error_return_trace_index;12597}
1302612598
13027 if (zcu.backendSupportsFeature(.is_named_enum_value) and12599fn wantSwitchProngBodyAnalysis(
13028 else_prong.body.len != 0 and block.wantSafety() and12600 sema: *Sema,
13029 operand_ty.zigTypeTag(zcu) == .@"enum" and12601 block: *Block,
13030 (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally))12602 item_ref: Air.Inst.Ref,
13031 {12603 operand_ty: Type,
13032 try sema.zirDbgStmt(&case_block, cond_dbg_node_index);12604 union_originally: bool,
13033 const ok = try case_block.addUnOp(.is_named_enum_value, operand);12605 err_set: bool,
13034 try sema.addSafetyCheck(&case_block, src, ok, .corrupt_switch);12606 prong_is_comptime_unreach: bool,
13035 }12607) bool {
12608 const zcu = sema.pt.zcu;
12609 if (union_originally) {
12610 const unresolved_item_val = sema.resolveConstDefinedValue(block, .unneeded, item_ref, undefined) catch unreachable;
12611 const item_val = sema.resolveLazyValue(unresolved_item_val) catch unreachable;
12612 const field_ty = operand_ty.unionFieldType(item_val, zcu).?;
12613 if (field_ty.isNoReturn(zcu)) return false;
12614 }
12615 if (err_set and prong_is_comptime_unreach) {
12616 const unresolved_item_val = sema.resolveConstDefinedValue(block, .unneeded, item_ref, undefined) catch unreachable;
12617 const item_val = sema.resolveLazyValue(unresolved_item_val) catch unreachable;
12618 const err_name = item_val.getErrorName(zcu).unwrap().?;
12619 if (!Type.errorSetHasFieldIp(&zcu.intern_pool, operand_ty.toIntern(), err_name)) return false;
12620 }
12621 return true;
12622}
1303612623
13037 const else_src_idx: LazySrcLoc.Offset.SwitchCaseIndex = if (else_prong_is_underscore)12624/// Assumes that `operand_ty` has more than one possible value.
13038 .special_under12625/// Sets up captures as needed. Uses `analyzeBodyRuntimeBreak`.
13039 else12626fn analyzeSwitchProng(
13040 .special_else;12627 sema: *Sema,
12628 case_block: *Block,
12629 operand: SwitchOperand,
12630 operand_ty: Type,
12631 raw_operand_ty: Type,
12632 prong_body: []const Zir.Inst.Index,
12633 /// Must use the `switch_capture` field in `offset`.
12634 capture_src: LazySrcLoc,
12635 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
12636 has_tag_capture: bool,
12637 inline_case_capture: Air.Inst.Ref,
12638 kind: SwitchProngKind,
12639 else_err_ty: ?Type,
12640 switch_inst: Zir.Inst.Index,
12641 zir_switch: *const Zir.UnwrappedSwitchBlock,
12642) CompileError!std.builtin.BranchHint {
12643 const pt = sema.pt;
12644 const zcu = pt.zcu;
1304112645
13042 const analyze_body = if (union_originally and !else_prong.is_inline)12646 const operand_src = case_block.src(.{ .node_offset_switch_operand = zir_switch.switch_src_node_offset });
13043 for (seen_enum_fields, 0..) |seen_field, index| {12647
13044 if (seen_field != null) continue;12648 if (operand_ty.zigTypeTag(zcu) == .error_set) {
13045 const union_obj = zcu.typeToUnion(maybe_union_ty).?;12649 const cond_ref = switch (operand) {
13046 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[index]);12650 .simple => |s| s.cond,
13047 if (field_ty.zigTypeTag(zcu) != .noreturn) break true;12651 .loop => |l| l.init_cond,
13048 } else false
13049 else
13050 true;
13051 const else_hint: std.builtin.BranchHint = if (else_prong.body.len != 0 and err_set and
13052 try sema.maybeErrorUnwrap(&case_block, else_prong.body, operand, operand_src, allow_err_code_unwrap))
13053 h: {
13054 // nothing to do here. weight against error branch
13055 break :h .unlikely;
13056 } else if (else_prong.body.len != 0 and analyze_body and !else_prong.is_inline) h: {
13057 break :h try spa.analyzeProngRuntime(
13058 &case_block,
13059 .special,
13060 else_prong.body,
13061 else_prong.capture,
13062 child_block.src(.{ .switch_capture = .{
13063 .switch_node_offset = switch_node_offset,
13064 .case_idx = else_src_idx,
13065 } }),
13066 undefined, // case_vals may be undefined for special prongs
13067 .none,
13068 false,
13069 );
13070 } else h: {
13071 // We still need a terminator in this block, but we have proven
13072 // that it is unreachable.
13073 if (case_block.wantSafety()) {
13074 try sema.zirDbgStmt(&case_block, cond_dbg_node_index);
13075 try sema.safetyPanic(&case_block, src, .corrupt_switch);
13076 } else {
13077 _ = try case_block.addNoOp(.unreach);
13078 }
13079 // Safety check / unreachable branches are cold.
13080 break :h .cold;
13081 };12652 };
12653 if (try sema.maybeErrorUnwrap(case_block, prong_body, cond_ref, operand_src, true)) {
12654 // nothing to do here. weight against error branch
12655 return .unlikely;
12656 }
12657 }
1308212658
13083 try branch_hints.append(gpa, else_hint);12659 const operand_val, const operand_ptr = load_operand: {
13084 break :else_body case_block.instructions.items;12660 if (capture == .none and !has_tag_capture) {
13085 } else else_body: {12661 // No need to load the operand for this prong!
13086 try branch_hints.append(gpa, .none);12662 break :load_operand .{ undefined, undefined };
13087 break :else_body &.{};12663 }
12664 if (inline_case_capture != .none and
12665 !(capture != .none and operand_ty.zigTypeTag(zcu) == .@"union"))
12666 {
12667 // We only need to load the operand if there's a union payload capture
12668 // since it's always runtime-known; only the tag is comptime-known here.
12669 break :load_operand .{ undefined, undefined };
12670 }
12671 assert(zir_switch.any_maybe_runtime_capture); // should have caught everything else by now
12672 switch (operand) {
12673 .simple => |s| break :load_operand .{ s.by_val, s.by_ref },
12674 .loop => |l| {
12675 const loaded = try sema.analyzeLoad(case_block, operand_src, l.operand_alloc, operand_src);
12676 if (l.operand_is_ref) {
12677 const by_val = try sema.analyzeLoad(case_block, operand_src, loaded, operand_src);
12678 break :load_operand .{ by_val, loaded };
12679 } else {
12680 break :load_operand .{ loaded, undefined };
12681 }
12682 },
12683 }
13088 };12684 };
1308912685
13090 assert(branch_hints.items.len == cases_len + 1);12686 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
1309112687 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
13092 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +12688 const payload_ref = try sema.analyzeSwitchPayloadCapture(
13093 cases_extra.items.len + else_body.len +12689 case_block,
13094 (std.math.divCeil(usize, branch_hints.items.len, 10) catch unreachable)); // branch hints12690 operand,
1309512691 operand_val,
13096 const payload_index = sema.addExtraAssumeCapacity(Air.SwitchBr{12692 operand_ptr,
13097 .cases_len = @intCast(cases_len),12693 operand_ty,
13098 .else_body_len = @intCast(else_body.len),12694 operand_src,
13099 });12695 capture_src,
12696 capture == .by_ref,
12697 kind == .special,
12698 switch (kind) {
12699 .item_refs => |item_refs| item_refs,
12700 .has_ranges, .special => undefined,
12701 },
12702 inline_case_capture,
12703 else_err_ty,
12704 );
12705 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
12706 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
12707 break :inst payload_inst;
12708 } else undefined;
12709 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1310012710
13101 {12711 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
13102 // Add branch hints.12712 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
13103 var cur_bag: u32 = 0;12713 const tag_ref = try sema.analyzeSwitchTagCapture(
13104 for (branch_hints.items, 0..) |hint, idx| {12714 case_block,
13105 const idx_in_bag = idx % 10;12715 operand_val,
13106 cur_bag |= @as(u32, @intFromEnum(hint)) << @intCast(idx_in_bag * 3);12716 operand_ty,
13107 if (idx_in_bag == 9) {12717 capture_src,
13108 sema.air_extra.appendAssumeCapacity(cur_bag);12718 inline_case_capture,
13109 cur_bag = 0;12719 );
13110 }12720 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
13111 }12721 break :inst tag_inst;
13112 if (branch_hints.items.len % 10 != 0) {12722 } else undefined;
13113 sema.air_extra.appendAssumeCapacity(cur_bag);12723 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
13114 }
13115 }
13116 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(cases_extra.items));
13117 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_body));
1311812724
13119 const has_any_continues = spa.operand == .loop and child_block.label.?.merges.extra_insts.items.len > 0;12725 if (zir_switch.has_continue) sema.inst_map.putAssumeCapacity(switch_inst, .fromType(raw_operand_ty));
12726 defer if (zir_switch.has_continue) assert(sema.inst_map.remove(switch_inst));
1312012727
13121 return try child_block.addInst(.{12728 return sema.analyzeBodyRuntimeBreak(case_block, prong_body);
13122 .tag = if (has_any_continues) .loop_switch_br else .switch_br,
13123 .data = .{ .pl_op = .{
13124 .operand = operand,
13125 .payload = payload_index,
13126 } },
13127 });
13128}12729}
1312912730
13130fn resolveSwitchComptimeLoop(12731fn analyzeSwitchTagCapture(
13131 sema: *Sema,12732 sema: *Sema,
13132 init_spa: SwitchProngAnalysis,12733 case_block: *Block,
13133 child_block: *Block,12734 /// May be `undefined` if `inline_case_capture` is not `.none`.
13134 maybe_ptr_operand_ty: Type,12735 operand_val: Air.Inst.Ref,
13135 cond_ty: Type,12736 operand_ty: Type,
13136 init_cond_val: Value,12737 capture_src: LazySrcLoc,
13137 switch_node_offset: std.zig.Ast.Node.Offset,12738 inline_case_capture: Air.Inst.Ref,
13138 special_members_only: ?SpecialProng,
13139 special_generic: SpecialProng,
13140 special_generic_is_under: bool,
13141 case_vals: std.ArrayList(Air.Inst.Ref),
13142 scalar_cases_len: u32,
13143 multi_cases_len: u32,
13144 err_set: bool,
13145 empty_enum: bool,
13146 operand_is_ref: bool,
13147) CompileError!Air.Inst.Ref {12739) CompileError!Air.Inst.Ref {
13148 var spa = init_spa;12740 const pt = sema.pt;
13149 var cond_val = init_cond_val;12741 const zcu = pt.zcu;
13150
13151 while (true) {
13152 if (resolveSwitchComptime(
13153 sema,
13154 spa,
13155 child_block,
13156 spa.operand.simple.cond,
13157 cond_val,
13158 cond_ty,
13159 switch_node_offset,
13160 special_members_only,
13161 special_generic,
13162 special_generic_is_under,
13163 case_vals,
13164 scalar_cases_len,
13165 multi_cases_len,
13166 err_set,
13167 empty_enum,
13168 )) |result| {
13169 return result;
13170 } else |err| switch (err) {
13171 error.ComptimeBreak => {
13172 const break_inst = sema.code.instructions.get(@intFromEnum(sema.comptime_break_inst));
13173 if (break_inst.tag != .switch_continue) return error.ComptimeBreak;
13174 const extra = sema.code.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data;
13175 if (extra.block_inst != spa.switch_block_inst) return error.ComptimeBreak;
13176 // This is a `switch_continue` targeting this block. Change the operand and start over.
13177 const src = child_block.nodeOffset(extra.operand_src_node.unwrap().?);
13178 const new_operand_uncoerced = try sema.resolveInst(break_inst.data.@"break".operand);
13179 const new_operand = try sema.coerce(child_block, maybe_ptr_operand_ty, new_operand_uncoerced, src);
13180
13181 try sema.emitBackwardBranch(child_block, src);
13182
13183 const val, const ref = if (operand_is_ref)
13184 .{ try sema.analyzeLoad(child_block, src, new_operand, src), new_operand }
13185 else
13186 .{ new_operand, undefined };
1318712742
13188 const cond_ref = try sema.switchCond(child_block, src, val);12743 const tag_capture_src: LazySrcLoc = .{
12744 .base_node_inst = capture_src.base_node_inst,
12745 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
12746 };
1318912747
13190 cond_val = try sema.resolveConstDefinedValue(child_block, src, cond_ref, null);12748 if (operand_ty.zigTypeTag(zcu) != .@"union") {
13191 spa.operand = .{ .simple = .{12749 return sema.fail(case_block, tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
13192 .by_val = val,12750 operand_ty.fmt(pt),
13193 .by_ref = ref,12751 });
13194 .cond = cond_ref,12752 }
13195 } };12753 if (inline_case_capture != .none) {
13196 },12754 return inline_case_capture; // this already is the tag, it's what we're switching on!
13197 else => |e| return e,
13198 }
13199 }12755 }
12756 const tag_ty = operand_ty.unionTagType(zcu).?;
12757 return sema.unionToTag(case_block, tag_ty, operand_val, tag_capture_src);
13200}12758}
1320112759
13202fn resolveSwitchComptime(12760fn analyzeSwitchPayloadCapture(
13203 sema: *Sema,12761 sema: *Sema,
13204 spa: SwitchProngAnalysis,12762 case_block: *Block,
13205 child_block: *Block,12763 operand: SwitchOperand,
13206 cond_operand: Air.Inst.Ref,12764 /// May be `undefined` if this is an inline capture and operand is not a union.
13207 operand_val: Value,12765 operand_val: Air.Inst.Ref,
12766 /// May be `undefined` if `capture_by_ref` is `false` or if `operand_val` is also `undefined`.
12767 operand_ptr: Air.Inst.Ref,
13208 operand_ty: Type,12768 operand_ty: Type,
13209 switch_node_offset: std.zig.Ast.Node.Offset,12769 operand_src: LazySrcLoc,
13210 special_members_only: ?SpecialProng,12770 capture_src: LazySrcLoc,
13211 special_generic: SpecialProng,12771 capture_by_ref: bool,
13212 special_generic_is_under: bool,12772 is_special_prong: bool,
13213 case_vals: std.ArrayList(Air.Inst.Ref),12773 /// May be `undefined` if `is_special_prong` is `true`.
13214 scalar_cases_len: u32,12774 case_vals: []const Air.Inst.Ref,
13215 multi_cases_len: u32,12775 /// If this is not `.none`, this is an inline capture.
13216 err_set: bool,12776 inline_case_capture: Air.Inst.Ref,
13217 empty_enum: bool,12777 else_err_ty: ?Type,
13218) CompileError!Air.Inst.Ref {12778) CompileError!Air.Inst.Ref {
13219 const zcu = sema.pt.zcu;12779 const pt = sema.pt;
13220 const merges = &child_block.label.?.merges;12780 const zcu = pt.zcu;
13221 const resolved_operand_val = try sema.resolveLazyValue(operand_val);12781 const ip = &zcu.intern_pool;
1322212782
13223 var extra_index: usize = special_generic.end;12783 const switch_node_offset = operand_src.offset.node_offset_switch_operand;
13224 {12784
13225 var scalar_i: usize = 0;12785 if (inline_case_capture != .none) {
13226 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {12786 const item_val = sema.resolveConstDefinedValue(case_block, .unneeded, inline_case_capture, undefined) catch unreachable;
13227 extra_index += 1;12787 if (operand_ty.zigTypeTag(zcu) == .@"union") {
13228 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);12788 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);
13229 extra_index += 1;12789 const union_obj = zcu.typeToUnion(operand_ty).?;
13230 const body = sema.code.bodySlice(extra_index, info.body_len);12790 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
13231 extra_index += info.body_len;12791 if (capture_by_ref) {
1323212792 const operand_ptr_info = sema.typeOf(operand_ptr).ptrInfo(zcu);
13233 const item = case_vals.items[scalar_i];12793 const ptr_field_ty = try pt.ptrTypeSema(.{
13234 const item_val = sema.resolveConstDefinedValue(child_block, LazySrcLoc.unneeded, item, undefined) catch unreachable;12794 .child = field_ty.toIntern(),
13235 if (operand_val.eql(item_val, operand_ty, sema.pt.zcu)) {12795 .flags = .{
13236 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_operand);12796 .is_const = operand_ptr_info.flags.is_const,
13237 return spa.resolveProngComptime(12797 .is_volatile = operand_ptr_info.flags.is_volatile,
13238 child_block,12798 .address_space = operand_ptr_info.flags.address_space,
13239 .normal,12799 },
13240 body,12800 });
13241 info.capture,12801 return case_block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);
13242 child_block.src(.{ .switch_capture = .{12802 } else {
13243 .switch_node_offset = switch_node_offset,12803 if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |union_val| {
13244 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },12804 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
13245 } }),12805 return .fromIntern(tag_and_val.val);
13246 &.{item},12806 }
13247 if (info.is_inline) cond_operand else .none,12807 return case_block.addStructFieldVal(operand_val, field_index, field_ty);
13248 info.has_tag_capture,
13249 merges,
13250 );
13251 }12808 }
12809 } else if (capture_by_ref) {
12810 return sema.uavRef(item_val.toIntern());
12811 } else {
12812 return inline_case_capture;
13252 }12813 }
13253 }12814 }
13254 {
13255 var multi_i: usize = 0;
13256 var case_val_idx: usize = scalar_cases_len;
13257 while (multi_i < multi_cases_len) : (multi_i += 1) {
13258 const items_len = sema.code.extra[extra_index];
13259 extra_index += 1;
13260 const ranges_len = sema.code.extra[extra_index];
13261 extra_index += 1;
13262 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
13263 extra_index += 1 + items_len;
13264 const body = sema.code.bodySlice(extra_index + 2 * ranges_len, info.body_len);
13265
13266 const items = case_vals.items[case_val_idx..][0..items_len];
13267 case_val_idx += items_len;
13268
13269 for (items) |item| {
13270 // Validation above ensured these will succeed.
13271 const item_val = sema.resolveConstDefinedValue(child_block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
13272 if (operand_val.eql(item_val, operand_ty, sema.pt.zcu)) {
13273 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_operand);
13274 return spa.resolveProngComptime(
13275 child_block,
13276 .normal,
13277 body,
13278 info.capture,
13279 child_block.src(.{ .switch_capture = .{
13280 .switch_node_offset = switch_node_offset,
13281 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
13282 } }),
13283 items,
13284 if (info.is_inline) cond_operand else .none,
13285 info.has_tag_capture,
13286 merges,
13287 );
13288 }
13289 }
1329012815
13291 var range_i: usize = 0;12816 const operand_ptr_ty = if (capture_by_ref) sema.typeOf(operand_ptr) else undefined;
13292 while (range_i < ranges_len) : (range_i += 1) {
13293 const range_items = case_vals.items[case_val_idx..][0..2];
13294 extra_index += 2;
13295 case_val_idx += 2;
1329612817
13297 // Validation above ensured these will succeed.12818 if (is_special_prong) {
13298 const first_val = sema.resolveConstDefinedValue(child_block, LazySrcLoc.unneeded, range_items[0], undefined) catch unreachable;12819 if (capture_by_ref) return operand_ptr;
13299 const last_val = sema.resolveConstDefinedValue(child_block, LazySrcLoc.unneeded, range_items[1], undefined) catch unreachable;12820 return switch (operand_ty.zigTypeTag(zcu)) {
13300 if ((try sema.compareAll(resolved_operand_val, .gte, first_val, operand_ty)) and12821 .error_set => e: {
13301 (try sema.compareAll(resolved_operand_val, .lte, last_val, operand_ty)))12822 if (else_err_ty) |err_ty| {
13302 {12823 break :e sema.bitCast(case_block, err_ty, operand_val, operand_src, null);
13303 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_operand);12824 } else {
13304 return spa.resolveProngComptime(12825 try sema.analyzeUnreachable(case_block, operand_src, false);
13305 child_block,12826 break :e .unreachable_value;
13306 .normal,
13307 body,
13308 info.capture,
13309 child_block.src(.{ .switch_capture = .{
13310 .switch_node_offset = switch_node_offset,
13311 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
13312 } }),
13313 undefined, // case_vals may be undefined for ranges
13314 if (info.is_inline) cond_operand else .none,
13315 info.has_tag_capture,
13316 merges,
13317 );
13318 }12827 }
13319 }12828 },
1332012829 else => operand_val,
13321 extra_index += info.body_len;12830 };
13322 }
13323 }
13324 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, special_generic.body, cond_operand);
13325 if (empty_enum) {
13326 return .void_value;
13327 }
13328 if (special_members_only) |special| {
13329 assert(operand_ty.isNonexhaustiveEnum(zcu));
13330 if (operand_ty.enumTagFieldIndex(operand_val, zcu)) |_| {
13331 return spa.resolveProngComptime(
13332 child_block,
13333 .special,
13334 special.body,
13335 special.capture,
13336 child_block.src(.{ .switch_capture = .{
13337 .switch_node_offset = switch_node_offset,
13338 .case_idx = .special_else,
13339 } }),
13340 undefined, // case_vals may be undefined for special prongs
13341 if (special.is_inline) cond_operand else .none,
13342 special.has_tag_capture,
13343 merges,
13344 );
13345 }
13346 }12831 }
1334712832
13348 return spa.resolveProngComptime(12833 switch (operand_ty.zigTypeTag(zcu)) {
13349 child_block,12834 .@"union" => {
13350 .special,12835 const union_obj = zcu.typeToUnion(operand_ty).?;
13351 special_generic.body,12836 const first_item_val = sema.resolveConstDefinedValue(case_block, .unneeded, case_vals[0], undefined) catch unreachable;
13352 special_generic.capture,
13353 child_block.src(.{ .switch_capture = .{
13354 .switch_node_offset = switch_node_offset,
13355 .case_idx = if (special_generic_is_under)
13356 .special_under
13357 else
13358 .special_else,
13359 } }),
13360 undefined, // case_vals may be undefined for special prongs
13361 if (special_generic.is_inline) cond_operand else .none,
13362 special_generic.has_tag_capture,
13363 merges,
13364 );
13365}
1336612837
13367const RangeSetUnhandledIterator = struct {12838 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;
13368 pt: Zcu.PerThread,12839 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);
13369 cur: ?InternPool.Index,
13370 max: InternPool.Index,
13371 range_i: usize,
13372 ranges: []const RangeSet.Range,
13373 limbs: []math.big.Limb,
1337412840
13375 const preallocated_limbs = math.big.int.calcTwosCompLimbCount(128);12841 const field_indices = try sema.arena.alloc(u32, case_vals.len);
12842 for (case_vals, field_indices) |item, *field_idx| {
12843 const item_val = sema.resolveConstDefinedValue(case_block, .unneeded, item, undefined) catch unreachable;
12844 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
12845 }
1337612846
13377 fn init(sema: *Sema, ty: Type, range_set: RangeSet) !RangeSetUnhandledIterator {12847 // Fast path: if all the operands are the same type already, we don't need to hit
13378 const pt = sema.pt;12848 // PTR! This will also allow us to emit simpler code.
13379 const int_type = pt.zcu.intern_pool.indexToKey(ty.toIntern()).int_type;12849 const same_types = for (field_indices[1..]) |field_idx| {
13380 const needed_limbs = math.big.int.calcTwosCompLimbCount(int_type.bits);12850 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
13381 return .{12851 if (!field_ty.eql(first_field_ty, zcu)) break false;
13382 .pt = pt,12852 } else true;
13383 .cur = (try ty.minInt(pt, ty)).toIntern(),
13384 .max = (try ty.maxInt(pt, ty)).toIntern(),
13385 .range_i = 0,
13386 .ranges = range_set.ranges.items,
13387 .limbs = if (needed_limbs > preallocated_limbs)
13388 try sema.arena.alloc(math.big.Limb, needed_limbs)
13389 else
13390 &.{},
13391 };
13392 }
1339312853
13394 fn addOne(it: *const RangeSetUnhandledIterator, val: InternPool.Index) !?InternPool.Index {12854 const capture_ty: Type = capture_ty: {
13395 if (val == it.max) return null;12855 if (same_types) break :capture_ty first_field_ty;
13396 const int = it.pt.zcu.intern_pool.indexToKey(val).int;12856 // We need values to run PTR on, so make a bunch of undef constants.
12857 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
12858 for (dummy_captures, field_indices) |*dummy, field_idx| {
12859 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12860 dummy.* = try pt.undefRef(field_ty);
12861 }
1339712862
13398 switch (int.storage) {12863 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
13399 inline .u64, .i64 => |val_int| {12864 for (case_srcs, 0..) |*case_src, item_i| {
13400 const next_int = @addWithOverflow(val_int, 1);12865 case_src.* = .{
13401 if (next_int[1] == 0)12866 .base_node_inst = capture_src.base_node_inst,
13402 return (try it.pt.intValue(.fromInterned(int.ty), next_int[0])).toIntern();12867 .offset = .{ .switch_case_item = .{
13403 },12868 .switch_node_offset = switch_node_offset,
13404 .big_int => {},12869 .case_idx = capture_src.offset.switch_capture.case_idx,
13405 .lazy_align, .lazy_size => unreachable,12870 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
13406 }12871 } },
12872 };
12873 }
12874
12875 break :capture_ty sema.resolvePeerTypes(
12876 case_block,
12877 capture_src,
12878 dummy_captures,
12879 .{ .override = case_srcs },
12880 ) catch |err| switch (err) {
12881 error.AnalysisFail => {
12882 const msg = sema.err orelse return error.AnalysisFail;
12883 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12884 return error.AnalysisFail;
12885 },
12886 else => |e| return e,
12887 };
12888 };
1340712889
13408 var val_space: InternPool.Key.Int.Storage.BigIntSpace = undefined;12890 // By-reference captures have some further restrictions which make them easier to emit
13409 const val_bigint = int.storage.toBigInt(&val_space);12891 if (capture_by_ref) {
12892 const operand_ptr_info = operand_ptr_ty.ptrInfo(zcu);
12893 const capture_ptr_ty = resolve: {
12894 // By-ref captures of hetereogeneous types are only allowed if all field
12895 // pointer types are peer resolvable to each other.
12896 // We need values to run PTR on, so make a bunch of undef constants.
12897 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
12898 for (field_indices, dummy_captures) |field_idx, *dummy| {
12899 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12900 const field_ptr_ty = try pt.ptrTypeSema(.{
12901 .child = field_ty.toIntern(),
12902 .flags = .{
12903 .is_const = operand_ptr_info.flags.is_const,
12904 .is_volatile = operand_ptr_info.flags.is_volatile,
12905 .address_space = operand_ptr_info.flags.address_space,
12906 .alignment = union_obj.fieldAlign(ip, field_idx),
12907 },
12908 });
12909 dummy.* = try pt.undefRef(field_ptr_ty);
12910 }
12911 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
12912 for (case_srcs, 0..) |*case_src, item_i| {
12913 case_src.* = .{
12914 .base_node_inst = capture_src.base_node_inst,
12915 .offset = .{ .switch_case_item = .{
12916 .switch_node_offset = switch_node_offset,
12917 .case_idx = capture_src.offset.switch_capture.case_idx,
12918 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
12919 } },
12920 };
12921 }
1341012922
13411 var result_limbs: [preallocated_limbs]math.big.Limb = undefined;12923 break :resolve sema.resolvePeerTypes(
13412 var result_bigint = math.big.int.Mutable.init(12924 case_block,
13413 if (it.limbs.len > 0) it.limbs else &result_limbs,12925 capture_src,
13414 0,12926 dummy_captures,
13415 );12927 .{ .override = case_srcs },
12928 ) catch |err| switch (err) {
12929 error.AnalysisFail => {
12930 const msg = sema.err orelse return error.AnalysisFail;
12931 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
12932 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12933 return error.AnalysisFail;
12934 },
12935 else => |e| return e,
12936 };
12937 };
1341612938
13417 result_bigint.addScalar(val_bigint, 1);12939 if (try sema.resolveDefinedValue(case_block, operand_src, operand_ptr)) |op_ptr_val| {
13418 return (try it.pt.intValue_big(.fromInterned(int.ty), result_bigint.toConst())).toIntern();12940 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
13419 }12941 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
12942 return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
12943 }
1342012944
13421 fn next(it: *RangeSetUnhandledIterator) !?InternPool.Index {12945 try sema.requireRuntimeBlock(case_block, operand_src, null);
13422 var cur = it.cur orelse return null;12946 return case_block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);
13423 while (it.range_i < it.ranges.len and cur == it.ranges[it.range_i].first) {12947 }
13424 defer it.range_i += 1;
13425 cur = (try it.addOne(it.ranges[it.range_i].last)) orelse {
13426 it.cur = null;
13427 return null;
13428 };
13429 }
13430 it.cur = try it.addOne(cur);
13431 return cur;
13432 }
13433};
1343412948
13435const ResolvedSwitchItem = struct {12949 if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |operand_val_val| {
13436 ref: Air.Inst.Ref,12950 if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);
13437 val: InternPool.Index,12951 const union_val = ip.indexToKey(operand_val_val.toIntern()).un;
13438};12952 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
13439fn resolveSwitchItemVal(12953 const uncoerced: Air.Inst.Ref = .fromIntern(union_val.val);
13440 sema: *Sema,12954 return sema.coerce(case_block, capture_ty, uncoerced, operand_src);
13441 block: *Block,12955 }
13442 item_ref: Zir.Inst.Ref,
13443 /// Coerce `item_ref` to this type.
13444 coerce_ty: Type,
13445 item_src: LazySrcLoc,
13446) CompileError!ResolvedSwitchItem {
13447 const uncoerced_item = try sema.resolveInst(item_ref);
1344812956
13449 // Constructing a LazySrcLoc is costly because we only have the switch AST node.12957 try sema.requireRuntimeBlock(case_block, operand_src, null);
13450 // Only if we know for sure we need to report a compile error do we resolve the
13451 // full source locations.
1345212958
13453 const item = try sema.coerce(block, coerce_ty, uncoerced_item, item_src);12959 if (same_types) {
12960 return case_block.addStructFieldVal(operand_val, first_field_index, capture_ty);
12961 }
1345412962
13455 const maybe_lazy = try sema.resolveConstDefinedValue(block, item_src, item, .{ .simple = .switch_item });12963 // We may have to emit a switch block which coerces the operand to the capture type.
12964 // If we can, try to avoid that using in-memory coercions.
12965 const first_non_imc = in_mem: {
12966 for (field_indices, 0..) |field_idx, i| {
12967 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12968 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12969 break :in_mem i;
12970 }
12971 }
12972 // All fields are in-memory coercible to the resolved type!
12973 // Just take the first field and bitcast the result.
12974 const uncoerced = try case_block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
12975 return case_block.addBitCast(capture_ty, uncoerced);
12976 };
1345612977
13457 const val = try sema.resolveLazyValue(maybe_lazy);12978 // By-val capture with heterogeneous types which are not all in-memory coercible to
13458 const new_item = if (val.toIntern() != maybe_lazy.toIntern()) blk: {12979 // the resolved capture type. We finally have to fall back to the ugly method.
13459 break :blk Air.internedToRef(val.toIntern());
13460 } else item;
1346112980
13462 return .{ .ref = new_item, .val = val.toIntern() };12981 // However, let's first track which operands are in-memory coercible. There may well
13463}12982 // be several, and we can squash all of these cases into the same switch prong using
12983 // a simple bitcast. We'll make this the 'else' prong.
1346412984
13465fn validateErrSetSwitch(12985 var in_mem_coercible: std.DynamicBitSet = try .initFull(sema.arena, field_indices.len);
13466 sema: *Sema,12986 in_mem_coercible.unset(first_non_imc);
13467 block: *Block,12987 {
13468 seen_errors: *SwitchErrorSet,12988 const next = first_non_imc + 1;
13469 case_vals: *std.ArrayList(Air.Inst.Ref),12989 for (field_indices[next..], next..) |field_idx, i| {
13470 operand_ty: Type,12990 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
13471 inst_data: @FieldType(Zir.Inst.Data, "pl_node"),12991 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
13472 scalar_cases_len: u32,12992 in_mem_coercible.unset(i);
13473 multi_cases_len: u32,12993 }
13474 else_case: struct { body: []const Zir.Inst.Index, end: usize, src: LazySrcLoc },12994 }
13475 has_else: bool,12995 }
13476) CompileError!?Type {
13477 const gpa = sema.gpa;
13478 const pt = sema.pt;
13479 const zcu = pt.zcu;
13480 const ip = &zcu.intern_pool;
1348112996
13482 const src_node_offset = inst_data.src_node;12997 const capture_block_inst = try case_block.addInstAsIndex(.{
13483 const src = block.nodeOffset(src_node_offset);12998 .tag = .block,
12999 .data = .{
13000 .ty_pl = .{
13001 .ty = .fromType(capture_ty),
13002 .payload = undefined, // updated below
13003 },
13004 },
13005 });
1348413006
13485 var extra_index: usize = else_case.end;13007 const prong_count = field_indices.len - in_mem_coercible.count();
13486 {
13487 var scalar_i: u32 = 0;
13488 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
13489 const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
13490 extra_index += 1;
13491 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
13492 extra_index += 1 + info.body_len;
1349313008
13494 case_vals.appendAssumeCapacity(try sema.validateSwitchItemError(13009 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
13495 block,13010 var cases_extra = try std.array_list.Managed(u32).initCapacity(sema.gpa, estimated_extra);
13496 seen_errors,13011 defer cases_extra.deinit();
13497 item_ref,
13498 operand_ty,
13499 block.src(.{ .switch_case_item = .{
13500 .switch_node_offset = src_node_offset,
13501 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
13502 .item_idx = .{ .kind = .single, .index = 0 },
13503 } }),
13504 ));
13505 }
13506 }
13507 {
13508 var multi_i: u32 = 0;
13509 while (multi_i < multi_cases_len) : (multi_i += 1) {
13510 const items_len = sema.code.extra[extra_index];
13511 extra_index += 1;
13512 const ranges_len = sema.code.extra[extra_index];
13513 extra_index += 1;
13514 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
13515 extra_index += 1;
13516 const items = sema.code.refSlice(extra_index, items_len);
13517 extra_index += items_len + info.body_len;
1351813012
13519 try case_vals.ensureUnusedCapacity(gpa, items.len);13013 {
13520 for (items, 0..) |item_ref, item_i| {13014 // All branch hints are `.none`, so just add zero elems.
13521 case_vals.appendAssumeCapacity(try sema.validateSwitchItemError(13015 comptime assert(@intFromEnum(std.builtin.BranchHint.none) == 0);
13522 block,13016 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
13523 seen_errors,13017 try cases_extra.appendNTimes(0, need_elems);
13524 item_ref,
13525 operand_ty,
13526 block.src(.{ .switch_case_item = .{
13527 .switch_node_offset = src_node_offset,
13528 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
13529 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
13530 } }),
13531 ));
13532 }13018 }
1353313019
13534 try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);13020 {
13535 }13021 // Non-bitcast cases
13536 }13022 var it = in_mem_coercible.iterator(.{ .kind = .unset });
1353713023 while (it.next()) |idx| {
13538 switch (try sema.resolveInferredErrorSetTy(block, src, operand_ty.toIntern())) {13024 var coerce_block = case_block.makeSubBlock();
13539 .anyerror_type => {13025 defer coerce_block.instructions.deinit(sema.gpa);
13540 if (!has_else) {
13541 return sema.fail(
13542 block,
13543 src,
13544 "else prong required when switching on type 'anyerror'",
13545 .{},
13546 );
13547 }
13548 return .anyerror;
13549 },
13550 else => |err_set_ty_index| else_validation: {
13551 const error_names = ip.indexToKey(err_set_ty_index).error_set_type.names;
13552 var maybe_msg: ?*Zcu.ErrorMsg = null;
13553 errdefer if (maybe_msg) |msg| msg.destroy(sema.gpa);
1355413026
13555 for (error_names.get(ip)) |error_name| {13027 const case_src: LazySrcLoc = .{
13556 if (!seen_errors.contains(error_name) and !has_else) {13028 .base_node_inst = capture_src.base_node_inst,
13557 const msg = maybe_msg orelse blk: {13029 .offset = .{ .switch_case_item = .{
13558 maybe_msg = try sema.errMsg(13030 .switch_node_offset = switch_node_offset,
13559 src,13031 .case_idx = capture_src.offset.switch_capture.case_idx,
13560 "switch must handle all possibilities",13032 .item_idx = .{ .kind = .single, .value = @intCast(idx) },
13561 .{},13033 } },
13562 );
13563 break :blk maybe_msg.?;
13564 };13034 };
1356513035
13566 try sema.errNote(13036 const field_idx = field_indices[idx];
13567 src,13037 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
13568 msg,13038 const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);
13569 "unhandled error value: 'error.{f}'",13039 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
13570 .{error_name.fmt(ip)},13040 _ = try coerce_block.addBr(capture_block_inst, coerced);
13571 );13041
13042 try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
13043 1 + // `item`, no ranges
13044 coerce_block.instructions.items.len);
13045 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
13046 .items_len = 1,
13047 .ranges_len = 0,
13048 .body_len = @intCast(coerce_block.instructions.items.len),
13049 }));
13050 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item
13051 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
13572 }13052 }
13573 }13053 }
13054 const else_body_len = len: {
13055 // 'else' prong uses a bitcast
13056 var coerce_block = case_block.makeSubBlock();
13057 defer coerce_block.instructions.deinit(sema.gpa);
1357413058
13575 if (maybe_msg) |msg| {13059 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
13576 maybe_msg = null;13060 const first_imc_field_idx = field_indices[first_imc_item_idx];
13577 try sema.addDeclaredHereNote(msg, operand_ty);13061 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
13578 return sema.failWithOwnedErrorMsg(block, msg);13062 const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);
13579 }13063 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
13064 _ = try coerce_block.addBr(capture_block_inst, coerced);
13065
13066 try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));
13067 break :len coerce_block.instructions.items.len;
13068 };
1358013069
13581 if (has_else and seen_errors.count() == error_names.len) {13070 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +
13582 // In order to enable common patterns for generic code allow simple else bodies13071 cases_extra.items.len +
13583 // else => unreachable,13072 @typeInfo(Air.Block).@"struct".fields.len +
13584 // else => return,13073 1);
13585 // else => |e| return e,13074
13586 // even if all the possible errors were already handled.13075 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
13587 const tags = sema.code.instructions.items(.tag);13076 try sema.air_instructions.append(sema.gpa, .{
13588 const datas = sema.code.instructions.items(.data);13077 .tag = .switch_br,
13589 for (else_case.body) |else_inst| switch (tags[@intFromEnum(else_inst)]) {13078 .data = .{
13590 .dbg_stmt,13079 .pl_op = .{
13591 .dbg_var_val,13080 .operand = undefined, // set by switch below
13592 .ret_type,13081 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
13593 .as_node,13082 .cases_len = @intCast(prong_count),
13594 .ret_node,13083 .else_body_len = @intCast(else_body_len),
13595 .@"unreachable",13084 }),
13596 .@"defer",
13597 .defer_err_code,
13598 .err_union_code,
13599 .ret_err_value_code,
13600 .save_err_ret_index,
13601 .restore_err_ret_index_unconditional,
13602 .restore_err_ret_index_fn_entry,
13603 .is_non_err,
13604 .ret_is_non_err,
13605 .condbr,
13606 => {},
13607 .extended => switch (datas[@intFromEnum(else_inst)].extended.opcode) {
13608 .restore_err_ret_index => {},
13609 else => break,
13610 },13085 },
13611 else => break,13086 },
13612 } else break :else_validation;13087 });
13088 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
13089
13090 // Set up block body
13091 switch (operand) {
13092 .simple => |s| {
13093 const air_datas = sema.air_instructions.items(.data);
13094 air_datas[switch_br_inst].pl_op.operand = s.cond;
13095 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
13096 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
13097 sema.air_extra.appendAssumeCapacity(switch_br_inst);
13098 },
13099 .loop => {
13100 // The block must first extract the tag from the loaded union.
13101 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
13102 try sema.air_instructions.append(sema.gpa, .{
13103 .tag = .get_union_tag,
13104 .data = .{ .ty_op = .{
13105 .ty = .fromIntern(union_obj.enum_tag_ty),
13106 .operand = operand_val,
13107 } },
13108 });
13109 const air_datas = sema.air_instructions.items(.data);
13110 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
13111 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
13112 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 2 });
13113 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
13114 sema.air_extra.appendAssumeCapacity(switch_br_inst);
13115 },
13116 }
1361313117
13118 return capture_block_inst.toRef();
13119 },
13120 .error_set => {
13121 if (capture_by_ref) {
13614 return sema.fail(13122 return sema.fail(
13615 block,13123 case_block,
13616 else_case.src,13124 capture_src,
13617 "unreachable else prong; all cases already handled",13125 "error set cannot be captured by reference",
13618 .{},13126 .{},
13619 );13127 );
13620 }13128 }
1362113129
13622 var names: InferredErrorSet.NameMap = .{};13130 if (case_vals.len == 1) {
13623 try names.ensureUnusedCapacity(sema.arena, error_names.len);13131 const item_val = sema.resolveConstDefinedValue(case_block, .unneeded, case_vals[0], undefined) catch unreachable;
13624 for (error_names.get(ip)) |error_name| {13132 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
13625 if (seen_errors.contains(error_name)) continue;13133 return sema.bitCast(case_block, item_ty, operand_val, operand_src, null);
13134 }
1362613135
13627 names.putAssumeCapacityNoClobber(error_name, {});13136 var names: InferredErrorSet.NameMap = .{};
13137 try names.ensureUnusedCapacity(sema.arena, case_vals.len);
13138 for (case_vals) |err| {
13139 const err_val = sema.resolveConstDefinedValue(case_block, .unneeded, err, undefined) catch unreachable;
13140 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});
13141 }
13142 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());
13143 return sema.bitCast(case_block, error_ty, operand_val, operand_src, null);
13144 },
13145 else => {
13146 // In this case the capture value is just the passed-through value
13147 // of the switch condition.
13148 if (capture_by_ref) {
13149 return operand_ptr;
13150 } else {
13151 return operand_val;
13628 }13152 }
13629 // No need to keep the hash map metadata correct; here we
13630 // extract the (sorted) keys only.
13631 return try pt.errorSetFromUnsortedNames(names.keys());
13632 },13153 },
13633 }13154 }
13634 return null;
13635}13155}
1363613156
13637fn validateSwitchRange(13157const ResolvedSwitchItem = struct {
13638 sema: *Sema,13158 ref: Air.Inst.Ref,
13639 block: *Block,13159 val: Value,
13640 range_set: *RangeSet,13160};
13641 first_ref: Zir.Inst.Ref,13161const ResolvedSwitchItemAndExtraIndex = struct { ResolvedSwitchItem, usize };
13642 last_ref: Zir.Inst.Ref,
13643 operand_ty: Type,
13644 item_src: LazySrcLoc,
13645) CompileError![2]Air.Inst.Ref {
13646 const first_src: LazySrcLoc = .{
13647 .base_node_inst = item_src.base_node_inst,
13648 .offset = .{ .switch_case_item_range_first = item_src.offset.switch_case_item },
13649 };
13650 const last_src: LazySrcLoc = .{
13651 .base_node_inst = item_src.base_node_inst,
13652 .offset = .{ .switch_case_item_range_last = item_src.offset.switch_case_item },
13653 };
13654 const first = try sema.resolveSwitchItemVal(block, first_ref, operand_ty, first_src);
13655 const last = try sema.resolveSwitchItemVal(block, last_ref, operand_ty, last_src);
13656 if (try Value.fromInterned(first.val).compareAll(.gt, Value.fromInterned(last.val), operand_ty, sema.pt)) {
13657 return sema.fail(block, item_src, "range start value is greater than the end value", .{});
13658 }
13659 const maybe_prev_src = try range_set.add(first.val, last.val, item_src);
13660 try sema.validateSwitchDupe(block, maybe_prev_src, item_src);
13661 return .{ first.ref, last.ref };
13662}
1366313162
13664fn validateSwitchItemInt(13163fn resolveSwitchItem(
13665 sema: *Sema,13164 sema: *Sema,
13666 block: *Block,13165 block: *Block,
13667 range_set: *RangeSet,
13668 item_ref: Zir.Inst.Ref,
13669 operand_ty: Type,
13670 item_src: LazySrcLoc,13166 item_src: LazySrcLoc,
13671) CompileError!Air.Inst.Ref {13167 item_ty: Type,
13672 const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, item_src);13168 item_info: Zir.Inst.SwitchBlock.ItemInfo,
13673 const maybe_prev_src = try range_set.add(item.val, item.val, item_src);13169 extra_index: usize,
13674 try sema.validateSwitchDupe(block, maybe_prev_src, item_src);13170 switch_inst: Zir.Inst.Index,
13675 return item.ref;13171 prong_is_comptime_unreach: bool,
13676}13172 prong_is_inline: bool,
13173) CompileError!ResolvedSwitchItemAndExtraIndex {
13174 const pt = sema.pt;
13175 const zcu = pt.zcu;
13176 const ip = &zcu.intern_pool;
13177 const gpa = sema.gpa;
1367713178
13678fn validateSwitchItemEnum(13179 var end = extra_index;
13679 sema: *Sema,13180 const uncoerced: Air.Inst.Ref, const uncoerced_ty: Type = uncoerced: switch (item_info.unwrap()) {
13680 block: *Block,13181 .enum_literal => |str_index| {
13681 seen_fields: []?LazySrcLoc,13182 const zir_str = sema.code.nullTerminatedString(str_index);
13682 range_set: *RangeSet,13183 const name = try ip.getOrPutString(gpa, pt.tid, zir_str, .no_embedded_nulls);
13683 item_ref: Zir.Inst.Ref,13184 const uncoerced = try sema.analyzeDeclLiteral(block, item_src, name, item_ty, false);
13684 operand_ty: Type,13185 break :uncoerced .{ uncoerced, .enum_literal };
13685 item_src: LazySrcLoc,13186 },
13686) CompileError!Air.Inst.Ref {13187 .error_value => |str_index| {
13687 const ip = &sema.pt.zcu.intern_pool;13188 const zir_str = sema.code.nullTerminatedString(str_index);
13688 const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, item_src);13189 const name = try ip.getOrPutString(gpa, pt.tid, zir_str, .no_embedded_nulls);
13689 const int = ip.indexToKey(item.val).enum_tag.int;13190 // Make sure there's an error integer value associated with `name`.
13690 const field_index = ip.loadEnumType(ip.typeOf(item.val)).tagValueIndex(ip, int) orelse {13191 _ = try pt.getErrorValue(name);
13691 const maybe_prev_src = try range_set.add(int, int, item_src);13192 const err_set_ty = try pt.singleErrorSetType(name);
13692 try sema.validateSwitchDupe(block, maybe_prev_src, item_src);13193 const uncoerced = Air.internedToRef(try pt.intern(.{ .err = .{
13693 return item.ref;13194 .ty = err_set_ty.toIntern(),
13694 };13195 .name = name,
13695 const maybe_prev_src = seen_fields[field_index];13196 } }));
13696 seen_fields[field_index] = item_src;13197 break :uncoerced .{ uncoerced, err_set_ty };
13697 try sema.validateSwitchDupe(block, maybe_prev_src, item_src);13198 },
13698 return item.ref;13199 .number_literal => |zir_ref| {
13699}13200 const uncoerced = try sema.resolveInst(zir_ref);
13201 break :uncoerced .{ uncoerced, sema.typeOf(uncoerced) };
13202 },
13203 .body_len => |body_len| {
13204 const body = sema.code.bodySlice(extra_index, body_len);
13205 end += body.len;
1370013206
13701fn validateSwitchItemError(13207 const uncoerced = ref: {
13702 sema: *Sema,13208 // The result location of item bodies is `.{ .coerce_ty = switch_inst }`.
13703 block: *Block,13209 sema.inst_map.putAssumeCapacity(switch_inst, .fromType(item_ty));
13704 seen_errors: *SwitchErrorSet,13210 defer assert(sema.inst_map.remove(switch_inst));
13705 item_ref: Zir.Inst.Ref,13211 break :ref try sema.resolveInlineBody(block, body, switch_inst);
13706 operand_ty: Type,13212 };
13707 item_src: LazySrcLoc,13213 break :uncoerced .{ uncoerced, sema.typeOf(uncoerced) };
13708) CompileError!Air.Inst.Ref {13214 },
13709 const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, item_src);13215 };
13710 const error_name = sema.pt.zcu.intern_pool.indexToKey(item.val).err.name;13216 const item_ref: Air.Inst.Ref = item_ref: {
13711 const maybe_prev_src = if (try seen_errors.fetchPut(error_name, item_src)) |prev|13217 if (item_ty.zigTypeTag(zcu) == .error_set and
13712 prev.value13218 uncoerced_ty.zigTypeTag(zcu) == .error_set)
13713 else13219 {
13714 null;13220 // We allow prongs with errors which are not part of the error set
13715 try sema.validateSwitchDupe(block, maybe_prev_src, item_src);13221 // being switched on if their prong body is `=> comptime unreachable,`.
13716 return item.ref;13222 switch (try sema.coerceInMemoryAllowedErrorSets(block, item_ty, uncoerced_ty, item_src, item_src)) {
13717}13223 .ok => if (try sema.resolveValue(uncoerced)) |uncoerced_val| {
13224 break :item_ref try sema.coerceInMemory(uncoerced_val, item_ty);
13225 },
13226 .missing_error => if (prong_is_comptime_unreach and !prong_is_inline) {
13227 break :item_ref uncoerced;
13228 },
13229 .from_anyerror => {},
13230 else => unreachable,
13231 }
13232 }
13233 break :item_ref try sema.coerce(block, item_ty, uncoerced, item_src);
13234 };
13235 const maybe_lazy = try sema.resolveConstDefinedValue(block, item_src, item_ref, .{ .simple = .switch_item });
1371813236
13719fn validateSwitchDupe(13237 // We have to resolve lazy values here to avoid false negatives when detecting
13720 sema: *Sema,13238 // duplicate items and comparing items to a comptime-known switch operand.
13721 block: *Block,
13722 maybe_prev_src: ?LazySrcLoc,
13723 item_src: LazySrcLoc,
13724) CompileError!void {
13725 const prev_item_src = maybe_prev_src orelse return;
13726 return sema.failWithOwnedErrorMsg(block, msg: {
13727 const msg = try sema.errMsg(
13728 item_src,
13729 "duplicate switch value",
13730 .{},
13731 );
13732 errdefer msg.destroy(sema.gpa);
13733 try sema.errNote(
13734 prev_item_src,
13735 msg,
13736 "previous value here",
13737 .{},
13738 );
13739 break :msg msg;
13740 });
13741}
1374213239
13743fn validateSwitchItemBool(13240 const val = try sema.resolveLazyValue(maybe_lazy);
13744 sema: *Sema,13241 const ref: Air.Inst.Ref = if (val.toIntern() == maybe_lazy.toIntern())
13745 block: *Block,13242 item_ref
13746 true_count: *u8,13243 else
13747 false_count: *u8,13244 .fromValue(val);
13748 item_ref: Zir.Inst.Ref,13245 return .{ .{ .ref = ref, .val = val }, end };
13749 item_src: LazySrcLoc,
13750) CompileError!Air.Inst.Ref {
13751 const item = try sema.resolveSwitchItemVal(block, item_ref, .bool, item_src);
13752 if (Value.fromInterned(item.val).toBool()) {
13753 true_count.* += 1;
13754 } else {
13755 false_count.* += 1;
13756 }
13757 if (true_count.* > 1 or false_count.* > 1) {
13758 return sema.fail(block, item_src, "duplicate switch value", .{});
13759 }
13760 return item.ref;
13761}13246}
1376213247
13763const ValueSrcMap = std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc);13248fn validateSwitchItemOrRange(
13764
13765fn validateSwitchItemSparse(
13766 sema: *Sema,13249 sema: *Sema,
13767 block: *Block,13250 block: *Block,
13768 seen_values: *ValueSrcMap,
13769 item_ref: Zir.Inst.Ref,
13770 operand_ty: Type,
13771 item_src: LazySrcLoc,13251 item_src: LazySrcLoc,
13772) CompileError!Air.Inst.Ref {13252 /// If `opt_last_val` is not `null`, this refers to the first val of a range.
13773 const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, item_src);13253 item_val: Value,
13774 const kv = try seen_values.fetchPut(sema.gpa, item.val, item_src) orelse return item.ref;13254 opt_last_val: ?Value,
13775 try sema.validateSwitchDupe(block, kv.value, item_src);13255 item_ty: Type,
13776 unreachable;13256 seen_enum_fields: []?LazySrcLoc,
13777}13257 seen_errors: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc),
1377813258 seen_sparse_values: *std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc),
13779fn validateSwitchNoRange(13259 range_set: *RangeSet,
13780 sema: *Sema,13260 true_src: *?LazySrcLoc,
13781 block: *Block,13261 false_src: *?LazySrcLoc,
13782 ranges_len: u32,13262 void_src: *?LazySrcLoc,
13783 operand_ty: Type,
13784 src_node_offset: std.zig.Ast.Node.Offset,
13785) CompileError!void {13263) CompileError!void {
13786 if (ranges_len == 0)13264 const pt = sema.pt;
13787 return;13265 const zcu = pt.zcu;
1378813266 const ip = &zcu.intern_pool;
13789 const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });13267 const maybe_prev_src: ?LazySrcLoc = maybe_prev_src: switch (item_ty.zigTypeTag(zcu)) {
13790 const range_src = block.src(.{ .node_offset_switch_range = src_node_offset });13268 .@"union" => unreachable,
1379113269 .@"enum" => {
13792 const msg = msg: {13270 const int = ip.indexToKey(item_val.toIntern()).enum_tag.int;
13793 const msg = try sema.errMsg(13271 if (ip.loadEnumType(item_ty.toIntern()).tagValueIndex(ip, int)) |field_index| {
13794 operand_src,13272 const maybe_prev_src = seen_enum_fields[field_index];
13795 "ranges not allowed when switching on type '{f}'",13273 seen_enum_fields[field_index] = item_src;
13796 .{operand_ty.fmt(sema.pt)},13274 break :maybe_prev_src maybe_prev_src;
13797 );13275 } else {
13798 errdefer msg.destroy(sema.gpa);13276 break :maybe_prev_src try range_set.add(sema.arena, .{
13799 try sema.errNote(13277 .first = .fromInterned(int),
13800 range_src,13278 .last = .fromInterned(int),
13801 msg,13279 .src = item_src,
13802 "range here",13280 }, .fromInterned(ip.typeOf(int)), zcu);
13803 .{},13281 }
13804 );13282 },
13805 break :msg msg;13283 .error_set => {
13284 const error_name = ip.indexToKey(item_val.toIntern()).err.name;
13285 break :maybe_prev_src if (seen_errors.fetchPutAssumeCapacity(error_name, item_src)) |prev|
13286 prev.value
13287 else
13288 null;
13289 },
13290 .int, .comptime_int => {
13291 if (opt_last_val) |last_val| {
13292 const first_val = item_val;
13293 if (try first_val.compareAll(.gt, last_val, item_ty, pt)) {
13294 return sema.fail(block, item_src, "range start value is greater than the end value", .{});
13295 }
13296 break :maybe_prev_src range_set.addAssumeCapacity(.{
13297 .first = first_val,
13298 .last = last_val,
13299 .src = item_src,
13300 }, item_ty, zcu);
13301 } else {
13302 break :maybe_prev_src range_set.addAssumeCapacity(.{
13303 .first = item_val,
13304 .last = item_val,
13305 .src = item_src,
13306 }, item_ty, zcu);
13307 }
13308 },
13309 .enum_literal, .@"fn", .pointer, .type => {
13310 break :maybe_prev_src if (seen_sparse_values.fetchPutAssumeCapacity(item_val.toIntern(), item_src)) |prev|
13311 prev.value
13312 else
13313 null;
13314 },
13315 .bool => {
13316 if (item_val.toBool()) {
13317 if (true_src.*) |prev_src| break :maybe_prev_src prev_src;
13318 true_src.* = item_src;
13319 } else {
13320 if (false_src.*) |prev_src| break :maybe_prev_src prev_src;
13321 false_src.* = item_src;
13322 }
13323 break :maybe_prev_src null;
13324 },
13325 .void => {
13326 if (void_src.*) |prev_src| break :maybe_prev_src prev_src;
13327 void_src.* = item_src;
13328 break :maybe_prev_src null;
13329 },
13330 else => unreachable, // should have already checked for invalid types
13806 };13331 };
13807 return sema.failWithOwnedErrorMsg(block, msg);13332 if (maybe_prev_src) |prev_src| {
13333 return sema.failWithOwnedErrorMsg(block, msg: {
13334 const msg = try sema.errMsg(
13335 item_src,
13336 "duplicate switch value",
13337 .{},
13338 );
13339 errdefer msg.destroy(sema.gpa);
13340 try sema.errNote(
13341 prev_src,
13342 msg,
13343 "previous value here",
13344 .{},
13345 );
13346 break :msg msg;
13347 });
13348 }
13808}13349}
1380913350
13810fn maybeErrorUnwrap(13351fn maybeErrorUnwrap(
src/Zcu.zig+40-38
...@@ -878,7 +878,7 @@ pub const Namespace = struct {...@@ -878,7 +878,7 @@ pub const Namespace = struct {
878 ns: Namespace,878 ns: Namespace,
879 zcu: *Zcu,879 zcu: *Zcu,
880 name: InternPool.NullTerminatedString,880 name: InternPool.NullTerminatedString,
881 writer: anytype,881 writer: *Writer,
882 ) @TypeOf(writer).Error!void {882 ) @TypeOf(writer).Error!void {
883 const sep: u8 = if (ns.parent.unwrap()) |parent| sep: {883 const sep: u8 = if (ns.parent.unwrap()) |parent| sep: {
884 try zcu.namespacePtr(parent).renderFullyQualifiedDebugName(884 try zcu.namespacePtr(parent).renderFullyQualifiedDebugName(
...@@ -1125,7 +1125,7 @@ pub const File = struct {...@@ -1125,7 +1125,7 @@ pub const File = struct {
1125 return file.sub_file_path.len - ext.len;1125 return file.sub_file_path.len - ext.len;
1126 }1126 }
11271127
1128 pub fn renderFullyQualifiedName(file: File, writer: anytype) !void {1128 pub fn renderFullyQualifiedName(file: File, writer: *Writer) !void {
1129 // Convert all the slashes into dots and truncate the extension.1129 // Convert all the slashes into dots and truncate the extension.
1130 const ext = std.fs.path.extension(file.sub_file_path);1130 const ext = std.fs.path.extension(file.sub_file_path);
1131 const noext = file.sub_file_path[0 .. file.sub_file_path.len - ext.len];1131 const noext = file.sub_file_path[0 .. file.sub_file_path.len - ext.len];
...@@ -1135,7 +1135,7 @@ pub const File = struct {...@@ -1135,7 +1135,7 @@ pub const File = struct {
1135 };1135 };
1136 }1136 }
11371137
1138 pub fn renderFullyQualifiedDebugName(file: File, writer: anytype) !void {1138 pub fn renderFullyQualifiedDebugName(file: File, writer: *Writer) !void {
1139 for (file.sub_file_path) |byte| switch (byte) {1139 for (file.sub_file_path) |byte| switch (byte) {
1140 '/', '\\' => try writer.writeByte('/'),1140 '/', '\\' => try writer.writeByte('/'),
1141 else => try writer.writeByte(byte),1141 else => try writer.writeByte(byte),
...@@ -2177,33 +2177,33 @@ pub const SrcLoc = struct {...@@ -2177,33 +2177,33 @@ pub const SrcLoc = struct {
2177 var multi_i: u32 = 0;2177 var multi_i: u32 = 0;
2178 var scalar_i: u32 = 0;2178 var scalar_i: u32 = 0;
2179 var underscore_node: Ast.Node.OptionalIndex = .none;2179 var underscore_node: Ast.Node.OptionalIndex = .none;
2180 const case = case: for (case_nodes) |case_node| {2180 const case: Ast.full.SwitchCase = case: for (case_nodes) |case_node| {
2181 const case = tree.fullSwitchCase(case_node).?;2181 const case = tree.fullSwitchCase(case_node).?;
2182 if (case.ast.values.len == 0) {2182 if (case.ast.values.len == 0) {
2183 if (want_case_idx == LazySrcLoc.Offset.SwitchCaseIndex.special_else) {2183 if (want_case_idx == Zir.UnwrappedSwitchBlock.Case.Index.@"else") {
2184 break :case case;2184 break :case case;
2185 }2185 }
2186 continue :case;2186 continue :case;
2187 }2187 }
2188 if (underscore_node == .none) for (case.ast.values) |val_node| {2188 if (underscore_node == .none) {
2189 if (tree.nodeTag(val_node) == .identifier and2189 for (case.ast.values) |value| {
2190 mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val_node)), "_"))2190 if (tree.nodeTag(value) == .identifier and
2191 {2191 mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(value)), "_"))
2192 underscore_node = val_node.toOptional();2192 {
2193 if (want_case_idx == LazySrcLoc.Offset.SwitchCaseIndex.special_under) {2193 underscore_node = value.toOptional();
2194 break :case case;2194 if (want_case_idx.is_under) break :case case;
2195 if (case.ast.values.len == 1) continue :case;
2195 }2196 }
2196 continue :case;
2197 }2197 }
2198 };2198 }
21992199
2200 const is_multi = case.ast.values.len != 1 or2200 const is_multi = case.ast.values.len != 1 or
2201 tree.nodeTag(case.ast.values[0]) == .switch_range;2201 tree.nodeTag(case.ast.values[0]) == .switch_range;
22022202
2203 switch (want_case_idx.kind) {2203 switch (want_case_idx.kind) {
2204 .scalar => if (!is_multi and want_case_idx.index == scalar_i)2204 .scalar => if (!is_multi and want_case_idx.value == scalar_i)
2205 break :case case,2205 break :case case,
2206 .multi => if (is_multi and want_case_idx.index == multi_i)2206 .multi => if (is_multi and want_case_idx.value == multi_i)
2207 break :case case,2207 break :case case,
2208 }2208 }
22092209
...@@ -2214,12 +2214,13 @@ pub const SrcLoc = struct {...@@ -2214,12 +2214,13 @@ pub const SrcLoc = struct {
2214 }2214 }
2215 } else unreachable;2215 } else unreachable;
22162216
2217 const want_item = switch (src_loc.lazy) {2217 const want_item_idx = switch (src_loc.lazy) {
2218 .switch_case_item,2218 .switch_case_item,
2219 .switch_case_item_range_first,2219 .switch_case_item_range_first,
2220 .switch_case_item_range_last,2220 .switch_case_item_range_last,
2221 => |x| item_idx: {2221 => |x| item_idx: {
2222 assert(want_case_idx != LazySrcLoc.Offset.SwitchCaseIndex.special_else);2222 assert(want_case_idx != Zir.UnwrappedSwitchBlock.Case.Index.@"else");
2223 assert(want_case_idx != Zir.UnwrappedSwitchBlock.Case.Index.bare_under);
2223 break :item_idx x.item_idx;2224 break :item_idx x.item_idx;
2224 },2225 },
2225 .switch_capture, .switch_tag_capture => {2226 .switch_capture, .switch_tag_capture => {
...@@ -2242,7 +2243,7 @@ pub const SrcLoc = struct {...@@ -2242,7 +2243,7 @@ pub const SrcLoc = struct {
2242 else => unreachable,2243 else => unreachable,
2243 };2244 };
22442245
2245 switch (want_item.kind) {2246 switch (want_item_idx.kind) {
2246 .single => {2247 .single => {
2247 var item_i: u32 = 0;2248 var item_i: u32 = 0;
2248 for (case.ast.values) |item_node| {2249 for (case.ast.values) |item_node| {
...@@ -2251,12 +2252,21 @@ pub const SrcLoc = struct {...@@ -2251,12 +2252,21 @@ pub const SrcLoc = struct {
2251 {2252 {
2252 continue;2253 continue;
2253 }2254 }
2254 if (item_i != want_item.index) {2255 if (item_i != want_item_idx.value) {
2255 item_i += 1;2256 item_i += 1;
2256 continue;2257 continue;
2257 }2258 }
2258 return tree.nodeToSpan(item_node);2259 return tree.nodeToSpan(item_node);
2259 } else unreachable;2260 } else {
2261 for (case.ast.values) |item_node| {
2262 const item_span = tree.nodeToSpan(item_node);
2263 std.debug.print("{s}\n", .{tree.source[item_span.start..item_span.end]});
2264 }
2265 std.debug.print("want_case_idx={any}\n", .{want_case_idx});
2266 std.debug.print("want_item_idx={any}\n", .{want_item_idx});
2267 unreachable;
2268 }
2269 // } else unreachable;
2260 },2270 },
2261 .range => {2271 .range => {
2262 var range_i: u32 = 0;2272 var range_i: u32 = 0;
...@@ -2264,7 +2274,7 @@ pub const SrcLoc = struct {...@@ -2264,7 +2274,7 @@ pub const SrcLoc = struct {
2264 if (tree.nodeTag(item_node) != .switch_range) {2274 if (tree.nodeTag(item_node) != .switch_range) {
2265 continue;2275 continue;
2266 }2276 }
2267 if (range_i != want_item.index) {2277 if (range_i != want_item_idx.value) {
2268 range_i += 1;2278 range_i += 1;
2269 continue;2279 continue;
2270 }2280 }
...@@ -2642,29 +2652,21 @@ pub const LazySrcLoc = struct {...@@ -2642,29 +2652,21 @@ pub const LazySrcLoc = struct {
2642 /// The offset of the switch AST node.2652 /// The offset of the switch AST node.
2643 switch_node_offset: Ast.Node.Offset,2653 switch_node_offset: Ast.Node.Offset,
2644 /// The index of the case to point to within this switch.2654 /// The index of the case to point to within this switch.
2645 case_idx: SwitchCaseIndex,2655 case_idx: Zir.UnwrappedSwitchBlock.Case.Index,
2646 /// The index of the item to point to within this case.2656 /// The index of the item to point to within this case.
2647 item_idx: SwitchItemIndex,2657 item_idx: SwitchItem.Index,
2658
2659 pub const Index = packed struct(u32) {
2660 kind: enum(u1) { single, range },
2661 value: u31,
2662 };
2648 };2663 };
26492664
2650 pub const SwitchCapture = struct {2665 pub const SwitchCapture = struct {
2651 /// The offset of the switch AST node.2666 /// The offset of the switch AST node.
2652 switch_node_offset: Ast.Node.Offset,2667 switch_node_offset: Ast.Node.Offset,
2653 /// The index of the case whose capture to point to.2668 /// The index of the case whose capture to point to.
2654 case_idx: SwitchCaseIndex,2669 case_idx: Zir.UnwrappedSwitchBlock.Case.Index,
2655 };
2656
2657 pub const SwitchCaseIndex = packed struct(u32) {
2658 kind: enum(u1) { scalar, multi },
2659 index: u31,
2660
2661 pub const special_else: SwitchCaseIndex = @bitCast(@as(u32, std.math.maxInt(u32)));
2662 pub const special_under: SwitchCaseIndex = @bitCast(@as(u32, std.math.maxInt(u32) - 1));
2663 };
2664
2665 pub const SwitchItemIndex = packed struct(u32) {
2666 kind: enum(u1) { single, range },
2667 index: u31,
2668 };2670 };
26692671
2670 pub const ArrayCat = struct {2672 pub const ArrayCat = struct {
test/behavior/switch.zig+156
...@@ -1120,3 +1120,159 @@ test "switch on non-exhaustive enum" {...@@ -1120,3 +1120,159 @@ test "switch on non-exhaustive enum" {
1120 try E.doTheTest(.a);1120 try E.doTheTest(.a);
1121 try comptime E.doTheTest(.a);1121 try comptime E.doTheTest(.a);
1122}1122}
1123
1124test "decl literals as switch cases" {
1125 const E = enum(u8) {
1126 bar = 3,
1127 _,
1128
1129 const foo: @This() = @enumFromInt(0xa);
1130
1131 fn doTheTest() !void {
1132 var e: @This() = .foo;
1133 _ = &e;
1134 const ok = switch (e) {
1135 .bar => false,
1136 .foo => true,
1137 else => false,
1138 };
1139 try expect(ok);
1140 }
1141 };
1142
1143 try E.doTheTest();
1144 try comptime E.doTheTest();
1145}
1146
1147// TODO audit after #15909 and/or #19855 are decided/implemented
1148test "switch with uninstantiable union fields" {
1149 const U = union(enum) {
1150 ok: void,
1151 a: noreturn,
1152 b: noreturn,
1153 c: error{},
1154
1155 fn doTheTest() !void {
1156 var u: @This() = .ok;
1157 _ = &u;
1158 try expect(switch (u) {
1159 .ok => true,
1160 .a => comptime unreachable,
1161 .b => comptime unreachable,
1162 .c => comptime unreachable,
1163 });
1164 try expect(switch (u) {
1165 .ok => true,
1166 .a, .b, .c => comptime unreachable,
1167 });
1168 try expect(switch (u) {
1169 .ok => true,
1170 else => comptime unreachable,
1171 });
1172 try expect(switch (u) {
1173 .a => comptime unreachable,
1174 .ok, .b, .c => true,
1175 });
1176 }
1177 };
1178
1179 try U.doTheTest();
1180 try comptime U.doTheTest();
1181}
1182
1183test "switch with tag capture" {
1184 const U = union(enum) {
1185 a,
1186 b: i32,
1187 c: u8,
1188 d: i32,
1189 e: noreturn,
1190
1191 fn doTheTest() !void {
1192 try doTheSwitch(.a);
1193 try doTheSwitch(.{ .b = 123 });
1194 try doTheSwitch(.{ .c = 0xFF });
1195 }
1196 fn doTheSwitch(u: @This()) !void {
1197 switch (u) {
1198 .a => |nothing, tag| {
1199 try expect(nothing == {});
1200 try expect(tag == .a);
1201 try expect(@intFromEnum(tag) == @intFromEnum(@This().a));
1202 },
1203 .b, .d => |_, tag| {
1204 try expect(tag == .b or tag == .d);
1205 },
1206 .e => |payload, tag| {
1207 _ = &payload;
1208 _ = &tag;
1209 comptime unreachable;
1210 },
1211 else => |un, tag| {
1212 try expect(tag == .c);
1213 try expect(un == .c);
1214 try expect(un.c == 0xFF);
1215 },
1216 }
1217 switch (u) {
1218 inline .a, .b, .c => |payload, tag| {
1219 if (@TypeOf(payload) == void) try expect(tag == .a);
1220 if (@TypeOf(payload) == i32) try expect(tag == .b);
1221 if (@TypeOf(payload) == u8) try expect(tag == .c);
1222 },
1223 inline else => |payload, tag| {
1224 if (@TypeOf(payload) == i32) try expect(tag == .d);
1225 try expect(tag != .e);
1226 },
1227 }
1228 }
1229 };
1230
1231 try U.doTheTest();
1232 try comptime U.doTheTest();
1233}
1234
1235test "switch with advanced prong items" {
1236 const S = struct {
1237 fn doTheTest() !void {
1238 try doTheSwitch(2000, 20);
1239 try doTheSwitch(2000, 10);
1240 try doTheSwitch(2000, 5);
1241
1242 try doTheOtherSwitch(@enumFromInt(123));
1243 try doTheOtherSwitch(@enumFromInt(456));
1244 }
1245 fn doTheSwitch(x: u32, comptime factor: u32) !void {
1246 const ok = switch (x) {
1247 num(factor) => true,
1248 typedNum(u32, factor) => true,
1249 blk: {
1250 var val = 400;
1251 val *= factor;
1252 break :blk val;
1253 } => true,
1254 else => false,
1255 };
1256 try expect(ok);
1257 }
1258 fn num(factor: u32) u32 {
1259 return 100 * factor;
1260 }
1261 fn typedNum(comptime T: type, factor: T) T {
1262 return 200 * factor;
1263 }
1264
1265 const E = enum(u32) { _ };
1266 fn doTheOtherSwitch(e: E) !void {
1267 const ok = switch (e) {
1268 @enumFromInt(123) => true,
1269 @enumFromInt(456) => true,
1270 else => false,
1271 };
1272 try expect(ok);
1273 }
1274 };
1275
1276 try S.doTheTest();
1277 try comptime S.doTheTest();
1278}
test/behavior/switch_loop.zig+164-2
...@@ -310,8 +310,8 @@ test "switch loop with single catch-all prong" {...@@ -310,8 +310,8 @@ test "switch loop with single catch-all prong" {
310 label: switch (E.a) {310 label: switch (E.a) {
311 else => {311 else => {
312 x += 1;312 x += 1;
313 if (x >= 5) continue :label .b;
314 if (x == 10) break :label;313 if (x == 10) break :label;
314 if (x >= 5) continue :label .b;
315 continue :label .c;315 continue :label .c;
316 },316 },
317 }317 }
...@@ -320,8 +320,8 @@ test "switch loop with single catch-all prong" {...@@ -320,8 +320,8 @@ test "switch loop with single catch-all prong" {
320 label: switch (E.a) {320 label: switch (E.a) {
321 .a, .b, .c => {321 .a, .b, .c => {
322 x += 1;322 x += 1;
323 if (x >= 15) continue :label .b;
324 if (x == 20) break :label;323 if (x == 20) break :label;
324 if (x >= 15) continue :label .b;
325 continue :label .c;325 continue :label .c;
326 },326 },
327 }327 }
...@@ -346,3 +346,165 @@ test "switch loop with single catch-all prong" {...@@ -346,3 +346,165 @@ test "switch loop with single catch-all prong" {
346 try S.doTheTest();346 try S.doTheTest();
347 try comptime S.doTheTest();347 try comptime S.doTheTest();
348}348}
349
350test "switch loop on type with opv" {
351 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
352
353 const S = struct {
354 const E = enum { opv };
355 const U = union(E) { opv: u0 };
356
357 fn doTheTest() !void {
358 var x: usize = 0;
359 label: switch (E.opv) {
360 .opv => {
361 x += 1;
362 if (x == 10) break :label;
363 if (x >= 5) continue :label .opv;
364 continue :label .opv;
365 },
366 }
367 try expect(x == 10);
368
369 label: switch (E.opv) {
370 else => {
371 x += 1;
372 if (x == 20) break :label;
373 if (x >= 15) continue :label .opv;
374 continue :label .opv;
375 },
376 }
377 try expect(x == 20);
378
379 label: switch (E.opv) {
380 .opv => if (false) continue :label true,
381 }
382
383 label: switch (U{ .opv = 0 }) {
384 .opv => |val| {
385 x += 1;
386 if (x == 30) break :label;
387 if (x >= 25) continue :label .{ .opv = val };
388 continue :label .{ .opv = 0 };
389 },
390 }
391 try expect(x == 30);
392 }
393 };
394 try S.doTheTest();
395 try comptime S.doTheTest();
396}
397
398test "switch loop with tag capture" {
399 const U = union(enum) {
400 a,
401 b: i32,
402 c: u8,
403 d: i32,
404 e: noreturn,
405
406 fn doTheTest() !void {
407 try doTheSwitch(.a);
408 try doTheSwitch(.{ .b = 123 });
409 try doTheSwitch(.{ .c = 0xFF });
410 }
411 fn doTheSwitch(u: @This()) !void {
412 const ok1 = label: switch (u) {
413 .a => |nothing, tag| {
414 try expect(nothing == {});
415 try expect(tag == .a);
416 try expect(@intFromEnum(tag) == @intFromEnum(@This().a));
417 continue :label .{ .d = 456 };
418 },
419 .b, .d => |_, tag| {
420 try expect(tag == .b or tag == .d);
421 continue :label .{ .c = 0x0F };
422 },
423 .e => |payload, tag| {
424 _ = &payload;
425 _ = &tag;
426 return error.AnalyzedNoreturnProng;
427 },
428 else => |un, tag| {
429 try expect(tag == .c);
430 try expect(un == .c);
431 if (un.c == 0xFF) continue :label .a;
432 if (un.c == 0x00) break :label false;
433 break :label true;
434 },
435 };
436 try expect(ok1);
437
438 const ok2 = label: switch (u) {
439 inline .a, .b, .c => |payload, tag| {
440 if (@TypeOf(payload) == void) {
441 try expect(tag == .a);
442 continue :label .{ .b = 456 };
443 }
444 if (@TypeOf(payload) == i32) {
445 try expect(tag == .b);
446 continue :label .{ .d = payload };
447 }
448 if (@TypeOf(payload) == u8) {
449 try expect(tag == .c);
450 continue :label .{ .d = payload };
451 }
452 },
453 inline else => |payload, tag| {
454 if (@TypeOf(payload) == i32) try expect(tag == .d);
455 try expect(tag != .e);
456 if (payload == 0) break :label false;
457 break :label true;
458 },
459 };
460 try expect(ok2);
461 }
462 };
463
464 try U.doTheTest();
465 try comptime U.doTheTest();
466}
467
468test "switch loop for error handling" {
469 const Error = error{ MyError, MyOtherError };
470 const S = struct {
471 fn doTheTest() !void {
472 try doThePayloadSwitch(123);
473 try doTheErrSwitch(error.MyError);
474 try doTheErrSwitch(error.MyOtherError);
475 }
476 fn doThePayloadSwitch(eu: Error!u32) !void {
477 const x = eu catch |err| label: switch (err) {
478 error.MyError => continue :label error.MyOtherError,
479 error.MyOtherError => break :label 0,
480 };
481 try expect(x == 123);
482
483 const y = if (eu) |payload| label: {
484 break :label payload * 2;
485 } else |err| label: switch (err) {
486 error.MyError => continue :label error.MyOtherError,
487 error.MyOtherError => break :label 0,
488 };
489 try expect(y == 246);
490 }
491 fn doTheErrSwitch(eu: Error!u32) !void {
492 const x = eu catch |err| label: switch (err) {
493 error.MyError => continue :label error.MyOtherError,
494 error.MyOtherError => break :label 123,
495 };
496 try expect(x == 123);
497
498 const y = if (eu) |payload| label: {
499 break :label payload * 2;
500 } else |err| label: switch (err) {
501 error.MyError => continue :label error.MyOtherError,
502 error.MyOtherError => break :label 123,
503 };
504 try expect(y == 123);
505 }
506 };
507
508 try S.doTheTest();
509 try comptime S.doTheTest();
510}
test/behavior/switch_on_captured_error.zig+198-15
...@@ -17,7 +17,7 @@ test "switch on error union catch capture" {...@@ -17,7 +17,7 @@ test "switch on error union catch capture" {
17 try testElse();17 try testElse();
18 try testCapture();18 try testCapture();
19 try testInline();19 try testInline();
20 try testEmptyErrSet();20 try testUnreachableElseProng();
21 try testAddressOf();21 try testAddressOf();
22 }22 }
2323
...@@ -240,22 +240,90 @@ test "switch on error union catch capture" {...@@ -240,22 +240,90 @@ test "switch on error union catch capture" {
240 {240 {
241 var a: error{}!u64 = 0;241 var a: error{}!u64 = 0;
242 _ = &a;242 _ = &a;
243 const b: u64 = a catch |err| switch (err) {243 const b = a catch |err| switch (err) {
244 else => |e| return e,244 undefined => @compileError("unreachable"),
245 };245 };
246 try expectEqual(@as(u64, 0), b);246 try expectEqual(@as(u64, 0), b);
247 }247 }
248 }
249
250 fn testUnreachableElseProng() !void {
248 {251 {
249 var a: error{}!u64 = 0;252 var a: error{}!u64 = 0;
250 _ = &a;253 _ = &a;
251 const b: u64 = a catch |err| switch (err) {254 const b = a catch |err| switch (err) {
252 error.UnknownError => return error.Fail,255 else => unreachable,
256 };
257 try expectEqual(@as(u64, 0), b);
258 }
259 {
260 var a: error{}!u64 = 0;
261 _ = &a;
262 const b = a catch |err| switch (err) {
263 else => return,
264 };
265 try expectEqual(@as(u64, 0), b);
266 }
267 {
268 var a: error{}!u64 = 0;
269 _ = &a;
270 const b = a catch |err| switch (err) {
271 else => |e| return e,
272 };
273 try expectEqual(@as(u64, 0), b);
274 }
275 {
276 var a: error{MyError}!u64 = error.MyError;
277 _ = &a;
278 const b = a catch |err| switch (err) {
279 error.MyError => 0,
280 else => unreachable,
281 };
282 try expectEqual(@as(u64, 0), b);
283 }
284 {
285 var a: error{MyError}!u64 = error.MyError;
286 _ = &a;
287 const b = a catch |err| switch (err) {
288 error.MyError => 0,
289 else => return,
290 };
291 try expectEqual(@as(u64, 0), b);
292 }
293 {
294 var a: error{MyError}!u64 = error.MyError;
295 _ = &a;
296 const b = a catch |err| switch (err) {
297 error.MyError => 0,
253 else => |e| return e,298 else => |e| return e,
254 };299 };
255 try expectEqual(@as(u64, 0), b);300 try expectEqual(@as(u64, 0), b);
256 }301 }
257 }302 }
258303
304 fn testErrNotInSet() !void {
305 {
306 var a: error{MyError}!u64 = 0;
307 _ = &a;
308 const b = a catch |err| switch (err) {
309 error.MyError => 1,
310 error.MyOtherError => comptime unreachable,
311 error.YetAnotherError, error.ThereIsAnother => comptime unreachable,
312 };
313 try expectEqual(@as(u64, 0), b);
314 }
315 {
316 var a: error{MyError}!u64 = error.MyError;
317 _ = &a;
318 const b = a catch |err| switch (err) {
319 error.MyError => 0,
320 error.MyOtherError => comptime unreachable,
321 error.YetAnotherError, error.ThereIsAnother => comptime unreachable,
322 };
323 try expectEqual(@as(u64, 0), b);
324 }
325 }
326
259 fn testAddressOf() !void {327 fn testAddressOf() !void {
260 {328 {
261 const a: anyerror!usize = 0;329 const a: anyerror!usize = 0;
...@@ -316,8 +384,8 @@ test "switch on error union if else capture" {...@@ -316,8 +384,8 @@ test "switch on error union if else capture" {
316 try testCapturePtr();384 try testCapturePtr();
317 try testInline();385 try testInline();
318 try testInlinePtr();386 try testInlinePtr();
319 try testEmptyErrSet();387 try testUnreachableElseProng();
320 try testEmptyErrSetPtr();388 try testUnreachableElseProngPtr();
321 try testAddressOf();389 try testAddressOf();
322 }390 }
323391
...@@ -755,40 +823,155 @@ test "switch on error union if else capture" {...@@ -755,40 +823,155 @@ test "switch on error union if else capture" {
755 {823 {
756 var a: error{}!u64 = 0;824 var a: error{}!u64 = 0;
757 _ = &a;825 _ = &a;
758 const b: u64 = if (a) |x| x else |err| switch (err) {826 const b = if (a) |x| x else |err| switch (err) {
759 else => |e| return e,827 undefined => @compileError("unreachable"),
760 };828 };
761 try expectEqual(@as(u64, 0), b);829 try expectEqual(@as(u64, 0), b);
762 }830 }
831 }
832
833 fn testEmptyErrSetPtr() !void {
763 {834 {
764 var a: error{}!u64 = 0;835 var a: error{}!u64 = 0;
765 _ = &a;836 _ = &a;
766 const b: u64 = if (a) |x| x else |err| switch (err) {837 const b = if (a) |*x| x.* else |err| switch (err) {
838 error.undefined => @compileError("unreachable"),
839 };
840 try expectEqual(@as(u64, 0), b);
841 }
842 }
843
844 fn testUnreachableElseProng() !void {
845 {
846 var a: error{}!u64 = 0;
847 _ = &a;
848 const b = if (a) |x| x else |err| switch (err) {
849 else => unreachable,
850 };
851 try expectEqual(@as(u64, 0), b);
852 }
853 {
854 var a: error{}!u64 = 0;
855 _ = &a;
856 const b = if (a) |x| x else |err| switch (err) {
857 error.UnknownError => return error.Fail,
858 else => return,
859 };
860 try expectEqual(@as(u64, 0), b);
861 }
862 {
863 var a: error{}!u64 = 0;
864 _ = &a;
865 const b = if (a) |x| x else |err| switch (err) {
767 error.UnknownError => return error.Fail,866 error.UnknownError => return error.Fail,
768 else => |e| return e,867 else => |e| return e,
769 };868 };
770 try expectEqual(@as(u64, 0), b);869 try expectEqual(@as(u64, 0), b);
771 }870 }
871 {
872 var a: error{MyError}!u64 = error.MyError;
873 _ = &a;
874 const b = if (a) |x| x else |err| switch (err) {
875 error.MyError => 0,
876 else => unreachable,
877 };
878 try expectEqual(@as(u64, 0), b);
879 }
880 {
881 var a: error{MyError}!u64 = error.MyError;
882 _ = &a;
883 const b = if (a) |x| x else |err| switch (err) {
884 error.MyError => 0,
885 else => return,
886 };
887 try expectEqual(@as(u64, 0), b);
888 }
889 {
890 var a: error{MyError}!u64 = error.MyError;
891 _ = &a;
892 const b = if (a) |x| x else |err| switch (err) {
893 error.MyError => 0,
894 else => |e| return e,
895 };
896 try expectEqual(@as(u64, 0), b);
897 }
772 }898 }
773899
774 fn testEmptyErrSetPtr() !void {900 fn testUnreachableElseProngPtr() !void {
775 {901 {
776 var a: error{}!u64 = 0;902 var a: error{}!u64 = 0;
777 _ = &a;903 _ = &a;
778 const b: u64 = if (a) |*x| x.* else |err| switch (err) {904 const b = if (a) |*x| x.* else |err| switch (err) {
779 else => |e| return e,905 else => unreachable,
780 };906 };
781 try expectEqual(@as(u64, 0), b);907 try expectEqual(@as(u64, 0), b);
782 }908 }
783 {909 {
784 var a: error{}!u64 = 0;910 var a: error{}!u64 = 0;
785 _ = &a;911 _ = &a;
786 const b: u64 = if (a) |*x| x.* else |err| switch (err) {912 const b = if (a) |*x| x.* else |err| switch (err) {
787 error.UnknownError => return error.Fail,913 else => return,
914 };
915 try expectEqual(@as(u64, 0), b);
916 }
917 {
918 var a: error{}!u64 = 0;
919 _ = &a;
920 const b = if (a) |*x| x.* else |err| switch (err) {
788 else => |e| return e,921 else => |e| return e,
789 };922 };
790 try expectEqual(@as(u64, 0), b);923 try expectEqual(@as(u64, 0), b);
791 }924 }
925 {
926 var a: error{MyError}!u64 = error.MyError;
927 _ = &a;
928 const b = if (a) |*x| x.* else |err| switch (err) {
929 error.MyError => 0,
930 else => unreachable,
931 };
932 try expectEqual(@as(u64, 0), b);
933 }
934 {
935 var a: error{MyError}!u64 = error.MyError;
936 _ = &a;
937 const b = if (a) |*x| x.* else |err| switch (err) {
938 error.MyError => 0,
939 else => return,
940 };
941 try expectEqual(@as(u64, 0), b);
942 }
943 {
944 var a: error{MyError}!u64 = error.MyError;
945 _ = &a;
946 const b = if (a) |*x| x.* else |err| switch (err) {
947 error.MyError => 0,
948 else => |e| return e,
949 };
950 try expectEqual(@as(u64, 0), b);
951 }
952 }
953
954 fn testErrNotInSet() !void {
955 {
956 var a: error{MyError}!u64 = 0;
957 _ = &a;
958 const b = if (a) |x| x else |err| switch (err) {
959 error.MyError => 1,
960 error.MyOtherError => comptime unreachable,
961 error.YetAnotherError, error.ThereIsAnother => comptime unreachable,
962 };
963 try expectEqual(@as(u64, 0), b);
964 }
965 {
966 var a: error{MyError}!u64 = error.MyError;
967 _ = &a;
968 const b = if (a) |x| x else |err| switch (err) {
969 error.MyError => 0,
970 error.MyOtherError => comptime unreachable,
971 error.YetAnotherError, error.ThereIsAnother => comptime unreachable,
972 };
973 try expectEqual(@as(u64, 0), b);
974 }
792 }975 }
793976
794 fn testAddressOf() !void {977 fn testAddressOf() !void {
test/cases/compile_errors/duplicate_boolean_switch_value.zig+2
...@@ -18,4 +18,6 @@ comptime {...@@ -18,4 +18,6 @@ comptime {
18// error18// error
19//19//
20// :5:9: error: duplicate switch value20// :5:9: error: duplicate switch value
21// :3:9: note: previous value here
21// :13:9: error: duplicate switch value22// :13:9: error: duplicate switch value
23// :11:9: note: previous value here
test/cases/compile_errors/invalid_switch_item.zig+4-4
...@@ -36,11 +36,11 @@ export fn f3() void {...@@ -36,11 +36,11 @@ export fn f3() void {
3636
37// error37// error
38//38//
39// :8:10: error: no field named 'x' in enum 'tmp.E'39// :8:10: error: enum 'tmp.E' has no member named 'x'
40// :1:11: note: enum declared here40// :1:11: note: enum declared here
41// :16:10: error: no field named 'x' in enum 'tmp.E'41// :16:10: error: enum 'tmp.E' has no member named 'x'
42// :1:11: note: enum declared here42// :1:11: note: enum declared here
43// :24:10: error: no field named 'x' in enum 'tmp.E'43// :24:10: error: enum 'tmp.E' has no member named 'x'
44// :1:11: note: enum declared here44// :1:11: note: enum declared here
45// :32:10: error: no field named 'x' in enum 'tmp.E'45// :32:10: error: enum 'tmp.E' has no member named 'x'
46// :1:11: note: enum declared here46// :1:11: note: enum declared here
test/cases/compile_errors/switch_on_error_with_1_field_with_no_prongs.zig+23-7
...@@ -1,18 +1,34 @@...@@ -1,18 +1,34 @@
1const Error = error{M};1const Error = error{M};
22
3export fn entry() void {3export fn entry1() void {
4 const f: Error!void = void{};4 var f: Error!void = {};
5 _ = &f;
5 if (f) {} else |e| switch (e) {}6 if (f) {} else |e| switch (e) {}
6}7}
78
8export fn entry2() void {9export fn entry2() void {
9 const f: Error!void = void{};10 var f: Error!void = {};
11 _ = &f;
12 f catch |e| switch (e) {};
13}
14
15export fn entry3() void {
16 const f: Error!void = error.M;
17 if (f) {} else |e| switch (e) {}
18}
19
20export fn entry4() void {
21 const f: Error!void = error.M;
10 f catch |e| switch (e) {};22 f catch |e| switch (e) {};
11}23}
1224
13// error25// error
14//26//
15// :5:24: error: switch must handle all possibilities27// :6:24: error: switch must handle all possibilities
16// :5:24: note: unhandled error value: 'error.M'28// :6:24: note: unhandled error value: 'error.M'
17// :10:17: error: switch must handle all possibilities29// :12:17: error: switch must handle all possibilities
18// :10:17: note: unhandled error value: 'error.M'30// :12:17: note: unhandled error value: 'error.M'
31// :17:24: error: switch must handle all possibilities
32// :17:24: note: unhandled error value: 'error.M'
33// :22:17: error: switch must handle all possibilities
34// :22:17: note: unhandled error value: 'error.M'
test/cases/compile_errors/tag_capture_on_non_inline_prong.zig deleted-12
...@@ -1,12 +0,0 @@
1const E = enum { a, b, c, d };
2pub export fn entry() void {
3 var x: E = .a;
4 switch (x) {
5 .a, .b => |aorb, d| @compileLog(aorb, d),
6 inline .c, .d => |*cord| @compileLog(cord),
7 }
8}
9
10// error
11//
12// :5:26: error: tag capture on non-inline prong