Commit ec27524da9

mlugg <mlugg@mlugg.co.uk>
2023-05-27 07:52:25
Sema: minor refactor to switch prong analysis
1 parent 00609e7
Changed files (1)
src/Sema.zig
@@ -10077,298 +10077,284 @@ fn zirSliceLength(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
     return sema.analyzeSlice(block, src, array_ptr, start, len, sentinel, sentinel_src, ptr_src, start_src, end_src, true);
 }
 
-/// Resolve a switch prong which is determined at comptime to have no peers. Uses
-/// `resolveBlockBody`. Sets up captures as needed.
-fn resolveSwitchProngComptime(
+/// Holds common data used when analyzing or resolving switch prong bodies,
+/// including setting up captures.
+const SwitchProngAnalysis = struct {
     sema: *Sema,
+    /// The block containing the `switch_block` itself.
     parent_block: *Block,
-    child_block: *Block,
-    src: LazySrcLoc,
+    /// The raw switch operand value (*not* the condition). Always defined.
     operand: Air.Inst.Ref,
+    /// May be `undefined` if no prong has a by-ref capture.
     operand_ptr: Air.Inst.Ref,
-    prong_type: enum { normal, special },
-    prong_body: []const Zir.Inst.Index,
-    capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
-    raw_capture_src: Module.SwitchProngSrc,
+    /// If this switch is on an error set, this is the type to assign to the
+    /// `else` prong. If `null`, the prong should be unreachable.
     else_error_ty: ?Type,
-    case_vals: []const Air.Inst.Ref,
+    /// The index of the `switch_block` instruction itself.
     switch_block_inst: Zir.Inst.Index,
-    merges: *Block.Merges,
-) CompileError!Air.Inst.Ref {
-    switch (capture) {
-        .none => {
-            return sema.resolveBlockBody(parent_block, src, child_block, prong_body, switch_block_inst, merges);
-        },
 
-        .by_val, .by_ref => {
-            const zir_datas = sema.code.instructions.items(.data);
-            const switch_info = zir_datas[switch_block_inst].pl_node;
-
-            const capture_ref = try sema.analyzeSwitchCapture(
-                child_block,
-                capture == .by_ref,
-                operand,
-                operand_ptr,
-                switch_info.src_node,
-                prong_type == .special,
-                raw_capture_src,
-                else_error_ty,
-                case_vals,
-            );
+    /// Resolve a switch prong which is determined at comptime to have no peers.
+    /// Uses `resolveBlockBody`. Sets up captures as needed.
+    fn resolveProngComptime(
+        spa: SwitchProngAnalysis,
+        child_block: *Block,
+        prong_type: enum { normal, special },
+        prong_body: []const Zir.Inst.Index,
+        capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
+        /// Must use the `scalar`, `special`, or `multi_capture` union field.
+        raw_capture_src: Module.SwitchProngSrc,
+        /// The set of all values which can reach this prong. May be undefined
+        /// if the prong is special or contains ranges.
+        case_vals: []const Air.Inst.Ref,
+        merges: *Block.Merges,
+    ) CompileError!Air.Inst.Ref {
+        const sema = spa.sema;
+        const src = sema.code.instructions.items(.data)[spa.switch_block_inst].pl_node.src();
+        switch (capture) {
+            .none => {
+                return sema.resolveBlockBody(spa.parent_block, src, child_block, prong_body, spa.switch_block_inst, merges);
+            },
+
+            .by_val, .by_ref => {
+                const capture_ref = try spa.analyzeCapture(
+                    child_block,
+                    capture == .by_ref,
+                    prong_type == .special,
+                    raw_capture_src,
+                    case_vals,
+                );
 
-            if (sema.typeOf(capture_ref).isNoReturn(sema.mod)) {
-                // This prong should be unreachable!
-                return Air.Inst.Ref.unreachable_value;
-            }
+                if (sema.typeOf(capture_ref).isNoReturn(sema.mod)) {
+                    // This prong should be unreachable!
+                    return Air.Inst.Ref.unreachable_value;
+                }
 
-            sema.inst_map.putAssumeCapacity(switch_block_inst, capture_ref);
-            defer assert(sema.inst_map.remove(switch_block_inst));
+                sema.inst_map.putAssumeCapacity(spa.switch_block_inst, capture_ref);
+                defer assert(sema.inst_map.remove(spa.switch_block_inst));
 
-            return sema.resolveBlockBody(parent_block, src, child_block, prong_body, switch_block_inst, merges);
-        },
+                return sema.resolveBlockBody(spa.parent_block, src, child_block, prong_body, spa.switch_block_inst, merges);
+            },
+        }
     }
-}
-
-/// Analyze a switch prong which may have peers at runtime. Uses
-/// `analyzeBodyRuntimeBreak`. Sets up captures as needed.
-fn analyzeSwitchProngRuntime(
-    sema: *Sema,
-    case_block: *Block,
-    operand: Air.Inst.Ref,
-    operand_ptr: Air.Inst.Ref,
-    prong_type: enum { normal, special },
-    prong_body: []const Zir.Inst.Index,
-    capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
-    raw_capture_src: Module.SwitchProngSrc,
-    else_error_ty: ?Type,
-    case_vals: []const Air.Inst.Ref,
-    switch_block_inst: Zir.Inst.Index,
-) CompileError!void {
-    switch (capture) {
-        .none => {
-            return sema.analyzeBodyRuntimeBreak(case_block, prong_body);
-        },
 
-        .by_val, .by_ref => {
-            const zir_datas = sema.code.instructions.items(.data);
-            const switch_info = zir_datas[switch_block_inst].pl_node;
-
-            const capture_ref = try sema.analyzeSwitchCapture(
-                case_block,
-                capture == .by_ref,
-                operand,
-                operand_ptr,
-                switch_info.src_node,
-                prong_type == .special,
-                raw_capture_src,
-                else_error_ty,
-                case_vals,
-            );
+    /// Analyze a switch prong which may have peers at runtime.
+    /// Uses `analyzeBodyRuntimeBreak`. Sets up captures as needed.
+    fn analyzeProngRuntime(
+        spa: SwitchProngAnalysis,
+        case_block: *Block,
+        prong_type: enum { normal, special },
+        prong_body: []const Zir.Inst.Index,
+        capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
+        /// Must use the `scalar`, `special`, or `multi_capture` union field.
+        raw_capture_src: Module.SwitchProngSrc,
+        /// The set of all values which can reach this prong. May be undefined
+        /// if the prong is special or contains ranges.
+        case_vals: []const Air.Inst.Ref,
+    ) CompileError!void {
+        const sema = spa.sema;
+        switch (capture) {
+            .none => {
+                return sema.analyzeBodyRuntimeBreak(case_block, prong_body);
+            },
+
+            .by_val, .by_ref => {
+                const capture_ref = try spa.analyzeCapture(
+                    case_block,
+                    capture == .by_ref,
+                    prong_type == .special,
+                    raw_capture_src,
+                    case_vals,
+                );
 
-            if (sema.typeOf(capture_ref).isNoReturn(sema.mod)) {
-                // No need to analyze any further, the prong is unreachable
-                return;
-            }
+                if (sema.typeOf(capture_ref).isNoReturn(sema.mod)) {
+                    // No need to analyze any further, the prong is unreachable
+                    return;
+                }
 
-            sema.inst_map.putAssumeCapacity(switch_block_inst, capture_ref);
-            defer assert(sema.inst_map.remove(switch_block_inst));
+                sema.inst_map.putAssumeCapacity(spa.switch_block_inst, capture_ref);
+                defer assert(sema.inst_map.remove(spa.switch_block_inst));
 
-            return sema.analyzeBodyRuntimeBreak(case_block, prong_body);
-        },
+                return sema.analyzeBodyRuntimeBreak(case_block, prong_body);
+            },
+        }
     }
-}
 
-fn analyzeSwitchCapture(
-    sema: *Sema,
-    /// Must be the child block so that `inline_case_capture` is set for inline prongs.
-    block: *Block,
-    capture_byref: bool,
-    /// The raw switch operand value.
-    operand: Air.Inst.Ref,
-    /// Pointer to the raw switch operand. May be undefined if `capture_byref` is false.
-    operand_ptr: Air.Inst.Ref,
-    switch_node_offset: i32,
-    /// `true` if this is the `else` or `_` prong of a switch.
-    is_special_prong: bool,
-    /// Must use the `scalar`, `special`, or `multi_capture` union field.
-    raw_capture_src: Module.SwitchProngSrc,
-    /// If this is the `else` prong of a switch on an error set, this is the
-    /// type that should be assigned to the capture. If `null`, the prong should
-    /// be unreachable.
-    else_error_ty: ?Type,
-    /// The set of all values which can reach this prong. May be undefined if
-    /// the prong has `is_special_prong` or contains ranges.
-    case_vals: []const Air.Inst.Ref,
-) CompileError!Air.Inst.Ref {
-    const mod = sema.mod;
-    const gpa = sema.gpa;
-    const operand_ty = sema.typeOf(operand);
-    const operand_ptr_ty = if (capture_byref) sema.typeOf(operand_ptr) else undefined;
-    const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = switch_node_offset };
-
-    if (block.inline_case_capture != .none) {
-        const item_val = sema.resolveConstValue(block, .unneeded, block.inline_case_capture, "") catch unreachable;
-        if (operand_ty.zigTypeTag(mod) == .Union) {
-            const field_index = @intCast(u32, operand_ty.unionTagFieldIndex(item_val, mod).?);
-            const union_obj = mod.typeToUnion(operand_ty).?;
-            const field_ty = union_obj.fields.values()[field_index].ty;
-            if (capture_byref) {
-                if (try sema.resolveDefinedValue(block, sema.src, operand_ptr)) |union_ptr| {
+    fn analyzeCapture(
+        spa: SwitchProngAnalysis,
+        block: *Block,
+        capture_byref: bool,
+        is_special_prong: bool,
+        raw_capture_src: Module.SwitchProngSrc,
+        case_vals: []const Air.Inst.Ref,
+    ) CompileError!Air.Inst.Ref {
+        const sema = spa.sema;
+        const mod = sema.mod;
+
+        const zir_datas = sema.code.instructions.items(.data);
+        const switch_node_offset = zir_datas[spa.switch_block_inst].pl_node.src_node;
+
+        const operand_ty = sema.typeOf(spa.operand);
+        const operand_ptr_ty = if (capture_byref) sema.typeOf(spa.operand_ptr) else undefined;
+        const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = switch_node_offset };
+
+        if (block.inline_case_capture != .none) {
+            const item_val = sema.resolveConstValue(block, .unneeded, block.inline_case_capture, "") catch unreachable;
+            if (operand_ty.zigTypeTag(mod) == .Union) {
+                const field_index = @intCast(u32, operand_ty.unionTagFieldIndex(item_val, mod).?);
+                const union_obj = mod.typeToUnion(operand_ty).?;
+                const field_ty = union_obj.fields.values()[field_index].ty;
+                if (capture_byref) {
                     const ptr_field_ty = try Type.ptr(sema.arena, mod, .{
                         .pointee_type = field_ty,
                         .mutable = operand_ptr_ty.ptrIsMutable(mod),
                         .@"volatile" = operand_ptr_ty.isVolatilePtr(mod),
                         .@"addrspace" = operand_ptr_ty.ptrAddressSpace(mod),
                     });
-                    return sema.addConstant(
-                        ptr_field_ty,
-                        (try mod.intern(.{ .ptr = .{
-                            .ty = ptr_field_ty.toIntern(),
-                            .addr = .{ .field = .{
-                                .base = union_ptr.toIntern(),
-                                .index = field_index,
-                            } },
-                        } })).toValue(),
-                    );
+                    if (try sema.resolveDefinedValue(block, sema.src, spa.operand_ptr)) |union_ptr| {
+                        return sema.addConstant(
+                            ptr_field_ty,
+                            (try mod.intern(.{ .ptr = .{
+                                .ty = ptr_field_ty.toIntern(),
+                                .addr = .{ .field = .{
+                                    .base = union_ptr.toIntern(),
+                                    .index = field_index,
+                                } },
+                            } })).toValue(),
+                        );
+                    }
+                    return block.addStructFieldPtr(spa.operand_ptr, field_index, ptr_field_ty);
+                } else {
+                    if (try sema.resolveDefinedValue(block, sema.src, spa.operand)) |union_val| {
+                        const tag_and_val = mod.intern_pool.indexToKey(union_val.toIntern()).un;
+                        return sema.addConstant(field_ty, tag_and_val.val.toValue());
+                    }
+                    return block.addStructFieldVal(spa.operand, field_index, field_ty);
                 }
-                const ptr_field_ty = try Type.ptr(sema.arena, mod, .{
-                    .pointee_type = field_ty,
-                    .mutable = operand_ptr_ty.ptrIsMutable(mod),
-                    .@"volatile" = operand_ptr_ty.isVolatilePtr(mod),
-                    .@"addrspace" = operand_ptr_ty.ptrAddressSpace(mod),
-                });
-                return block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);
+            } else if (capture_byref) {
+                return sema.addConstantMaybeRef(block, operand_ty, item_val, true);
             } else {
-                if (try sema.resolveDefinedValue(block, sema.src, operand)) |union_val| {
-                    const tag_and_val = mod.intern_pool.indexToKey(union_val.toIntern()).un;
-                    return sema.addConstant(field_ty, tag_and_val.val.toValue());
-                }
-                return block.addStructFieldVal(operand, field_index, field_ty);
+                return block.inline_case_capture;
             }
-        } else if (capture_byref) {
-            return sema.addConstantMaybeRef(block, operand_ty, item_val, true);
-        } else {
-            return block.inline_case_capture;
         }
-    }
 
-    if (is_special_prong) {
-        if (capture_byref) {
-            return operand_ptr;
-        }
+        if (is_special_prong) {
+            if (capture_byref) {
+                return spa.operand_ptr;
+            }
 
-        switch (operand_ty.zigTypeTag(mod)) {
-            .ErrorSet => if (else_error_ty) |ty| {
-                return sema.bitCast(block, ty, operand, operand_src, null);
-            } else {
-                try block.addUnreachable(false);
-                return Air.Inst.Ref.unreachable_value;
-            },
-            else => return operand,
+            switch (operand_ty.zigTypeTag(mod)) {
+                .ErrorSet => if (spa.else_error_ty) |ty| {
+                    return sema.bitCast(block, ty, spa.operand, operand_src, null);
+                } else {
+                    try block.addUnreachable(false);
+                    return Air.Inst.Ref.unreachable_value;
+                },
+                else => return spa.operand,
+            }
         }
-    }
 
-    switch (operand_ty.zigTypeTag(mod)) {
-        .Union => {
-            const union_obj = mod.typeToUnion(operand_ty).?;
-            const first_item_val = sema.resolveConstValue(block, .unneeded, case_vals[0], "") catch unreachable;
+        switch (operand_ty.zigTypeTag(mod)) {
+            .Union => {
+                const union_obj = mod.typeToUnion(operand_ty).?;
+                const first_item_val = sema.resolveConstValue(block, .unneeded, case_vals[0], "") 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];
+                const first_field_index = @intCast(u32, operand_ty.unionTagFieldIndex(first_item_val, mod).?);
+                const first_field = union_obj.fields.values()[first_field_index];
 
-            for (case_vals[1..], 0..) |item, i| {
-                const item_val = sema.resolveConstValue(block, .unneeded, item, "") catch unreachable;
+                for (case_vals[1..], 0..) |item, i| {
+                    const item_val = sema.resolveConstValue(block, .unneeded, item, "") catch unreachable;
 
-                const field_index = operand_ty.unionTagFieldIndex(item_val, mod).?;
-                const field = union_obj.fields.values()[field_index];
-                if (!field.ty.eql(first_field.ty, mod)) {
-                    const msg = msg: {
-                        const capture_src = raw_capture_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .none);
+                    const field_index = operand_ty.unionTagFieldIndex(item_val, mod).?;
+                    const field = union_obj.fields.values()[field_index];
+                    if (!field.ty.eql(first_field.ty, mod)) {
+                        const msg = msg: {
+                            const capture_src = raw_capture_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .none);
 
-                        const msg = try sema.errMsg(block, capture_src, "capture group with incompatible types", .{});
-                        errdefer msg.destroy(gpa);
+                            const msg = try sema.errMsg(block, capture_src, "capture group with incompatible types", .{});
+                            errdefer msg.destroy(sema.gpa);
 
-                        // This must be a multi-prong so this must be a `multi_capture` src
-                        const multi_idx = raw_capture_src.multi_capture;
+                            // This must be a multi-prong so this must be a `multi_capture` src
+                            const multi_idx = raw_capture_src.multi_capture;
 
-                        const raw_first_item_src = Module.SwitchProngSrc{ .multi = .{ .prong = multi_idx, .item = 0 } };
-                        const first_item_src = raw_first_item_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .first);
-                        const raw_item_src = Module.SwitchProngSrc{ .multi = .{ .prong = multi_idx, .item = 1 + @intCast(u32, i) } };
-                        const item_src = raw_item_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .first);
-                        try sema.errNote(block, first_item_src, msg, "type '{}' here", .{first_field.ty.fmt(mod)});
-                        try sema.errNote(block, item_src, msg, "type '{}' here", .{field.ty.fmt(mod)});
-                        break :msg msg;
-                    };
-                    return sema.failWithOwnedErrorMsg(msg);
+                            const raw_first_item_src = Module.SwitchProngSrc{ .multi = .{ .prong = multi_idx, .item = 0 } };
+                            const first_item_src = raw_first_item_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .first);
+                            const raw_item_src = Module.SwitchProngSrc{ .multi = .{ .prong = multi_idx, .item = 1 + @intCast(u32, i) } };
+                            const item_src = raw_item_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .first);
+                            try sema.errNote(block, first_item_src, msg, "type '{}' here", .{first_field.ty.fmt(mod)});
+                            try sema.errNote(block, item_src, msg, "type '{}' here", .{field.ty.fmt(mod)});
+                            break :msg msg;
+                        };
+                        return sema.failWithOwnedErrorMsg(msg);
+                    }
                 }
-            }
 
-            if (capture_byref) {
-                const field_ty_ptr = try Type.ptr(sema.arena, mod, .{
-                    .pointee_type = first_field.ty,
-                    .@"addrspace" = .generic,
-                    .mutable = operand_ptr_ty.ptrIsMutable(mod),
-                });
+                if (capture_byref) {
+                    const field_ty_ptr = try Type.ptr(sema.arena, mod, .{
+                        .pointee_type = first_field.ty,
+                        .@"addrspace" = .generic,
+                        .mutable = operand_ptr_ty.ptrIsMutable(mod),
+                    });
 
-                if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |op_ptr_val| {
-                    return sema.addConstant(field_ty_ptr, (try mod.intern(.{ .ptr = .{
-                        .ty = field_ty_ptr.toIntern(),
-                        .addr = .{ .field = .{
-                            .base = op_ptr_val.toIntern(),
-                            .index = first_field_index,
-                        } },
-                    } })).toValue());
+                    if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |op_ptr_val| {
+                        return sema.addConstant(field_ty_ptr, (try mod.intern(.{ .ptr = .{
+                            .ty = field_ty_ptr.toIntern(),
+                            .addr = .{ .field = .{
+                                .base = op_ptr_val.toIntern(),
+                                .index = first_field_index,
+                            } },
+                        } })).toValue());
+                    }
+                    try sema.requireRuntimeBlock(block, operand_src, null);
+                    return block.addStructFieldPtr(spa.operand_ptr, first_field_index, field_ty_ptr);
                 }
-                try sema.requireRuntimeBlock(block, operand_src, null);
-                return block.addStructFieldPtr(operand_ptr, first_field_index, field_ty_ptr);
-            }
 
-            if (try sema.resolveDefinedValue(block, operand_src, operand)) |operand_val| {
-                return sema.addConstant(
-                    first_field.ty,
-                    mod.intern_pool.indexToKey(operand_val.toIntern()).un.val.toValue(),
-                );
-            }
-            try sema.requireRuntimeBlock(block, operand_src, null);
-            return block.addStructFieldVal(operand, first_field_index, first_field.ty);
-        },
-        .ErrorSet => {
-            if (capture_byref) {
-                const capture_src = raw_capture_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .none);
-                return sema.fail(
-                    block,
-                    capture_src,
-                    "error set cannot be captured by reference",
-                    .{},
-                );
-            }
+                if (try sema.resolveDefinedValue(block, operand_src, spa.operand)) |operand_val| {
+                    return sema.addConstant(
+                        first_field.ty,
+                        mod.intern_pool.indexToKey(operand_val.toIntern()).un.val.toValue(),
+                    );
+                }
+                try sema.requireRuntimeBlock(block, operand_src, null);
+                return block.addStructFieldVal(spa.operand, first_field_index, first_field.ty);
+            },
+            .ErrorSet => {
+                if (capture_byref) {
+                    const capture_src = raw_capture_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .none);
+                    return sema.fail(
+                        block,
+                        capture_src,
+                        "error set cannot be captured by reference",
+                        .{},
+                    );
+                }
 
-            if (case_vals.len == 1) {
-                const item_val = sema.resolveConstValue(block, .unneeded, case_vals[0], "") catch unreachable;
-                const item_ty = try mod.singleErrorSetType(item_val.getErrorName(mod).unwrap().?);
-                return sema.bitCast(block, item_ty, operand, operand_src, null);
-            }
+                if (case_vals.len == 1) {
+                    const item_val = sema.resolveConstValue(block, .unneeded, case_vals[0], "") catch unreachable;
+                    const item_ty = try mod.singleErrorSetType(item_val.getErrorName(mod).unwrap().?);
+                    return sema.bitCast(block, item_ty, spa.operand, operand_src, null);
+                }
 
-            var names: Module.Fn.InferredErrorSet.NameMap = .{};
-            try names.ensureUnusedCapacity(sema.arena, case_vals.len);
-            for (case_vals) |err| {
-                const err_val = sema.resolveConstValue(block, .unneeded, err, "") catch unreachable;
-                names.putAssumeCapacityNoClobber(err_val.getErrorName(mod).unwrap().?, {});
-            }
-            const error_ty = try mod.errorSetFromUnsortedNames(names.keys());
-            return sema.bitCast(block, error_ty, operand, operand_src, null);
-        },
-        else => {
-            // In this case the capture value is just the passed-through value
-            // of the switch condition.
-            if (capture_byref) {
-                return operand_ptr;
-            } else {
-                return operand;
-            }
-        },
+                var names: Module.Fn.InferredErrorSet.NameMap = .{};
+                try names.ensureUnusedCapacity(sema.arena, case_vals.len);
+                for (case_vals) |err| {
+                    const err_val = sema.resolveConstValue(block, .unneeded, err, "") catch unreachable;
+                    names.putAssumeCapacityNoClobber(err_val.getErrorName(mod).unwrap().?, {});
+                }
+                const error_ty = try mod.errorSetFromUnsortedNames(names.keys());
+                return sema.bitCast(block, error_ty, spa.operand, operand_src, null);
+            },
+            else => {
+                // In this case the capture value is just the passed-through value
+                // of the switch condition.
+                if (capture_byref) {
+                    return spa.operand_ptr;
+                } else {
+                    return spa.operand;
+                }
+            },
+        }
     }
