Commit cebd80032a

mlugg <mlugg@mlugg.co.uk>
2023-05-06 00:04:34
Move switch case value coercion from AstGen to Sema
This is in preparation for #2473. Also fixes a bug where switching on bools allows invalid case combinations.
1 parent a377bf8
Changed files (2)
src/AstGen.zig
@@ -6837,9 +6837,7 @@ fn switchExpr(
     const cond = try parent_gz.addUnNode(cond_tag, raw_operand, operand_node);
     // Sema expects a dbg_stmt immediately after switch_cond(_ref)
     try emitDbgStmt(parent_gz, operand_lc);
-    // We need the type of the operand to use as the result location for all the prong items.
-    const cond_ty_inst = try parent_gz.addUnNode(.typeof, cond, operand_node);
-    const item_ri: ResultInfo = .{ .rl = .{ .ty = cond_ty_inst } };
+    const item_ri: ResultInfo = .{ .rl = .none };
 
     // This contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti,
     // except the first cases_nodes.len slots are a table that indexes payloads later in the array, with
src/Sema.zig
@@ -10099,6 +10099,8 @@ fn zirSwitchCapture(
     const switch_info = zir_datas[capture_info.switch_inst].pl_node;
     const switch_extra = sema.code.extraData(Zir.Inst.SwitchBlock, switch_info.payload_index);
     const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = switch_info.src_node };
+    const cond = try sema.resolveInst(switch_extra.data.operand);
+    const cond_ty = sema.typeOf(cond);
     const cond_inst = Zir.refToIndex(switch_extra.data.operand).?;
     const cond_info = zir_datas[cond_inst].un_node;
     const cond_tag = sema.code.instructions.items(.tag)[cond_inst];
@@ -10175,6 +10177,8 @@ fn zirSwitchCapture(
         }
     }
 
+    // Note that these are the *uncasted* prong items.
+    // Also note that items from ranges are not included so this only works for non-ranged types.
     const items = switch_extra.data.getProng(sema.code, switch_extra.end, capture_info.prong_index).items;
 
     switch (operand_ty.zigTypeTag(mod)) {
@@ -10182,7 +10186,8 @@ fn zirSwitchCapture(
             const union_obj = mod.typeToUnion(operand_ty).?;
             const first_item = try sema.resolveInst(items[0]);
             // Previous switch validation ensured this will succeed
-            const first_item_val = sema.resolveConstValue(block, .unneeded, first_item, "") catch unreachable;
+            const first_item_coerced = try sema.coerce(block, cond_ty, first_item, .unneeded);
+            const first_item_val = sema.resolveConstValue(block, .unneeded, first_item_coerced, "") catch unreachable;
 
             const first_field_index = @intCast(u32, operand_ty.unionTagFieldIndex(first_item_val, mod).?);
             const first_field = union_obj.fields.values()[first_field_index];
@@ -10190,7 +10195,8 @@ fn zirSwitchCapture(
             for (items[1..], 0..) |item, i| {
                 const item_ref = try sema.resolveInst(item);
                 // Previous switch validation ensured this will succeed
-                const item_val = sema.resolveConstValue(block, .unneeded, item_ref, "") catch unreachable;
+                const item_coerced = try sema.coerce(block, cond_ty, item_ref, .unneeded);
+                const item_val = sema.resolveConstValue(block, .unneeded, item_coerced, "") catch unreachable;
 
                 const field_index = operand_ty.unionTagFieldIndex(item_val, mod).?;
                 const field = union_obj.fields.values()[field_index];
@@ -10406,6 +10412,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
         break :blk multi_cases_len;
     } else 0;
 
+    var case_vals = try std.ArrayListUnmanaged(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len);
+    defer case_vals.deinit(gpa);
+
     const special_prong = extra.data.bits.specialProng();
     const special: struct { body: []const Zir.Inst.Index, end: usize, is_inline: bool } = switch (special_prong) {
         .none => .{ .body = &.{}, .end = header_extra_index, .is_inline = false },
@@ -10491,14 +10500,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     extra_index += 1;
                     extra_index += body_len;
 
-                    try sema.validateSwitchItemEnum(
+                    case_vals.appendAssumeCapacity(try sema.validateSwitchItemEnum(
                         block,
                         seen_enum_fields,
                         &range_set,
                         item_ref,
+                        operand_ty,
                         src_node_offset,
                         .{ .scalar = scalar_i },
-                    );
+                    ));
                 }
             }
             {
@@ -10513,15 +10523,17 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     const items = sema.code.refSlice(extra_index, items_len);
                     extra_index += items_len + body_len;
 
+                    try case_vals.ensureUnusedCapacity(gpa, items.len);
                     for (items, 0..) |item_ref, item_i| {
-                        try sema.validateSwitchItemEnum(
+                        case_vals.appendAssumeCapacity(try sema.validateSwitchItemEnum(
                             block,
                             seen_enum_fields,
                             &range_set,
                             item_ref,
+                            operand_ty,
                             src_node_offset,
                             .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } },
-                        );
+                        ));
                     }
 
                     try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);
