Commit 195ddab2be
Changed files (7)
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,
+ }, .{});
+ }
+}