-}
+};
 
 fn zirSwitchCaptureTag(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
@@ -11075,6 +11061,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
         }),
     }
 
+    const spa: SwitchProngAnalysis = .{
+        .sema = sema,
+        .parent_block = block,
+        .operand = raw_operand.val,
+        .operand_ptr = raw_operand.ptr,
+        .else_error_ty = else_error_ty,
+        .switch_block_inst = inst,
+    };
+
     const block_inst = @intCast(Air.Inst.Index, sema.air_instructions.len);
     try sema.air_instructions.append(gpa, .{
         .tag = .block,
@@ -11129,19 +11124,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                 if (operand_val.eql(item_val, operand_ty, sema.mod)) {
                     if (info.is_inline) child_block.inline_case_capture = operand;
                     if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand);
-                    return sema.resolveSwitchProngComptime(
-                        block,
+                    return spa.resolveProngComptime(
                         &child_block,
-                        src,
-                        raw_operand.val,
-                        raw_operand.ptr,
                         .normal,
                         body,
                         info.capture,
                         .{ .scalar = @intCast(u32, scalar_i) },
-                        else_error_ty,
                         &.{item},
-                        inst,
                         merges,
                     );
                 }
@@ -11168,19 +11157,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     if (operand_val.eql(item_val, operand_ty, sema.mod)) {
                         if (info.is_inline) child_block.inline_case_capture = operand;
                         if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand);
-                        return sema.resolveSwitchProngComptime(
-                            block,
+                        return spa.resolveProngComptime(
                             &child_block,
-                            src,
-                            raw_operand.val,
-                            raw_operand.ptr,
                             .normal,
                             body,
                             info.capture,
                             .{ .multi_capture = @intCast(u32, multi_i) },
-                            else_error_ty,
                             items,
-                            inst,
                             merges,
                         );
                     }
@@ -11200,19 +11183,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     {
                         if (info.is_inline) child_block.inline_case_capture = operand;
                         if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand);
-                        return sema.resolveSwitchProngComptime(
-                            block,
+                        return spa.resolveProngComptime(
                             &child_block,
-                            src,
-                            raw_operand.val,
-                            raw_operand.ptr,
                             .normal,
                             body,
                             info.capture,
                             .{ .multi_capture = @intCast(u32, multi_i) },
-                            else_error_ty,
-                            undefined,
-                            inst,
+                            undefined, // case_vals may be undefined for ranges
                             merges,
                         );
                     }
@@ -11227,19 +11204,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
             return Air.Inst.Ref.void_value;
         }
 
-        return sema.resolveSwitchProngComptime(
-            block,
+        return spa.resolveProngComptime(
             &child_block,
-            src,
-            raw_operand.val,
-            raw_operand.ptr,
             .special,
             special.body,
             special.capture,
             .special,
-            else_error_ty,
-            undefined,
-            inst,
+            undefined, // case_vals may be undefined for special prongs
             merges,
         );
     }
@@ -11262,19 +11233,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
             try sema.addSafetyCheck(block, ok, .corrupt_switch);
         }
 
