Commit 195ddab2be

Andrew Kelley <andrew@ziglang.org>
2021-03-30 06:59:08
Sema: implement switch expressions
The logic for putting ranges into the else prong is moved from AstGen to Sema. However, logic to emit multi-items the same as single-items cannot be done until TZIR supports mapping multiple items to the same block of code. This will be simple to represent when we do the upcoming TZIR memory layout changes. Not yet implemented in this commit is the validation of duplicate values. The trick is going to be emitting error messages with accurate source locations, without adding extra source nodes to the ZIR switch instruction. This will be done by computing the respective AST node based on the switch node (which we do have available), only when a compile error occurs and we need to know the source location to attach the message to.
1 parent 623d5f4
src/AstGen.zig
@@ -1258,17 +1258,17 @@ fn blockExprStmts(
                         .condbr,
                         .condbr_inline,
                         .switch_br,
-                        .switch_br_range,
+                        .switch_br_multi,
                         .switch_br_else,
-                        .switch_br_else_range,
-                        .switch_br_underscore,
-                        .switch_br_underscore_range,
+                        .switch_br_else_multi,
+                        .switch_br_under,
+                        .switch_br_under_multi,
                         .switch_br_ref,
-                        .switch_br_ref_range,
+                        .switch_br_ref_multi,
                         .switch_br_ref_else,
-                        .switch_br_ref_else_range,
-                        .switch_br_ref_underscore,
-                        .switch_br_ref_underscore_range,
+                        .switch_br_ref_else_multi,
+                        .switch_br_ref_under,
+                        .switch_br_ref_under_multi,
                         .compile_error,
                         .ret_node,
                         .ret_tok,
@@ -2550,35 +2550,12 @@ fn switchExpr(
 ) InnerError!zir.Inst.Ref {
     const tree = parent_gz.tree();
     const node_datas = tree.nodes.items(.data);
-    const main_tokens = tree.nodes.items(.main_token);
-    const token_tags = tree.tokens.items(.tag);
     const node_tags = tree.nodes.items(.tag);
-
-    if (true) @panic("TODO rework for zir-memory-layout branch");
-
-    const switch_token = main_tokens[switch_node];
-    const target_node = node_datas[switch_node].lhs;
+    const token_tags = tree.tokens.items(.tag);
+    const operand_node = node_datas[switch_node].lhs;
     const extra = tree.extraData(node_datas[switch_node].rhs, ast.Node.SubRange);
     const case_nodes = tree.extra_data[extra.start..extra.end];
 
-    var block_scope: GenZir = .{
-        .parent = scope,
-        .decl = scope.ownerDecl().?,
-        .arena = parent_gz.astgen.arena,
-        .force_comptime = parent_gz.force_comptime,
-        .instructions = .{},
-    };
-    block_scope.setBreakResultLoc(rl);
-    defer block_scope.instructions.deinit(mod.gpa);
-
-    var items = std.ArrayList(zir.Inst.Ref).init(mod.gpa);
-    defer items.deinit();
-
-    // First we gather all the switch items and check else/'_' prongs.
-    var else_src: ?usize = null;
-    var underscore_src: ?usize = null;
-    var first_range: ?*zir.Inst = null;
-    var simple_case_count: usize = 0;
     var any_payload_is_ref = false;
     for (case_nodes) |case_node| {
         const case = switch (node_tags[case_node]) {
@@ -2591,284 +2568,22 @@ fn switchExpr(
                 any_payload_is_ref = true;
             }
         }
-        // Check for else/_ prong, those are handled last.
-        if (case.ast.values.len == 0) {
-            const case_src = token_starts[case.ast.arrow_token - 1];
-            if (else_src) |src| {
-                const msg = msg: {
-                    const msg = try mod.errMsg(
-                        scope,
-                        case_src,
-                        "multiple else prongs in switch expression",
-                        .{},
-                    );
-                    errdefer msg.destroy(mod.gpa);
-                    try mod.errNote(scope, src, msg, "previous else prong is here", .{});
-                    break :msg msg;
-                };
-                return mod.failWithOwnedErrorMsg(scope, msg);
-            }
-            else_src = case_src;
-            continue;
-        } else if (case.ast.values.len == 1 and
-            node_tags[case.ast.values[0]] == .identifier and
-            mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"))
-        {
-            const case_src = token_starts[case.ast.arrow_token - 1];
-            if (underscore_src) |src| {
-                const msg = msg: {
-                    const msg = try mod.errMsg(
-                        scope,
-                        case_src,
-                        "multiple '_' prongs in switch expression",
-                        .{},
-                    );
-                    errdefer msg.destroy(mod.gpa);
-                    try mod.errNote(scope, src, msg, "previous '_' prong is here", .{});
-                    break :msg msg;
-                };
-                return mod.failWithOwnedErrorMsg(scope, msg);
-            }
-            underscore_src = case_src;
-            continue;
-        }
-
-        if (else_src) |some_else| {
-            if (underscore_src) |some_underscore| {
-                const msg = msg: {
-                    const msg = try mod.errMsg(
-                        scope,
-                        parent_gz.nodeSrcLoc(switch_node),
-                        "else and '_' prong in switch expression",
-                        .{},
-                    );
-                    errdefer msg.destroy(mod.gpa);
-                    try mod.errNote(scope, some_else, msg, "else prong is here", .{});
-                    try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{});
-                    break :msg msg;
-                };
-                return mod.failWithOwnedErrorMsg(scope, msg);
-            }
-        }
-
-        if (case.ast.values.len == 1 and
-            getRangeNode(node_tags, node_datas, case.ast.values[0]) == null)
-        {
-            simple_case_count += 1;
-        }
-
-        // Generate all the switch items as comptime expressions.
-        for (case.ast.values) |item| {
-            if (getRangeNode(node_tags, node_datas, item)) |range| {
-                const start = try comptimeExpr(&block_scope, &block_scope.base, .none, node_datas[range].lhs);
-                const end = try comptimeExpr(&block_scope, &block_scope.base, .none, node_datas[range].rhs);
-                const range_src = token_starts[main_tokens[range]];
-                const range_inst = try addZIRBinOp(mod, &block_scope.base, range_src, .switch_range, start, end);
-                try items.append(range_inst);
-            } else {
-                const item_inst = try comptimeExpr(&block_scope, &block_scope.base, .none, item);
-                try items.append(item_inst);
-            }
-        }
     }
 
-    var special_prong: zir.Inst.SwitchBr.SpecialProng = .none;
-    if (else_src != null) special_prong = .@"else";
-    if (underscore_src != null) special_prong = .underscore;
-    var cases = try block_scope.arena.alloc(zir.Inst.SwitchBr.Case, simple_case_count);
-
     const rl_and_tag: struct { rl: ResultLoc, tag: zir.Inst.Tag } = if (any_payload_is_ref) .{
         .rl = .ref,
-        .tag = .switchbr_ref,
+        .tag = .switch_br_ref,
     } else .{
         .rl = .none,
-        .tag = .switchbr,
-    };
-    const target = try expr(&block_scope, &block_scope.base, rl_and_tag.rl, target_node);
-    const switch_inst = try addZirInstT(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, rl_and_tag.tag, .{
-        .target = target,
-        .cases = cases,
-        .items = try block_scope.arena.dupe(zir.Inst.Ref, items.items),
-        .else_body = undefined, // populated below
-        .range = first_range,
-        .special_prong = special_prong,
-    });
-    const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{
-        .instructions = try block_scope.arena.dupe(zir.Inst.Ref, block_scope.instructions.items),
-    });
-
-    var case_scope: GenZir = .{
-        .parent = scope,
-        .decl = block_scope.decl,
-        .arena = block_scope.arena,
-        .force_comptime = block_scope.force_comptime,
-        .instructions = .{},
+        .tag = .switch_br,
     };
-    defer case_scope.instructions.deinit(mod.gpa);
+    const operand = try expr(parent_gz, scope, rl_and_tag.rl, operand_node);
 
-    var else_scope: GenZir = .{
-        .parent = scope,
-        .decl = case_scope.decl,
-        .arena = case_scope.arena,
-        .force_comptime = case_scope.force_comptime,
-        .instructions = .{},
-    };
-    defer else_scope.instructions.deinit(mod.gpa);
-
-    // Now generate all but the special cases.
-    var special_case: ?ast.full.SwitchCase = null;
-    var items_index: usize = 0;
-    var case_index: usize = 0;
-    for (case_nodes) |case_node| {
-        const case = switch (node_tags[case_node]) {
-            .switch_case_one => tree.switchCaseOne(case_node),
-            .switch_case => tree.switchCase(case_node),
-            else => unreachable,
-        };
-        const case_src = token_starts[main_tokens[case_node]];
-        case_scope.instructions.shrinkRetainingCapacity(0);
-
-        // Check for else/_ prong, those are handled last.
-        if (case.ast.values.len == 0) {
-            special_case = case;
-            continue;
-        } else if (case.ast.values.len == 1 and
-            node_tags[case.ast.values[0]] == .identifier and
-            mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"))
-        {
-            special_case = case;
-            continue;
-        }
-
-        // If this is a simple one item prong then it is handled by the switchbr.
-        if (case.ast.values.len == 1 and
-            getRangeNode(node_tags, node_datas, case.ast.values[0]) == null)
-        {
-            const item = items.items[items_index];
-            items_index += 1;
-            try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target);
-
-            cases[case_index] = .{
-                .item = item,
-                .body = .{ .instructions = try parent_gz.astgen.arena.dupe(zir.Inst.Ref, case_scope.instructions.items) },
-            };
-            case_index += 1;
-            continue;
-        }
-
-        // Check if the target matches any of the items.
-        // 1, 2, 3..6 will result in
-        // target == 1 or target == 2 or (target >= 3 and target <= 6)
-        // TODO handle multiple items as switch prongs rather than along with ranges.
-        var any_ok: ?*zir.Inst = null;
-        for (case.ast.values) |item| {
-            if (getRangeNode(node_tags, node_datas, item)) |range| {
-                const range_src = token_starts[main_tokens[range]];
-                const range_inst = items.items[items_index].castTag(.switch_range).?;
-                items_index += 1;
-
-                // target >= start and target <= end
-                const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, range_inst.positionals.lhs);
-                const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, range_inst.positionals.rhs);
-                const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_and, range_start_ok, range_end_ok);
-
-                if (any_ok) |some| {
-                    any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_or, some, range_ok);
-                } else {
-                    any_ok = range_ok;
-                }
-                continue;
-            }
-
-            const item_inst = items.items[items_index];
-            items_index += 1;
-            const cpm_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .cmp_eq, target, item_inst);
-
-            if (any_ok) |some| {
-                any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .bool_or, some, cpm_ok);
-            } else {
-                any_ok = cpm_ok;
-            }
-        }
-
-        const condbr = try addZIRInstSpecial(mod, &case_scope.base, case_src, zir.Inst.CondBr, .{
-            .condition = any_ok.?,
-            .then_body = undefined, // populated below
-            .else_body = undefined, // populated below
-        }, .{});
-        const cond_block = try addZIRInstBlock(mod, &else_scope.base, case_src, .block, .{
-            .instructions = try parent_gz.astgen.arena.dupe(zir.Inst.Ref, case_scope.instructions.items),
-        });
-
-        // reset cond_scope for then_body
-        case_scope.instructions.items.len = 0;
-        try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target);
-        condbr.positionals.then_body = .{
-            .instructions = try parent_gz.astgen.arena.dupe(zir.Inst.Ref, case_scope.instructions.items),
-        };
-
-        // reset cond_scope for else_body
-        case_scope.instructions.items.len = 0;
-        _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{
-            .block = cond_block,
-        }, .{});
-        condbr.positionals.else_body = .{
-            .instructions = try parent_gz.astgen.arena.dupe(zir.Inst.Ref, case_scope.instructions.items),
-        };
-    }
-
-    // Finally generate else block or a break.
-    if (special_case) |case| {
-        try switchCaseExpr(mod, &else_scope.base, block_scope.break_result_loc, block, case, target);
-    } else {
-        // Not handling all possible cases is a compile error.
-        _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreachable_unsafe);
-    }
-    switch_inst.positionals.else_body = .{
-        .instructions = try block_scope.arena.dupe(zir.Inst.Ref, else_scope.instructions.items),
-    };
-
-    return &block.base;
-}
-
-fn switchCaseExpr(
-    gz: *GenZir,
-    scope: *Scope,
-    rl: ResultLoc,
-    block: *zir.Inst.Block,
-    case: ast.full.SwitchCase,
-    target: zir.Inst.Ref,
-) !void {
-    const tree = gz.tree();
-    const node_datas = tree.nodes.items(.data);
-    const main_tokens = tree.nodes.items(.main_token);
-    const token_tags = tree.tokens.items(.tag);
-
-    const case_src = token_starts[case.ast.arrow_token];
-    const sub_scope = blk: {
-        const payload_token = case.payload_token orelse break :blk scope;
-        const ident = if (token_tags[payload_token] == .asterisk)
-            payload_token + 1
-        else
-            payload_token;
-        const is_ptr = ident != payload_token;
-        const value_name = tree.tokenSlice(ident);
-        if (mem.eql(u8, value_name, "_")) {
-            if (is_ptr) {
-                return mod.failTok(scope, payload_token, "pointer modifier invalid on discard", .{});
-            }
-            break :blk scope;
-        }
-        return mod.failTok(scope, ident, "TODO implement switch value payload", .{});
-    };
-
-    const case_body = try expr(gz, sub_scope, rl, case.ast.target_expr);
-    if (!case_body.tag.isNoReturn()) {
-        _ = try addZIRInst(mod, sub_scope, case_src, zir.Inst.Break, .{
-            .block = block,
-            .operand = case_body,
-        }, .{});
-    }
+    const result = try parent_gz.addPlNode(.switch_br, switch_node, zir.Inst.SwitchBr{
+        .operand = operand,
+        .cases_len = 0,
+    });
+    return rvalue(parent_gz, scope, rl, result, switch_node);
 }
 
 fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref {
src/Module.zig
@@ -888,7 +888,7 @@ pub const Scope = struct {
         pub fn addSwitchBr(
             block: *Scope.Block,
             src: LazySrcLoc,
-            target: *ir.Inst,
+            operand: *ir.Inst,
             cases: []ir.Inst.SwitchBr.Case,
             else_body: ir.Body,
         ) !*ir.Inst {
@@ -899,7 +899,7 @@ pub const Scope = struct {
                     .ty = Type.initTag(.noreturn),
                     .src = src,
                 },
-                .target = target,
+                .target = operand,
                 .cases = cases,
                 .else_body = else_body,
             };
@@ -1533,6 +1533,8 @@ pub const SrcLoc = struct {
             .node_offset_bin_lhs,
             .node_offset_bin_rhs,
             .node_offset_switch_operand,
+            .node_offset_switch_special_prong,
+            .node_offset_switch_range,
             => src_loc.container.decl.container.file_scope,
         };
     }
@@ -1665,6 +1667,8 @@ pub const SrcLoc = struct {
                 return token_starts[tok_index];
             },
             .node_offset_switch_operand => @panic("TODO"),
+            .node_offset_switch_special_prong => @panic("TODO"),
+            .node_offset_switch_range => @panic("TODO"),
         }
     }
 };