@@ -10588,13 +10600,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     extra_index += 1;
                     extra_index += body_len;
 
-                    try sema.validateSwitchItemError(
+                    case_vals.appendAssumeCapacity(try sema.validateSwitchItemError(
                         block,
                         &seen_errors,
                         item_ref,
+                        operand_ty,
                         src_node_offset,
                         .{ .scalar = scalar_i },
-                    );
+                    ));
                 }
             }
             {
@@ -10609,14 +10622,16 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     const items = sema.code.refSlice(extra_index, items_len);
                     extra_index += items_len + body_len;
 
+                    try case_vals.ensureUnusedCapacity(gpa, items.len);
                     for (items, 0..) |item_ref, item_i| {
-                        try sema.validateSwitchItemError(
+                        case_vals.appendAssumeCapacity(try sema.validateSwitchItemError(
                             block,
                             &seen_errors,
                             item_ref,
+                            operand_ty,
                             src_node_offset,
                             .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } },
-                        );
+                        ));
                     }
 
                     try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);
@@ -10728,13 +10743,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     extra_index += 1;
                     extra_index += body_len;
 
-                    try sema.validateSwitchItem(
+                    case_vals.appendAssumeCapacity(try sema.validateSwitchItemInt(
                         block,
                         &range_set,
                         item_ref,
+                        operand_ty,
                         src_node_offset,
                         .{ .scalar = scalar_i },
-                    );
+                    ));
                 }
             }
             {
@@ -10749,16 +10765,19 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     const items = sema.code.refSlice(extra_index, items_len);
                     extra_index += items_len;
 
+                    try case_vals.ensureUnusedCapacity(gpa, items.len);
                     for (items, 0..) |item_ref, item_i| {
-                        try sema.validateSwitchItem(
+                        case_vals.appendAssumeCapacity(try sema.validateSwitchItemInt(
                             block,
                             &range_set,
                             item_ref,
+                            operand_ty,
                             src_node_offset,
                             .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } },
-                        );
+                        ));
                     }
 
+                    try case_vals.ensureUnusedCapacity(gpa, 2 * ranges_len);
                     var range_i: u32 = 0;
                     while (range_i < ranges_len) : (range_i += 1) {
                         const item_first = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
@@ -10766,14 +10785,17 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                         const item_last = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
                         extra_index += 1;
 
-                        try sema.validateSwitchRange(
+                        const vals = try sema.validateSwitchRange(
                             block,
                             &range_set,
                             item_first,
                             item_last,
+                            operand_ty,
                             src_node_offset,
                             .{ .range = .{ .prong = multi_i, .item = range_i } },
                         );
+                        case_vals.appendAssumeCapacity(vals[0]);
+                        case_vals.appendAssumeCapacity(vals[1]);
                     }
 
                     extra_index += body_len;
@@ -10817,14 +10839,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     extra_index += 1;
                     extra_index += body_len;
 
-                    try sema.validateSwitchItemBool(
+                    case_vals.appendAssumeCapacity(try sema.validateSwitchItemBool(
                         block,
                         &true_count,
                         &false_count,
                         item_ref,
                         src_node_offset,
                         .{ .scalar = scalar_i },
-                    );
+                    ));
                 }
             }
             {
@@ -10839,15 +10861,16 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     const items = sema.code.refSlice(extra_index, items_len);
                     extra_index += items_len + body_len;
 
+                    try case_vals.ensureUnusedCapacity(gpa, items.len);
                     for (items, 0..) |item_ref, item_i| {
-                        try sema.validateSwitchItemBool(
+                        case_vals.appendAssumeCapacity(try sema.validateSwitchItemBool(
                             block,
                             &true_count,
                             &false_count,
                             item_ref,
                             src_node_offset,
                             .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } },
-                        );
+                        ));
                     }
 
                     try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);