-        return sema.resolveSwitchProngComptime(
-            block,
+        return spa.resolveProngComptime(
             &child_block,
-            src,
-            raw_operand.val,
-            raw_operand.ptr,
             .special,
             special.body,
             special.capture,
             .special,
-            else_error_ty,
-            undefined,
-            inst,
+            undefined, // case_vals may be undefined for special prongs
             merges,
         );
     }
@@ -11328,17 +11293,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
         if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) {
             // nothing to do here
         } else if (analyze_body) {
-            try sema.analyzeSwitchProngRuntime(
+            try spa.analyzeProngRuntime(
                 &case_block,
-                raw_operand.val,
-                raw_operand.ptr,
                 .normal,
                 body,
                 info.capture,
                 .{ .scalar = @intCast(u32, scalar_i) },
-                else_error_ty,
                 &.{item},
-                inst,
             );
         } else {
             _ = try case_block.addNoOp(.unreach);
@@ -11422,17 +11383,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     };
                     emit_bb = true;
 
-                    try sema.analyzeSwitchProngRuntime(
+                    try spa.analyzeProngRuntime(
                         &case_block,
-                        raw_operand.val,
-                        raw_operand.ptr,
                         .normal,
                         body,
                         info.capture,
                         .{ .multi_capture = multi_i },
-                        else_error_ty,
-                        undefined,
-                        inst,
+                        undefined, // case_vals may be undefined for ranges
                     );
 
                     try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
@@ -11469,17 +11426,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                 emit_bb = true;
 
                 if (analyze_body) {
-                    try sema.analyzeSwitchProngRuntime(
+                    try spa.analyzeProngRuntime(
                         &case_block,
-                        raw_operand.val,
-                        raw_operand.ptr,
                         .normal,
                         body,
                         info.capture,
                         .{ .multi_capture = multi_i },
-                        else_error_ty,
                         &.{item},
-                        inst,
                     );
                 } else {
                     _ = try case_block.addNoOp(.unreach);
@@ -11518,17 +11471,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
             if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) {
                 // nothing to do here
             } else if (analyze_body) {
-                try sema.analyzeSwitchProngRuntime(
+                try spa.analyzeProngRuntime(
                     &case_block,
-                    raw_operand.val,
-                    raw_operand.ptr,
                     .normal,
                     body,
                     info.capture,
                     .{ .multi_capture = multi_i },
-                    else_error_ty,
                     items,
-                    inst,
                 );
             } else {
                 _ = try case_block.addNoOp(.unreach);
@@ -11607,17 +11556,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
             if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) {
                 // nothing to do here
             } else {
-                try sema.analyzeSwitchProngRuntime(
+                try spa.analyzeProngRuntime(
                     &case_block,
-                    raw_operand.val,
-                    raw_operand.ptr,
                     .normal,
                     body,
                     info.capture,
                     .{ .multi_capture = multi_i },
-                    else_error_ty,
                     items,
-                    inst,
                 );
             }
 
@@ -11677,17 +11622,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     emit_bb = true;
 
                     if (analyze_body) {
-                        try sema.analyzeSwitchProngRuntime(
+                        try spa.analyzeProngRuntime(
                             &case_block,
-                            raw_operand.val,
-                            raw_operand.ptr,
                             .special,
                             special.body,
                             special.capture,
                             .special,
-                            else_error_ty,
                             &.{item_ref},
-                            inst,
                         );
                     } else {
                         _ = try case_block.addNoOp(.unreach);
@@ -11724,17 +11665,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src);
                     emit_bb = true;
 
-                    try sema.analyzeSwitchProngRuntime(
+                    try spa.analyzeProngRuntime(
                         &case_block,
-                        raw_operand.val,
-                        raw_operand.ptr,
                         .special,
                         special.body,
                         special.capture,
                         .special,
-                        else_error_ty,
                         &.{item_ref},
-                        inst,
                     );
 
                     try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
@@ -11758,17 +11695,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src);
                     emit_bb = true;
 
-                    try sema.analyzeSwitchProngRuntime(
+                    try spa.analyzeProngRuntime(
                         &case_block,
-                        raw_operand.val,
-                        raw_operand.ptr,
                         .special,
                         special.body,
                         special.capture,
                         .special,
-                        else_error_ty,
                         &.{item_ref},
-                        inst,
                     );
 
                     try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
@@ -11789,17 +11722,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src);
                     emit_bb = true;
 
-                    try sema.analyzeSwitchProngRuntime(
+                    try spa.analyzeProngRuntime(
                         &case_block,
-                        raw_operand.val,
-                        raw_operand.ptr,
                         .special,
                         special.body,
                         special.capture,
                         .special,
-                        else_error_ty,
                         &.{Air.Inst.Ref.bool_true},
-                        inst,
                     );
 
                     try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
@@ -11818,17 +11747,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src);
                     emit_bb = true;
 
-                    try sema.analyzeSwitchProngRuntime(
+                    try spa.analyzeProngRuntime(
                         &case_block,
-                        raw_operand.val,
-                        raw_operand.ptr,
                         .special,
                         special.body,
                         special.capture,
                         .special,
-                        else_error_ty,
                         &.{Air.Inst.Ref.bool_false},
-                        inst,
                     );
 
                     try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
@@ -11872,17 +11797,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
         {
             // nothing to do here
         } else if (special.body.len != 0 and analyze_body and !special.is_inline) {
-            try sema.analyzeSwitchProngRuntime(
+            try spa.analyzeProngRuntime(
                 &case_block,
-                raw_operand.val,
-                raw_operand.ptr,
                 .special,
                 special.body,
                 special.capture,
                 .special,
-                else_error_ty,
-                undefined,
-                inst,
+                undefined, // case_vals may be undefined for special prongs
             );
         } else {
             // We still need a terminator in this block, but we have proven