@@ -1802,6 +1806,17 @@ pub const LazySrcLoc = union(enum) {
     /// which points to a switch expression AST node. Next, nagivate to the operand.
     /// The Decl is determined contextually.
     node_offset_switch_operand: i32,
+    /// The source location points to the else/`_` prong of a switch expression, found
+    /// by taking this AST node index offset from the containing Decl AST node,
+    /// which points to a switch expression AST node. Next, nagivate to the else/`_` prong.
+    /// The Decl is determined contextually.
+    node_offset_switch_special_prong: i32,
+    /// The source location points to all the ranges of a switch expression, found
+    /// by taking this AST node index offset from the containing Decl AST node,
+    /// which points to a switch expression AST node. Next, nagivate to any of the
+    /// range nodes. The error applies to all of them.
+    /// The Decl is determined contextually.
+    node_offset_switch_range: i32,
 
     /// Upgrade to a `SrcLoc` based on the `Decl` or file in the provided scope.
     pub fn toSrcLoc(lazy: LazySrcLoc, scope: *Scope) SrcLoc {
@@ -1836,6 +1851,8 @@ pub const LazySrcLoc = union(enum) {
             .node_offset_bin_lhs,
             .node_offset_bin_rhs,
             .node_offset_switch_operand,
+            .node_offset_switch_special_prong,
+            .node_offset_switch_range,
             => .{
                 .container = .{ .decl = scope.srcDecl().? },
                 .lazy = lazy,
@@ -1876,6 +1893,8 @@ pub const LazySrcLoc = union(enum) {
             .node_offset_bin_lhs,
             .node_offset_bin_rhs,
             .node_offset_switch_operand,
+            .node_offset_switch_special_prong,
+            .node_offset_switch_range,
             => .{
                 .container = .{ .decl = decl },
                 .lazy = lazy,
src/RangeSet.zig
@@ -44,6 +44,9 @@ fn lessThan(_: void, a: Range, b: Range) bool {
 }
 
 pub fn spans(self: *RangeSet, start: Value, end: Value) !bool {
+    if (self.ranges.items.len == 0)
+        return false;
+
     std.sort.sort(Range, self.ranges.items, {}, lessThan);
 
     if (!self.ranges.items[0].start.eql(start) or
src/Sema.zig
@@ -59,6 +59,9 @@ const Scope = Module.Scope;
 const InnerError = Module.InnerError;
 const Decl = Module.Decl;
 const LazySrcLoc = Module.LazySrcLoc;
+const RangeSet = @import("RangeSet.zig");
+
+const ValueSrcMap = std.HashMap(Value, LazySrcLoc, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage);
 
 pub fn root(sema: *Sema, root_block: *Scope.Block) !zir.Inst.Index {
     const inst_data = sema.code.instructions.items(.data)[0].pl_node;
@@ -242,18 +245,18 @@ pub fn analyzeBody(
             .ret_tok => return sema.zirRetTok(block, inst, false),
             .@"unreachable" => return sema.zirUnreachable(block, inst),
             .repeat => return sema.zirRepeat(block, inst),
-            .switch_br => return sema.zirSwitchBr(block, inst, false, .full),
-            .switch_br_range => return sema.zirSwitchBrRange(block, inst, false, .full),
+            .switch_br => return sema.zirSwitchBr(block, inst, false, .none),
+            .switch_br_multi => return sema.zirSwitchBrMulti(block, inst, false, .none),
             .switch_br_else => return sema.zirSwitchBr(block, inst, false, .@"else"),
-            .switch_br_else_range => return sema.zirSwitchBrRange(block, inst, false, .@"else"),
-            .switch_br_underscore => return sema.zirSwitchBr(block, inst, false, .under),
-            .switch_br_underscore_range => return sema.zirSwitchBrRange(block, inst, false, .under),
-            .switch_br_ref => return sema.zirSwitchBr(block, inst, true, .full),
-            .switch_br_ref_range => return sema.zirSwitchBrRange(block, inst, true, .full),
+            .switch_br_else_multi => return sema.zirSwitchBrMulti(block, inst, false, .@"else"),
+            .switch_br_under => return sema.zirSwitchBr(block, inst, false, .under),
+            .switch_br_under_multi => return sema.zirSwitchBrMulti(block, inst, false, .under),
+            .switch_br_ref => return sema.zirSwitchBr(block, inst, true, .none),
+            .switch_br_ref_multi => return sema.zirSwitchBrMulti(block, inst, true, .none),
             .switch_br_ref_else => return sema.zirSwitchBr(block, inst, true, .@"else"),
-            .switch_br_ref_else_range => return sema.zirSwitchBrRange(block, inst, true, .@"else"),
-            .switch_br_ref_underscore => return sema.zirSwitchBr(block, inst, true, .under),
-            .switch_br_ref_underscore_range => return sema.zirSwitchBrRange(block, inst, true, .under),
+            .switch_br_ref_else_multi => return sema.zirSwitchBrMulti(block, inst, true, .@"else"),
+            .switch_br_ref_under => return sema.zirSwitchBr(block, inst, true, .under),
+            .switch_br_ref_under_multi => return sema.zirSwitchBrMulti(block, inst, true, .under),
 
             // Instructions that we know can *never* be noreturn based solely on
             // their tag. We avoid needlessly checking if they are noreturn and
@@ -2205,14 +2208,14 @@ fn zirSliceSentinel(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) Inne
     return sema.analyzeSlice(block, src, array_ptr, start, end, sentinel, sentinel_src);
 }
 
-const ElseProng = enum { full, @"else", under };
+const SpecialProng = enum { none, @"else", under };
 
 fn zirSwitchBr(
     sema: *Sema,
     block: *Scope.Block,
     inst: zir.Inst.Index,
     is_ref: bool,
-    else_prong: ElseProng,
+    special_prong: SpecialProng,
 ) InnerError!zir.Inst.Index {
     const tracy = trace(@src());
     defer tracy.end();
@@ -2228,15 +2231,23 @@ fn zirSwitchBr(
     else
         operand_ptr;
 
-    return sema.analyzeSwitch(block, operand, extra.end, else_prong, extra.data.cases_len, 0, 0);
+    return sema.analyzeSwitch(
+        block,
+        operand,
+        extra.end,
+        special_prong,
+        extra.data.cases_len,
+        0,
+        inst_data.src_node,
+    );
 }
 
-fn zirSwitchBrRange(
+fn zirSwitchBrMulti(
     sema: *Sema,
     block: *Scope.Block,
     inst: zir.Inst.Index,
     is_ref: bool,
-    else_prong: ElseProng,
+    special_prong: SpecialProng,
 ) InnerError!zir.Inst.Index {
     const tracy = trace(@src());
     defer tracy.end();
@@ -2244,7 +2255,7 @@ fn zirSwitchBrRange(
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
     const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = inst_data.src_node };
-    const extra = sema.code.extraData(zir.Inst.SwitchBrRange, inst_data.payload_index);
+    const extra = sema.code.extraData(zir.Inst.SwitchBrMulti, inst_data.payload_index);
 
     const operand_ptr = try sema.resolveInst(extra.data.operand);
     const operand = if (is_ref)
@@ -2256,196 +2267,285 @@ fn zirSwitchBrRange(
         block,
         operand,
         extra.end,
-        else_prong,
+        special_prong,
         extra.data.scalar_cases_len,
         extra.data.multi_cases_len,
-        extra.data.range_cases_len,
+        inst_data.src_node,
     );
 }
 
 fn analyzeSwitch(
     sema: *Sema,
-    parent_block: *Scope.Block,
+    block: *Scope.Block,
     operand: *Inst,
     extra_end: usize,
-    else_prong: ElseProng,
+    special_prong: SpecialProng,
     scalar_cases_len: usize,
     multi_cases_len: usize,
-    range_cases_len: usize,
+    src_node_offset: i32,
 ) InnerError!zir.Inst.Index {
-    if (true) @panic("TODO rework for zir-memory-layout branch");
-
-    try sema.validateSwitch(parent_block, operand, inst);
-
-    if (try sema.resolveDefinedValue(parent_block, inst.base.src, operand)) |target_val| {
-        for (inst.positionals.cases) |case| {
-            const resolved = try sema.resolveInst(case.item);
-            const casted = try sema.coerce(block, operand.ty, resolved, resolved_src);
-            const item = try sema.resolveConstValue(parent_block, case_src, casted);
-
-            if (target_val.eql(item)) {
-                _ = try sema.analyzeBody(parent_block, case.body);
-                return always_noreturn;
-            }
-        }
-        _ = try sema.analyzeBody(parent_block, inst.positionals.else_body);
-        return always_noreturn;
-    }
-
-    if (inst.positionals.cases.len == 0) {
-        // no cases just analyze else_branch
-        _ = try sema.analyzeBody(parent_block, inst.positionals.else_body);
-        return always_noreturn;
-    }
-
-    try sema.requireRuntimeBlock(parent_block, inst.base.src);
-    const cases = try sema.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len);
-
-    var case_block: Scope.Block = .{
-        .parent = parent_block,
-        .sema = sema,
-        .src_decl = parent_block.src_decl,
-        .instructions = .{},
-        .inlining = parent_block.inlining,
-        .is_comptime = parent_block.is_comptime,
+    const special: struct { body: []const zir.Inst.Index, end: usize } = switch (special_prong) {
+        .none => .{ .body = &.{}, .end = extra_end },
+        .under, .@"else" => blk: {
+            const body_len = sema.code.extra[extra_end];
+            const extra_body_start = extra_end + 1;
+            break :blk .{
+                .body = sema.code.extra[extra_body_start..][0..body_len],
+                .end = extra_body_start + body_len,
+            };
+        },
     };
-    defer case_block.instructions.deinit(sema.gpa);
 
-    for (inst.positionals.cases) |case, i| {
-        // Reset without freeing.
-        case_block.instructions.items.len = 0;
+    const src: LazySrcLoc = .{ .node_offset = src_node_offset };
+    const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset };
+    const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset };
 
-        const resolved = try sema.resolveInst(case.item);
-        const casted = try sema.coerce(block, operand.ty, resolved, resolved_src);
-        const item = try sema.resolveConstValue(parent_block, case_src, casted);
-
-        _ = try sema.analyzeBody(&case_block, case.body);
-
-        cases[i] = .{
-            .item = item,
-            .body = .{ .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items) },
+    // Validate usage of '_' prongs.
+    if (special_prong == .under and !operand.ty.isExhaustiveEnum()) {
+        const msg = msg: {
+            const msg = try sema.mod.errMsg(
+                &block.base,
+                src,
+                "'_' prong only allowed when switching on non-exhaustive enums",
+                .{},
+            );
+            errdefer msg.destroy(sema.gpa);
+            try sema.mod.errNote(
+                &block.base,
+                special_prong_src,
+                msg,
+                "'_' prong here",
+                .{},
+            );
+            break :msg msg;
         };
+        return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
     }
 
-    case_block.instructions.items.len = 0;
-    _ = try sema.analyzeBody(&case_block, inst.positionals.else_body);
-
-    const else_body: ir.Body = .{
-        .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items),
-    };
-
-    return mod.addSwitchBr(parent_block, inst.base.src, operand, cases, else_body);
-}
-
-fn validateSwitch(sema: *Sema, block: *Scope.Block, operand: *Inst, inst: zir.Inst.Index) InnerError!void {
-    // validate usage of '_' prongs
-    if (inst.positionals.special_prong == .underscore and operand.ty.zigTypeTag() != .Enum) {
-        return sema.mod.fail(&block.base, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{});
-        // TODO notes "'_' prong here" inst.positionals.cases[last].src
-    }
-
-    // check that operand type supports ranges
-    if (inst.positionals.range) |range_inst| {
-        switch (operand.ty.zigTypeTag()) {
-            .Int, .ComptimeInt => {},
-            else => {
-                return sema.mod.fail(&block.base, operand.src, "ranges not allowed when switching on type {}", .{operand.ty});
-                // TODO notes "range used here" range_inst.src
-            },
-        }
-    }
-
-    // validate for duplicate items/missing else prong
+    // Validate for duplicate items, missing else prong, and invalid range.
     switch (operand.ty.zigTypeTag()) {
-        .Enum => return sema.mod.fail(&block.base, inst.base.src, "TODO validateSwitch .Enum", .{}),
-        .ErrorSet => return sema.mod.fail(&block.base, inst.base.src, "TODO validateSwitch .ErrorSet", .{}),
-        .Union => return sema.mod.fail(&block.base, inst.base.src, "TODO validateSwitch .Union", .{}),
+        .Enum => return sema.mod.fail(&block.base, src, "TODO validate switch .Enum", .{}),
+        .ErrorSet => return sema.mod.fail(&block.base, src, "TODO validate switch .ErrorSet", .{}),
+        .Union => return sema.mod.fail(&block.base, src, "TODO validate switch .Union", .{}),
         .Int, .ComptimeInt => {
-            var range_set = @import("RangeSet.zig").init(sema.gpa);
+            var range_set = RangeSet.init(sema.gpa);
             defer range_set.deinit();
 
-            for (inst.positionals.items) |item| {
-                const maybe_src = if (item.castTag(.switch_range)) |range| blk: {
-                    const start_resolved = try sema.resolveInst(range.positionals.lhs);
-                    const start_casted = try sema.coerce(block, operand.ty, start_resolved);
-                    const end_resolved = try sema.resolveInst(range.positionals.rhs);
-                    const end_casted = try sema.coerce(block, operand.ty, end_resolved);
-
-                    break :blk try range_set.add(
-                        try sema.resolveConstValue(block, range_start_src, start_casted),
-                        try sema.resolveConstValue(block, range_end_src, end_casted),
-                        item.src,
+            var extra_index: usize = special.end;
+            {
+                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 = sema.code.extra[extra_index];
+                    extra_index += 1;
+                    const body = sema.code.extra[extra_index..][0..body_len];
+                    extra_index += body_len;
+
+                    try sema.validateSwitchItem(
+                        block,
+                        &range_set,
+                        item_ref,
+                        src_node_offset,
                     );
-                } else blk: {
-                    const resolved = try sema.resolveInst(item);
-                    const casted = try sema.coerce(block, operand.ty, resolved);
-                    const value = try sema.resolveConstValue(block, item_src, casted);
-                    break :blk try range_set.add(value, value, item.src);
-                };
-
-                if (maybe_src) |previous_src| {
-                    return sema.mod.fail(&block.base, item.src, "duplicate switch value", .{});
-                    // TODO notes "previous value is here" previous_src
                 }
             }
+            {
+                var multi_i: usize = 0;
+                while (multi_i < multi_cases_len) : (multi_i += 1) {
+                    const items_len = sema.code.extra[extra_index];
+                    extra_index += 1;
+                    const ranges_len = sema.code.extra[extra_index];
+                    extra_index += 1;
+                    const body_len = sema.code.extra[extra_index];
+                    extra_index += 1;
+                    const items = sema.code.refSlice(extra_index, items_len);
+                    extra_index += items_len;
+
+                    for (items) |item_ref| {
+                        try sema.validateSwitchItem(
+                            block,
+                            &range_set,
+                            item_ref,
+                            src_node_offset,
+                        );
+                    }
 
-            if (operand.ty.zigTypeTag() == .Int) {
-                var arena = std.heap.ArenaAllocator.init(sema.gpa);
-                defer arena.deinit();
-
-                const start = try operand.ty.minInt(&arena, mod.getTarget());
-                const end = try operand.ty.maxInt(&arena, mod.getTarget());
-                if (try range_set.spans(start, end)) {
-                    if (inst.positionals.special_prong == .@"else") {
-                        return sema.mod.fail(&block.base, inst.base.src, "unreachable else prong, all cases already handled", .{});
+                    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;
+
+                        try sema.validateSwitchRange(
+                            block,
+                            &range_set,
+                            item_first,
+                            item_last,
+                            src_node_offset,
+                        );
                     }
-                    return;
+
+                    extra_index += body_len;
                 }
             }
 
-            if (inst.positionals.special_prong != .@"else") {
-                return sema.mod.fail(&block.base, inst.base.src, "switch must handle all possibilities", .{});
+            check_range: {
+                if (operand.ty.zigTypeTag() == .Int) {
+                    var arena = std.heap.ArenaAllocator.init(sema.gpa);
+                    defer arena.deinit();
+
+                    const min_int = try operand.ty.minInt(&arena, sema.mod.getTarget());
+                    const max_int = try operand.ty.maxInt(&arena, sema.mod.getTarget());
+                    if (try range_set.spans(min_int, max_int)) {
+                        if (special_prong == .@"else") {
+                            return sema.mod.fail(
+                                &block.base,
+                                special_prong_src,
+                                "unreachable else prong; all cases already handled",
+                                .{},
+                            );
+                        }
+                        break :check_range;
+                    }
+                }
+                if (special_prong != .@"else") {
+                    return sema.mod.fail(
+                        &block.base,
+                        src,
+                        "switch must handle all possibilities",
+                        .{},
+                    );
+                }
             }
         },
         .Bool => {
             var true_count: u8 = 0;
             var false_count: u8 = 0;
-            for (inst.positionals.items) |item| {
-                const resolved = try sema.resolveInst(item);
-                const casted = try sema.coerce(block, Type.initTag(.bool), resolved);
-                if ((try sema.resolveConstValue(block, item_src, casted)).toBool()) {
-                    true_count += 1;
-                } else {
-                    false_count += 1;
-                }
 
-                if (true_count + false_count > 2) {
-                    return sema.mod.fail(&block.base, item.src, "duplicate switch value", .{});
+            var extra_index: usize = special.end;
+            {
+                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 = sema.code.extra[extra_index];
+                    extra_index += 1;
+                    const body = sema.code.extra[extra_index..][0..body_len];
+                    extra_index += body_len;
+
+                    try sema.validateSwitchItemBool(
+                        block,
+                        &true_count,
+                        &false_count,
+                        item_ref,
+                        src_node_offset,
+                    );
                 }
             }
-            if ((true_count + false_count < 2) and inst.positionals.special_prong != .@"else") {
-                return sema.mod.fail(&block.base, inst.base.src, "switch must handle all possibilities", .{});
+            {
+                var multi_i: usize = 0;
+                while (multi_i < multi_cases_len) : (multi_i += 1) {
+                    const items_len = sema.code.extra[extra_index];
+                    extra_index += 1;
+                    const ranges_len = sema.code.extra[extra_index];
+                    extra_index += 1;
+                    const body_len = sema.code.extra[extra_index];
+                    extra_index += 1;
+                    const items = sema.code.refSlice(extra_index, items_len);
+                    extra_index += items_len + body_len;
+
+                    for (items) |item_ref| {
+                        try sema.validateSwitchItemBool(
+                            block,
+                            &true_count,
+                            &false_count,
+                            item_ref,
+                            src_node_offset,
+                        );
+                    }
+
+                    try sema.validateSwitchNoRange(block, ranges_len, operand.ty, src_node_offset);
+                }
             }
-            if ((true_count + false_count == 2) and inst.positionals.special_prong == .@"else") {
-                return sema.mod.fail(&block.base, inst.base.src, "unreachable else prong, all cases already handled", .{});
+            switch (special_prong) {
+                .@"else" => {
+                    if (true_count + false_count == 2) {
+                        return sema.mod.fail(
+                            &block.base,
+                            src,
+                            "unreachable else prong; all cases already handled",
+                            .{},
+                        );
+                    }
+                },
+                .under, .none => {
+                    if (true_count + false_count < 2) {
+                        return sema.mod.fail(
+                            &block.base,
+                            src,
+                            "switch must handle all possibilities",
+                            .{},
+                        );
+                    }
+                },
             }
         },
         .EnumLiteral, .Void, .Fn, .Pointer, .Type => {
-            if (inst.positionals.special_prong != .@"else") {
-                return sema.mod.fail(&block.base, inst.base.src, "else prong required when switching on type '{}'", .{operand.ty});
+            if (special_prong != .@"else") {
+                return sema.mod.fail(
+                    &block.base,
+                    src,
+                    "else prong required when switching on type '{}'",
+                    .{operand.ty},
+                );
             }
 
-            var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(sema.gpa);
+            var seen_values = ValueSrcMap.init(sema.gpa);
             defer seen_values.deinit();
 
-            for (inst.positionals.items) |item| {
-                const resolved = try sema.resolveInst(item);
-                const casted = try sema.coerce(block, operand.ty, resolved);
-                const val = try sema.resolveConstValue(block, item_src, casted);
+            var extra_index: usize = special.end;
+            {
+                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 = sema.code.extra[extra_index];
+                    extra_index += 1;
+                    const body = sema.code.extra[extra_index..][0..body_len];
+                    extra_index += body_len;
+
+                    try sema.validateSwitchItemSparse(
+                        block,
+                        &seen_values,
+                        item_ref,
+                        src_node_offset,
+                    );
+                }
+            }
+            {
+                var multi_i: usize = 0;
+                while (multi_i < multi_cases_len) : (multi_i += 1) {
+                    const items_len = sema.code.extra[extra_index];
+                    extra_index += 1;
+                    const ranges_len = sema.code.extra[extra_index];
+                    extra_index += 1;
+                    const body_len = sema.code.extra[extra_index];
+                    extra_index += 1;
+                    const items = sema.code.refSlice(extra_index, items_len);
+                    extra_index += items_len + body_len;
+
+                    for (items) |item_ref| {
+                        try sema.validateSwitchItemSparse(
+                            block,
+                            &seen_values,
+                            item_ref,
+                            src_node_offset,
+                        );
+                    }
 
-                if (try seen_values.fetchPut(val, item.src)) |prev| {
-                    return sema.mod.fail(&block.base, item.src, "duplicate switch value", .{});
-                    // TODO notes "previous value here" prev.value
+                    try sema.validateSwitchNoRange(block, ranges_len, operand.ty, src_node_offset);
                 }
             }
         },
@@ -2464,10 +2564,298 @@ fn validateSwitch(sema: *Sema, block: *Scope.Block, operand: *Inst, inst: zir.In
         .AnyFrame,
         .ComptimeFloat,
         .Float,
-        => {
-            return sema.mod.fail(&block.base, operand.src, "invalid switch operand type '{}'", .{operand.ty});
-        },
+        => return sema.mod.fail(&block.base, operand_src, "invalid switch operand type '{}'", .{
+            operand.ty,
+        }),
+    }
+
+    if (try sema.resolveDefinedValue(block, src, operand)) |operand_val| {
+        var extra_index: usize = special.end;
+        {
+            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 = sema.code.extra[extra_index];
+                extra_index += 1;
+                const body = sema.code.extra[extra_index..][0..body_len];
+                extra_index += body_len;
+
+                const item = try sema.resolveInst(item_ref);
+                const item_val = try sema.resolveConstValue(block, item.src, item);
+                if (operand_val.eql(item_val)) {
+                    return sema.analyzeBody(block, body);
+                }
+            }
+        }
+        {
+            var multi_i: usize = 0;
+            while (multi_i < multi_cases_len) : (multi_i += 1) {
+                const items_len = sema.code.extra[extra_index];
+                extra_index += 1;
+                const ranges_len = sema.code.extra[extra_index];
+                extra_index += 1;
+                const body_len = sema.code.extra[extra_index];
+                extra_index += 1;
+                const items = sema.code.refSlice(extra_index, items_len);
+                extra_index += 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 item_val = try sema.resolveConstValue(block, item.src, item);
+                    if (operand_val.eql(item_val)) {
+                        return sema.analyzeBody(block, body);
+                    }
+                }
+
+                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 first_tv = try sema.resolveInstConst(block, .todo, item_first);
+                    const last_tv = try sema.resolveInstConst(block, .todo, item_last);
+                    if (Value.compare(operand_val, .gte, first_tv.val) and
+                        Value.compare(operand_val, .lte, last_tv.val))
+                    {
+                        return sema.analyzeBody(block, body);
+                    }
+                }
+
+                extra_index += body_len;
+            }
+        }
+        return sema.analyzeBody(block, special.body);
+    }
+
+    if (scalar_cases_len + multi_cases_len == 0) {
+        return sema.analyzeBody(block, special.body);
+    }
+
+    try sema.requireRuntimeBlock(block, src);
+    // TODO when reworking TZIR memory layout make multi cases get generated as cases,
+    // not as part of the "else" block.
+    const cases = try sema.arena.alloc(Inst.SwitchBr.Case, scalar_cases_len);
+
+    var case_block = block.makeSubBlock();
+    defer case_block.instructions.deinit(sema.gpa);
+
+    var extra_index: usize = special.end;
+
+    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 = sema.code.extra[extra_index];
+        extra_index += 1;
+        const body = sema.code.extra[extra_index..][0..body_len];
+        extra_index += body_len;
+
+        case_block.instructions.shrinkRetainingCapacity(0);
+        const item = try sema.resolveInst(item_ref);
+        const item_val = try sema.resolveConstValue(block, item.src, item);
+
+        _ = try sema.analyzeBody(&case_block, body);
+
+        cases[scalar_i] = .{
+            .item = item_val,
+            .body = .{ .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items) },
+        };
+    }
+
+    var first_condbr: *Inst.CondBr = undefined;
+    var prev_condbr: ?*Inst.CondBr = null;
+
+    var multi_i: usize = 0;
+    while (multi_i < multi_cases_len) : (multi_i += 1) {
+        const items_len = sema.code.extra[extra_index];
+        extra_index += 1;
+        const ranges_len = sema.code.extra[extra_index];
+        extra_index += 1;
+        const body_len = sema.code.extra[extra_index];
+        extra_index += 1;
+        const items = sema.code.refSlice(extra_index, items_len);
+        extra_index += items_len;
+
+        case_block.instructions.shrinkRetainingCapacity(0);
+
+        var any_ok: ?*Inst = null;
+        const bool_ty = comptime Type.initTag(.bool);
+
+        for (items) |item_ref| {
+            const item = try sema.resolveInst(item_ref);
+            _ = try sema.resolveConstValue(block, item.src, item);
+
+            const cmp_ok = try case_block.addBinOp(item.src, bool_ty, .cmp_eq, operand, item);
+            if (any_ok) |some| {
+                any_ok = try case_block.addBinOp(item.src, bool_ty, .bool_or, some, cmp_ok);
+            } else {
+                any_ok = cmp_ok;
+            }
+        }
+
+        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 item_first = try sema.resolveInst(first_ref);
+            const item_last = try sema.resolveInst(last_ref);
+
+            _ = try sema.resolveConstValue(block, item_first.src, item_first);
+            _ = try sema.resolveConstValue(block, item_last.src, item_last);
+
+            const range_src = item_first.src;
+
+            // operand >= first and operand <= last
+            const range_first_ok = try case_block.addBinOp(
+                item_first.src,
+                bool_ty,
+                .cmp_gte,
+                operand,
+                item_first,
+            );
+            const range_last_ok = try case_block.addBinOp(
+                item_last.src,
+                bool_ty,
+                .cmp_lte,
+                operand,
+                item_last,
+            );
+            const range_ok = try case_block.addBinOp(
+                range_src,
+                bool_ty,
+                .bool_and,
+                range_first_ok,
+                range_last_ok,
+            );
+            if (any_ok) |some| {
+                any_ok = try case_block.addBinOp(range_src, bool_ty, .bool_or, some, range_ok);
+            } else {
+                any_ok = range_ok;
+            }
+        }
+
+        const body = sema.code.extra[extra_index..][0..body_len];
+        extra_index += body_len;
+        _ = try sema.analyzeBody(&case_block, body);
+        const then_body: Body = .{
+            .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items),
+        };
+        const new_condbr = try sema.arena.create(Inst.CondBr);
+        new_condbr.* = .{
+            .base = .{
+                .tag = .condbr,
+                .ty = Type.initTag(.noreturn),
+                .src = src,
+            },
+            .condition = any_ok.?,
+            .then_body = then_body,
+            .else_body = undefined,
+        };
+        if (prev_condbr) |condbr| {
+            condbr.else_body = .{
+                .instructions = try sema.arena.dupe(*Inst, &[1]*Inst{&new_condbr.base}),
+            };
+        } else {
+            first_condbr = new_condbr;
+        }
+        prev_condbr = new_condbr;
     }
+
+    case_block.instructions.shrinkRetainingCapacity(0);
+    _ = try sema.analyzeBody(&case_block, special.body);
+    const else_body: Body = .{
+        .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items),
+    };
+    first_condbr.else_body = else_body;
+
+    const final_else_body: Body = .{
+        .instructions = try sema.arena.dupe(*Inst, &[1]*Inst{&first_condbr.base}),
+    };
+
+    _ = try block.addSwitchBr(src, operand, cases, final_else_body);
+    return always_noreturn;
+}
+
+fn validateSwitchItem(
+    sema: *Sema,
+    block: *Scope.Block,
+    range_set: *RangeSet,
+    item_ref: zir.Inst.Ref,
+    src_node_offset: i32,
+) InnerError!void {
+    @panic("TODO");
+}
+
+fn validateSwitchItemBool(
+    sema: *Sema,
+    block: *Scope.Block,
+    true_count: *u8,
+    false_count: *u8,
+    item_ref: zir.Inst.Ref,
+    src_node_offset: i32,
+) InnerError!void {
+    @panic("TODO");
+}
+
+fn validateSwitchRange(
+    sema: *Sema,
+    block: *Scope.Block,
+    range_set: *RangeSet,
+    item_first: zir.Inst.Ref,
+    item_last: zir.Inst.Ref,
+    src_node_offset: i32,
+) InnerError!void {
+    @panic("TODO");
+}
+
+fn validateSwitchItemSparse(
+    sema: *Sema,
+    block: *Scope.Block,
+    seen_values: *ValueSrcMap,
+    item_ref: zir.Inst.Ref,
+    src_node_offset: i32,
+) InnerError!void {
+    @panic("TODO");
+}
+
+fn validateSwitchNoRange(
+    sema: *Sema,
+    block: *Scope.Block,
+    ranges_len: u32,
+    operand_ty: Type,
+    src_node_offset: i32,
+) InnerError!void {
+    if (ranges_len == 0)
+        return;
+
+    const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset };
+    const range_src: LazySrcLoc = .{ .node_offset_switch_range = src_node_offset };
+
+    const msg = msg: {
+        const msg = try sema.mod.errMsg(
+            &block.base,
+            operand_src,
+            "ranges not allowed when switching on type '{}'",
+            .{operand_ty},
+        );
+        errdefer msg.destroy(sema.gpa);
+        try sema.mod.errNote(
+            &block.base,
+            range_src,
+            msg,
+            "range here",
+            .{},
+        );
+        break :msg msg;
+    };
+    return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
 }
 
 fn zirImport(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
@@ -3095,30 +3483,21 @@ fn zirCondbr(
         return always_noreturn;
     }
 
-    var true_block: Scope.Block = .{
-        .parent = parent_block,
-        .sema = sema,
-        .src_decl = parent_block.src_decl,
-        .instructions = .{},
-        .inlining = parent_block.inlining,
-        .is_comptime = parent_block.is_comptime,
+    var sub_block = parent_block.makeSubBlock();
+    defer sub_block.instructions.deinit(sema.gpa);
+
+    _ = try sema.analyzeBody(&sub_block, then_body);
+    const tzir_then_body: ir.Body = .{
+        .instructions = try sema.arena.dupe(*Inst, sub_block.instructions.items),
     };
-    defer true_block.instructions.deinit(sema.gpa);
-    _ = try sema.analyzeBody(&true_block, then_body);
 
-    var false_block: Scope.Block = .{
-        .parent = parent_block,
-        .sema = sema,
-        .src_decl = parent_block.src_decl,
-        .instructions = .{},
-        .inlining = parent_block.inlining,
-        .is_comptime = parent_block.is_comptime,
+    sub_block.instructions.shrinkRetainingCapacity(0);
+
+    _ = try sema.analyzeBody(&sub_block, else_body);
+    const tzir_else_body: ir.Body = .{
+        .instructions = try sema.arena.dupe(*Inst, sub_block.instructions.items),
     };
-    defer false_block.instructions.deinit(sema.gpa);
-    _ = try sema.analyzeBody(&false_block, else_body);
 
-    const tzir_then_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, true_block.instructions.items) };
-    const tzir_else_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, false_block.instructions.items) };
     _ = try parent_block.addCondBr(src, cond, tzir_then_body, tzir_else_body);
     return always_noreturn;
 }
src/type.zig
@@ -3303,6 +3303,10 @@ pub const Type = extern union {
         }
     }
 
+    pub fn isExhaustiveEnum(ty: Type) bool {
+        return false; // TODO
+    }
+
     /// This enum does not directly correspond to `std.builtin.TypeId` because
     /// it has extra enum tags in it, as a way of using less memory. For example,
     /// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types
src/zir.zig
@@ -589,31 +589,31 @@ pub const Inst = struct {
         /// AST node is the switch, payload is `SwitchBr`.
         /// All prongs of target handled.
         switch_br,
-        /// Same as switch_br, except has a range field.
-        switch_br_range,
+        /// Same as switch_br, except one or more prongs have multiple items.
+        switch_br_multi,
         /// Same as switch_br, except has an else prong.
         switch_br_else,
-        /// Same as switch_br_else, except has a range field.
-        switch_br_else_range,
+        /// Same as switch_br_else, except one or more prongs have multiple items.
+        switch_br_else_multi,
         /// Same as switch_br, except has an underscore prong.
-        switch_br_underscore,
-        /// Same as switch_br, except has a range field.
-        switch_br_underscore_range,
+        switch_br_under,
+        /// Same as switch_br, except one or more prongs have multiple items.
+        switch_br_under_multi,
         /// Same as `switch_br` but the target is a pointer to the value being switched on.
         switch_br_ref,
-        /// Same as `switch_br_range` but the target is a pointer to the value being switched on.
-        switch_br_ref_range,
+        /// Same as `switch_br_multi` but the target is a pointer to the value being switched on.
+        switch_br_ref_multi,
         /// Same as `switch_br_else` but the target is a pointer to the value being switched on.
         switch_br_ref_else,
-        /// Same as `switch_br_else_range` but the target is a pointer to the
+        /// Same as `switch_br_else_multi` but the target is a pointer to the
         /// value being switched on.
-        switch_br_ref_else_range,
-        /// Same as `switch_br_underscore` but the target is a pointer to the value
+        switch_br_ref_else_multi,
+        /// Same as `switch_br_under` but the target is a pointer to the value
         /// being switched on.
-        switch_br_ref_underscore,
-        /// Same as `switch_br_underscore_range` but the target is a pointer to
+        switch_br_ref_under,
+        /// Same as `switch_br_under_multi` but the target is a pointer to
         /// the value being switched on.
-        switch_br_ref_underscore_range,
+        switch_br_ref_under_multi,
 
         /// Returns whether the instruction is one of the control flow "noreturn" types.
         /// Function calls do not count.
@@ -757,17 +757,17 @@ pub const Inst = struct {
                 .repeat,
                 .repeat_inline,
                 .switch_br,
-                .switch_br_range,
+                .switch_br_multi,
                 .switch_br_else,
-                .switch_br_else_range,
-                .switch_br_underscore,
-                .switch_br_underscore_range,
+                .switch_br_else_multi,
+                .switch_br_under,
+                .switch_br_under_multi,
                 .switch_br_ref,
-                .switch_br_ref_range,
+                .switch_br_ref_multi,
                 .switch_br_ref_else,
-                .switch_br_ref_else_range,
-                .switch_br_ref_underscore,
-                .switch_br_ref_underscore_range,
+                .switch_br_ref_else_multi,
+                .switch_br_ref_under,
+                .switch_br_ref_under_multi,
                 => true,
             };
         }
@@ -1333,7 +1333,7 @@ pub const Inst = struct {
     /// This form is supported when there are no ranges, and exactly 1 item per block.
     /// Depending on zir tag and len fields, extra fields trail
     /// this one in the extra array.
-    /// 0. else_body { // If the tag has "_else" or "_underscore" in it.
+    /// 0. else_body { // If the tag has "_else" or "_under" in it.
     ///        body_len: u32,
     ///        body member Index for every body_len
     ///     }
@@ -1351,7 +1351,7 @@ pub const Inst = struct {
     /// or a range.
     /// Depending on zir tag and len fields, extra fields trail
     /// this one in the extra array.
-    /// 0. else_body { // If the tag has "_else" or "_underscore" in it.
+    /// 0. else_body { // If the tag has "_else" or "_under" in it.
     ///        body_len: u32,
     ///        body member Index for every body_len
     ///     }
@@ -1362,19 +1362,19 @@ pub const Inst = struct {
     ///     }
     /// 2. multi_cases: { // for every multi_cases_len
     ///        items_len: u32,
-    ///        item: Ref for every items_len
-    ///        block_index: u32, // index in extra to a `Block`
-    ///    }
-    /// 3. range_cases: { // for every range_cases_len
-    ///        item_start: Ref,
-    ///        item_end: Ref,
-    ///        block_index: u32, // index in extra to a `Block`
+    ///        ranges_len: u32,
+    ///        body_len: u32,
+    ///        item: Ref // for every items_len
+    ///        ranges: { // for every ranges_len
+    ///            item_first: Ref,
+    ///            item_last: Ref,
+    ///        }
+    ///        body member Index for every body_len
     ///    }
-    pub const SwitchBrRange = struct {
+    pub const SwitchBrMulti = struct {
         operand: Ref,
         scalar_cases_len: u32,
         multi_cases_len: u32,
-        range_cases_len: u32,
     };
 
     pub const Field = struct {
@@ -1544,19 +1544,19 @@ const Writer = struct {
 
             .switch_br,
             .switch_br_else,
-            .switch_br_underscore,
+            .switch_br_under,
             .switch_br_ref,
             .switch_br_ref_else,
-            .switch_br_ref_underscore,
+            .switch_br_ref_under,
             => try self.writePlNodeSwitchBr(stream, inst),
 
-            .switch_br_range,
-            .switch_br_else_range,
-            .switch_br_underscore_range,
-            .switch_br_ref_range,
-            .switch_br_ref_else_range,
-            .switch_br_ref_underscore_range,
-            => try self.writePlNodeSwitchBrRange(stream, inst),
+            .switch_br_multi,
+            .switch_br_else_multi,
+            .switch_br_under_multi,
+            .switch_br_ref_multi,
+            .switch_br_ref_else_multi,
+            .switch_br_ref_under_multi,
+            => try self.writePlNodeSwitchBrMulti(stream, inst),
 
             .compile_log,
             .typeof_peer,
@@ -1766,17 +1766,98 @@ const Writer = struct {
     fn writePlNodeSwitchBr(self: *Writer, stream: anytype, inst: Inst.Index) !void {
         const inst_data = self.code.instructions.items(.data)[inst].pl_node;
         const extra = self.code.extraData(Inst.SwitchBr, inst_data.payload_index);
-
         try self.writeInstRef(stream, extra.data.operand);
-        try stream.writeAll(", TODO) ");
+        var extra_index: usize = extra.end;
+        {
+            var scalar_i: usize = 0;
+            while (scalar_i < extra.data.cases_len) : (scalar_i += 1) {
+                const item_ref = @intToEnum(Inst.Ref, self.code.extra[extra_index]);
+                extra_index += 1;
+                const body_len = self.code.extra[extra_index];
+                extra_index += 1;
+                const body = self.code.extra[extra_index..][0..body_len];
+                extra_index += body_len;
+
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, item_ref);
+                try stream.writeAll(" => {\n");
+                self.indent += 2;
+                try self.writeBody(stream, body);
+                self.indent -= 2;
+                try stream.writeByteNTimes(' ', self.indent);
+                try stream.writeAll("}");
+            }
+        }
+        try stream.writeAll(") ");
         try self.writeSrc(stream, inst_data.src());
     }
 
-    fn writePlNodeSwitchBrRange(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+    fn writePlNodeSwitchBrMulti(self: *Writer, stream: anytype, inst: Inst.Index) !void {
         const inst_data = self.code.instructions.items(.data)[inst].pl_node;
-        const extra = self.code.extraData(Inst.SwitchBrRange, inst_data.payload_index);
+        const extra = self.code.extraData(Inst.SwitchBrMulti, inst_data.payload_index);
         try self.writeInstRef(stream, extra.data.operand);
-        try stream.writeAll(", TODO) ");
+        var extra_index: usize = extra.end;
+        {
+            var scalar_i: usize = 0;
+            while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) {
+                const item_ref = @intToEnum(Inst.Ref, self.code.extra[extra_index]);
+                extra_index += 1;
+                const body_len = self.code.extra[extra_index];
+                extra_index += 1;
+                const body = self.code.extra[extra_index..][0..body_len];
+                extra_index += body_len;
+
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, item_ref);
+                try stream.writeAll(" => {\n");
+                self.indent += 2;
+                try self.writeBody(stream, body);
+                self.indent -= 2;
+                try stream.writeByteNTimes(' ', self.indent);
+                try stream.writeAll("}");
+            }
+        }
+        {
+            var multi_i: usize = 0;
+            while (multi_i < extra.data.multi_cases_len) : (multi_i += 1) {
+                const items_len = self.code.extra[extra_index];
+                extra_index += 1;
+                const ranges_len = self.code.extra[extra_index];
+                extra_index += 1;
+                const body_len = self.code.extra[extra_index];
+                extra_index += 1;
+                const items = self.code.refSlice(extra_index, items_len);
+                extra_index += items_len;
+
+                for (items) |item_ref| {
+                    try stream.writeAll(", ");
+                    try self.writeInstRef(stream, item_ref);
+                }
+
+                var range_i: usize = 0;
+                while (range_i < ranges_len) : (range_i += 1) {
+                    const item_first = @intToEnum(Inst.Ref, self.code.extra[extra_index]);
+                    extra_index += 1;
+                    const item_last = @intToEnum(Inst.Ref, self.code.extra[extra_index]);
+                    extra_index += 1;
+
+                    try stream.writeAll(", ");
+                    try self.writeInstRef(stream, item_first);
+                    try stream.writeAll("...");
+                    try self.writeInstRef(stream, item_last);
+                }
+
+                const body = self.code.extra[extra_index..][0..body_len];
+                extra_index += body_len;
+                try stream.writeAll(" => {\n");
+                self.indent += 2;
+                try self.writeBody(stream, body);
+                self.indent -= 2;
+                try stream.writeByteNTimes(' ', self.indent);
+                try stream.writeAll("}");
+            }
+        }
+        try stream.writeAll(") ");
         try self.writeSrc(stream, inst_data.src());
     }
 
BRANCH_TODO
@@ -35,3 +35,94 @@ Performance optimizations to look into:
    var decl and assignment instructions, etc.
    - make it set sema.src where appropriate
  * look into not emitting redundant dbg stmts to TZIR
+ * make decl references in ZIR be u32 indexes to the Decl dependencies array hash map
+   instead of duplicating *Decl entries in zir.Code.
+
+                    if (maybe_src) |previous_src| {
+                        return sema.mod.fail(&block.base, item.src, "duplicate switch value", .{});
+                        // TODO notes "previous value is here" previous_src
+                    }
+
+                    const item = try sema.resolveInst(item_ref);
+                    const value = try sema.resolveConstValue(block, item.src, item);
+                    const maybe_src = try range_set.add(value, value, item.src);
+                    try sema.validateSwitchDupeValue(parent_block, maybe_src, item.src);
+
+
+                    const first = try sema.resolveInst(item_first);
+                    const last = try sema.resolveInst(item_last);
+                    const maybe_src = try range_set.add(
+                            try sema.resolveConstValue(block, range_first_src, first_casted),
+                            try sema.resolveConstValue(block, range_last_src, last_casted),
+                            item.src,
+                        );
+                    };
+
+
+                    const item = try sema.resolveInst(item_ref);
+                    if ((try sema.resolveConstValue(block, item.src, item)).toBool()) {
+                        true_count += 1;
+                    } else {
+                        false_count += 1;
+                    }
+                    if (true_count + false_count > 2) {
+                        return sema.mod.fail(&block.base, item.src, "duplicate switch value", .{});
+                    }
+
+
+
+            for (inst.positionals.items) |item| {
+                const resolved = try sema.resolveInst(item);
+                const casted = try sema.coerce(block, operand.ty, resolved);
+                const val = try sema.resolveConstValue(block, item_src, casted);
+
+                if (try seen_values.fetchPut(val, item.src)) |prev| {
+                    return sema.mod.fail(&block.base, item.src, "duplicate switch value", .{});
+                    // TODO notes "previous value here" prev.value
+                }
+            }
+
+
+
+
+
+
+fn switchCaseExpr(
+    gz: *GenZir,
+    scope: *Scope,
+    rl: ResultLoc,
+    block: *zir.Inst.Block,
+    case: ast.full.SwitchCase,
+    target: zir.Inst.Ref,
+) !void {
+    const tree = gz.tree();
+    const node_datas = tree.nodes.items(.data);
+    const main_tokens = tree.nodes.items(.main_token);
+    const token_tags = tree.tokens.items(.tag);
+
+    const case_src = token_starts[case.ast.arrow_token];
+    const sub_scope = blk: {
+        const payload_token = case.payload_token orelse break :blk scope;
+        const ident = if (token_tags[payload_token] == .asterisk)
+            payload_token + 1
+        else
+            payload_token;
+        const is_ptr = ident != payload_token;
+        const value_name = tree.tokenSlice(ident);
+        if (mem.eql(u8, value_name, "_")) {
+            if (is_ptr) {
+                return mod.failTok(scope, payload_token, "pointer modifier invalid on discard", .{});
+            }
+            break :blk scope;
+        }
+        return mod.failTok(scope, ident, "TODO implement switch value payload", .{});
+    };
+
+    const case_body = try expr(gz, sub_scope, rl, case.ast.target_expr);
+    if (!case_body.tag.isNoReturn()) {
+        _ = try addZIRInst(mod, sub_scope, case_src, zir.Inst.Break, .{
+            .block = block,
+            .operand = case_body,
+        }, .{});
+    }
+}