@@ -10899,13 +10922,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     extra_index += 1;
                     extra_index += body_len;
 
-                    try sema.validateSwitchItemSparse(
+                    case_vals.appendAssumeCapacity(try sema.validateSwitchItemSparse(
                         block,
                         &seen_values,
                         item_ref,
+                        operand_ty,
                         src_node_offset,
                         .{ .scalar = scalar_i },
-                    );
+                    ));
                 }
             }
             {
@@ -10920,14 +10944,16 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     const items = sema.code.refSlice(extra_index, items_len);
                     extra_index += items_len + body_len;
 
+                    try case_vals.ensureUnusedCapacity(gpa, items.len);
                     for (items, 0..) |item_ref, item_i| {
-                        try sema.validateSwitchItemSparse(
+                        case_vals.appendAssumeCapacity(try sema.validateSwitchItemSparse(
                             block,
                             &seen_values,
                             item_ref,
+                            operand_ty,
                             src_node_offset,
                             .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } },
-                        );
+                        ));
                     }
 
                     try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);
@@ -10997,7 +11023,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
         {
             var scalar_i: usize = 0;
             while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
-                const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
                 extra_index += 1;
                 const body_len = @truncate(u31, sema.code.extra[extra_index]);
                 const is_inline = sema.code.extra[extra_index] >> 31 != 0;
@@ -11005,8 +11030,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                 const body = sema.code.extra[extra_index..][0..body_len];
                 extra_index += body_len;
 
-                const item = try sema.resolveInst(item_ref);
-                // Validation above ensured these will succeed.
+                const item = case_vals.items[scalar_i];
                 const item_val = sema.resolveConstLazyValue(&child_block, .unneeded, item, "") catch unreachable;
                 if (resolved_operand_val.eql(item_val, operand_ty, mod)) {
                     if (is_inline) child_block.inline_case_capture = operand;
@@ -11018,6 +11042,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
         }
         {
             var multi_i: usize = 0;
+            var case_val_idx: usize = scalar_cases_len;
             while (multi_i < multi_cases_len) : (multi_i += 1) {
                 const items_len = sema.code.extra[extra_index];
                 extra_index += 1;
@@ -11025,13 +11050,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                 extra_index += 1;
                 const body_len = @truncate(u31, sema.code.extra[extra_index]);
                 const is_inline = sema.code.extra[extra_index] >> 31 != 0;
-                extra_index += 1;
-                const items = sema.code.refSlice(extra_index, items_len);
-                extra_index += items_len;
+                extra_index += 1 + items_len;
                 const body = sema.code.extra[extra_index + 2 * ranges_len ..][0..body_len];
 
-                for (items) |item_ref| {
-                    const item = try sema.resolveInst(item_ref);
+                const items = case_vals.items[case_val_idx..][0..items_len];
+                case_val_idx += items_len;
+
+                for (items) |item| {
                     // Validation above ensured these will succeed.
                     const item_val = sema.resolveConstLazyValue(&child_block, .unneeded, item, "") catch unreachable;
                     if (resolved_operand_val.eql(item_val, operand_ty, mod)) {
@@ -11044,16 +11069,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
 
                 var range_i: usize = 0;
                 while (range_i < ranges_len) : (range_i += 1) {
-                    const item_first = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
-                    extra_index += 1;
-                    const item_last = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
-                    extra_index += 1;
+                    const range_items = case_vals.items[case_val_idx..][0..2];
+                    extra_index += 2;
+                    case_val_idx += 2;
 
                     // Validation above ensured these will succeed.
-                    const first_tv = sema.resolveInstConst(&child_block, .unneeded, item_first, "") catch unreachable;
-                    const last_tv = sema.resolveInstConst(&child_block, .unneeded, item_last, "") catch unreachable;
-                    if ((try sema.compareAll(resolved_operand_val, .gte, first_tv.val, operand_ty)) and
-                        (try sema.compareAll(resolved_operand_val, .lte, last_tv.val, operand_ty)))
+                    const first_val = sema.resolveConstValue(&child_block, .unneeded, range_items[0], "") catch unreachable;
+                    const last_val = sema.resolveConstValue(&child_block, .unneeded, range_items[1], "") catch unreachable;
+                    if ((try sema.compareAll(resolved_operand_val, .gte, first_val, operand_ty)) and
+                        (try sema.compareAll(resolved_operand_val, .lte, last_val, operand_ty)))
                     {
                         if (is_inline) child_block.inline_case_capture = operand;
                         if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand);
@@ -11115,7 +11139,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
 
     var scalar_i: usize = 0;
     while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
-        const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
         extra_index += 1;
         const body_len = @truncate(u31, sema.code.extra[extra_index]);
         const is_inline = sema.code.extra[extra_index] >> 31 != 0;
@@ -11130,7 +11153,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
         case_block.wip_capture_scope = wip_captures.scope;
         case_block.inline_case_capture = .none;
 
-        const item = try sema.resolveInst(item_ref);
+        const item = case_vals.items[scalar_i];
         if (is_inline) case_block.inline_case_capture = item;
         // `item` is already guaranteed to be constant known.
 
@@ -11165,6 +11188,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
     defer gpa.free(prev_then_body);
 
     var cases_len = scalar_cases_len;
+    var case_val_idx: usize = scalar_cases_len;
     var multi_i: u32 = 0;
     while (multi_i < multi_cases_len) : (multi_i += 1) {
         const items_len = sema.code.extra[extra_index];
@@ -11173,9 +11197,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
         extra_index += 1;
         const body_len = @truncate(u31, sema.code.extra[extra_index]);
         const is_inline = sema.code.extra[extra_index] >> 31 != 0;
-        extra_index += 1;
-        const items = sema.code.refSlice(extra_index, items_len);
-        extra_index += items_len;
+        extra_index += 1 + items_len;
+
+        const items = case_vals.items[case_val_idx..][0..items_len];
+        case_val_idx += items_len;
 
         case_block.instructions.shrinkRetainingCapacity(0);
         case_block.wip_capture_scope = child_block.wip_capture_scope;
@@ -11189,14 +11214,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
 
             var range_i: u32 = 0;
             while (range_i < ranges_len) : (range_i += 1) {
-                const first_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
-                extra_index += 1;
-                const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
-                extra_index += 1;
+                const range_items = case_vals.items[case_val_idx..][0..2];
+                extra_index += 2;
+                case_val_idx += 2;
+
+                const item_first_ref = range_items[0];
+                const item_last_ref = range_items[1];
 
-                const item_first_ref = try sema.resolveInst(first_ref);
                 var item = sema.resolveConstValue(block, .unneeded, item_first_ref, undefined) catch unreachable;
-                const item_last_ref = try sema.resolveInst(last_ref);
                 const item_last = sema.resolveConstValue(block, .unneeded, item_last_ref, undefined) catch unreachable;
 
                 while (item.compareScalar(.lte, item_last, operand_ty, mod)) : ({
@@ -11235,10 +11260,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                 }
             }
 
-            for (items, 0..) |item_ref, item_i| {
+            for (items, 0..) |item, item_i| {
                 cases_len += 1;
 
-                const item = try sema.resolveInst(item_ref);
                 case_block.inline_case_capture = item;
 
                 case_block.instructions.shrinkRetainingCapacity(0);
@@ -11287,8 +11311,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
             cases_len += 1;
 
             const analyze_body = if (union_originally)
-                for (items) |item_ref| {
-                    const item = try sema.resolveInst(item_ref);
+                for (items) |item| {
                     const item_val = sema.resolveConstValue(block, .unneeded, item, "") catch unreachable;
                     const field_ty = maybe_union_ty.unionFieldType(item_val, mod);
                     if (field_ty.zigTypeTag(mod) != .NoReturn) break true;
@@ -11312,15 +11335,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
             cases_extra.appendAssumeCapacity(@intCast(u32, items.len));
             cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
 
-            for (items) |item_ref| {
-                const item = try sema.resolveInst(item_ref);
+            for (items) |item| {
                 cases_extra.appendAssumeCapacity(@enumToInt(item));
             }
 
             cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
         } else {
-            for (items) |item_ref| {
-                const item = try sema.resolveInst(item_ref);
+            for (items) |item| {
                 const cmp_ok = try case_block.addBinOp(if (case_block.float_mode == .Optimized) .cmp_eq_optimized else .cmp_eq, operand, item);
                 if (any_ok != .none) {
                     any_ok = try case_block.addBinOp(.bool_or, any_ok, cmp_ok);
@@ -11331,13 +11352,12 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
 
             var range_i: usize = 0;
             while (range_i < ranges_len) : (range_i += 1) {
-                const first_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
-                extra_index += 1;
-                const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
-                extra_index += 1;
+                const range_items = case_vals.items[case_val_idx..][0..2];
+                extra_index += 2;
+                case_val_idx += 2;
 
-                const item_first = try sema.resolveInst(first_ref);
-                const item_last = try sema.resolveInst(last_ref);
+                const item_first = range_items[0];
+                const item_last = range_items[1];
 
                 // operand >= first and operand <= last
                 const range_first_ok = try case_block.addBinOp(
@@ -11696,29 +11716,46 @@ const RangeSetUnhandledIterator = struct {
     }
 };
 
+const ResolvedSwitchItem = struct {
+    ref: Air.Inst.Ref,
+    val: InternPool.Index,
+};
 fn resolveSwitchItemVal(
     sema: *Sema,
     block: *Block,
     item_ref: Zir.Inst.Ref,
+    /// Coerce `item_ref` to this type.
+    coerce_ty: Type,
     switch_node_offset: i32,
     switch_prong_src: Module.SwitchProngSrc,
     range_expand: Module.SwitchProngSrc.RangeExpand,
-) CompileError!InternPool.Index {
+) CompileError!ResolvedSwitchItem {
     const mod = sema.mod;
-    const item = try sema.resolveInst(item_ref);
+    const uncoerced_item = try sema.resolveInst(item_ref);
+
     // Constructing a LazySrcLoc is costly because we only have the switch AST node.
     // Only if we know for sure we need to report a compile error do we resolve the
     // full source locations.
-    if (sema.resolveConstLazyValue(block, .unneeded, item, "")) |val| {
-        return val.toIntern();
-    } else |err| switch (err) {
+
+    const item = sema.coerce(block, coerce_ty, uncoerced_item, .unneeded) catch |err| switch (err) {
+        error.NeededSourceLocation => {
+            const src = switch_prong_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, range_expand);
+            _ = try sema.coerce(block, coerce_ty, uncoerced_item, src);
+            unreachable;
+        },
+        else => |e| return e,
+    };
+
+    const val = sema.resolveConstLazyValue(block, .unneeded, item, "") catch |err| switch (err) {
         error.NeededSourceLocation => {
             const src = switch_prong_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, range_expand);
             _ = try sema.resolveConstValue(block, src, item, "switch prong values must be comptime-known");
             unreachable;
         },
         else => |e| return e,
-    }
+    };
+
+    return .{ .ref = item, .val = val.toIntern() };
 }
 
 fn validateSwitchRange(
@@ -11727,31 +11764,35 @@ fn validateSwitchRange(
     range_set: *RangeSet,
     first_ref: Zir.Inst.Ref,
     last_ref: Zir.Inst.Ref,
+    operand_ty: Type,
     src_node_offset: i32,
     switch_prong_src: Module.SwitchProngSrc,
-) CompileError!void {
+) CompileError![2]Air.Inst.Ref {
     const mod = sema.mod;
-    const first = try sema.resolveSwitchItemVal(block, first_ref, src_node_offset, switch_prong_src, .first);
-    const last = try sema.resolveSwitchItemVal(block, last_ref, src_node_offset, switch_prong_src, .last);
-    if (first.toValue().compareScalar(.gt, last.toValue(), mod.intern_pool.typeOf(first).toType(), mod)) {
+    const first = try sema.resolveSwitchItemVal(block, first_ref, operand_ty, src_node_offset, switch_prong_src, .first);
+    const last = try sema.resolveSwitchItemVal(block, last_ref, operand_ty, src_node_offset, switch_prong_src, .last);
+    if (try first.val.toValue().compareAll(.gt, last.val.toValue(), operand_ty, mod)) {
         const src = switch_prong_src.resolve(mod, mod.declPtr(block.src_decl), src_node_offset, .first);
         return sema.fail(block, src, "range start value is greater than the end value", .{});
     }
-    const maybe_prev_src = try range_set.add(first, last, switch_prong_src);
-    return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
+    const maybe_prev_src = try range_set.add(first.val, last.val, switch_prong_src);
+    try sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
+    return .{ first.ref, last.ref };
 }
 
-fn validateSwitchItem(
+fn validateSwitchItemInt(
     sema: *Sema,
     block: *Block,
     range_set: *RangeSet,
     item_ref: Zir.Inst.Ref,
+    operand_ty: Type,
     src_node_offset: i32,
     switch_prong_src: Module.SwitchProngSrc,
-) CompileError!void {
-    const item = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
-    const maybe_prev_src = try range_set.add(item, item, switch_prong_src);
-    return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
+) CompileError!Air.Inst.Ref {
+    const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, src_node_offset, switch_prong_src, .none);
+    const maybe_prev_src = try range_set.add(item.val, item.val, switch_prong_src);
+    try sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
+    return item.ref;
 }
 
 fn validateSwitchItemEnum(
@@ -11760,19 +11801,22 @@ fn validateSwitchItemEnum(
     seen_fields: []?Module.SwitchProngSrc,
     range_set: *RangeSet,
     item_ref: Zir.Inst.Ref,
+    operand_ty: Type,
     src_node_offset: i32,
     switch_prong_src: Module.SwitchProngSrc,
-) CompileError!void {
+) CompileError!Air.Inst.Ref {
     const ip = &sema.mod.intern_pool;
-    const item = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
-    const int = ip.indexToKey(item).enum_tag.int;
-    const field_index = ip.indexToKey(ip.typeOf(item)).enum_type.tagValueIndex(ip, int) orelse {
+    const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, src_node_offset, switch_prong_src, .none);
+    const int = ip.indexToKey(item.val).enum_tag.int;
+    const field_index = ip.indexToKey(ip.typeOf(item.val)).enum_type.tagValueIndex(ip, int) orelse {
         const maybe_prev_src = try range_set.add(int, int, switch_prong_src);
-        return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
+        try sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
+        return item.ref;
     };
     const maybe_prev_src = seen_fields[field_index];
     seen_fields[field_index] = switch_prong_src;
-    return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
+    try sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
+    return item.ref;
 }
 
 fn validateSwitchItemError(
@@ -11780,18 +11824,19 @@ fn validateSwitchItemError(
     block: *Block,
     seen_errors: *SwitchErrorSet,
     item_ref: Zir.Inst.Ref,
+    operand_ty: Type,
     src_node_offset: i32,
     switch_prong_src: Module.SwitchProngSrc,
-) CompileError!void {
+) CompileError!Air.Inst.Ref {
     const ip = &sema.mod.intern_pool;
-    const item = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
-    // TODO: Do i need to typecheck here?
-    const error_name = ip.indexToKey(item).err.name;
+    const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, src_node_offset, switch_prong_src, .none);
+    const error_name = ip.indexToKey(item.val).err.name;
     const maybe_prev_src = if (try seen_errors.fetchPut(error_name, switch_prong_src)) |prev|
         prev.value
     else
         null;
-    return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
+    try sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
+    return item.ref;
 }
 
 fn validateSwitchDupe(
@@ -11834,19 +11879,20 @@ fn validateSwitchItemBool(
     item_ref: Zir.Inst.Ref,
     src_node_offset: i32,
     switch_prong_src: Module.SwitchProngSrc,
-) CompileError!void {
+) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
-    const item = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
-    if (item.toValue().toBool()) {
+    const item = try sema.resolveSwitchItemVal(block, item_ref, Type.bool, src_node_offset, switch_prong_src, .none);
+    if (item.val.toValue().toBool()) {
         true_count.* += 1;
     } else {
         false_count.* += 1;
     }
-    if (true_count.* + false_count.* > 2) {
-        const block_src_decl = mod.declPtr(block.src_decl);
+    if (true_count.* > 1 or false_count.* > 1) {
+        const block_src_decl = sema.mod.declPtr(block.src_decl);
         const src = switch_prong_src.resolve(mod, block_src_decl, src_node_offset, .none);
         return sema.fail(block, src, "duplicate switch value", .{});
     }
+    return item.ref;
 }
 
 const ValueSrcMap = std.AutoHashMapUnmanaged(InternPool.Index, Module.SwitchProngSrc);
@@ -11856,12 +11902,14 @@ fn validateSwitchItemSparse(
     block: *Block,
     seen_values: *ValueSrcMap,
     item_ref: Zir.Inst.Ref,
+    operand_ty: Type,
     src_node_offset: i32,
     switch_prong_src: Module.SwitchProngSrc,
-) CompileError!void {
-    const item = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
-    const kv = (try seen_values.fetchPut(sema.gpa, item, switch_prong_src)) orelse return;
-    return sema.validateSwitchDupe(block, kv.value, switch_prong_src, src_node_offset);
+) CompileError!Air.Inst.Ref {
+    const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, src_node_offset, switch_prong_src, .none);
+    const kv = (try seen_values.fetchPut(sema.gpa, item.val, switch_prong_src)) orelse return item.ref;
+    try sema.validateSwitchDupe(block, kv.value, switch_prong_src, src_node_offset);
+    unreachable;
 }
 
 fn validateSwitchNoRange(