stage2: reimplement switch

This commit is contained in:
Veikka Tuominen 2021-02-01 15:45:11 +02:00
parent 3ec5c9a3bc
commit 75acfcf0ea
No known key found for this signature in database
GPG Key ID: 59AEB8936E16A6AC
4 changed files with 606 additions and 5 deletions

View File

@ -309,7 +309,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
.Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?),
.Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?),
.OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?),
.Switch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Switch", .{}),
.Switch => return switchExpr(mod, scope, rl, node.castTag(.Switch).?),
.ContainerDecl => return containerDecl(mod, scope, rl, node.castTag(.ContainerDecl).?),
.Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}),
@ -2246,6 +2246,317 @@ fn forExpr(
);
}
fn switchCaseUsesRef(node: *ast.Node.Switch) bool {
for (node.cases()) |uncasted_case| {
const case = uncasted_case.castTag(.SwitchCase).?;
const uncasted_payload = case.payload orelse continue;
const payload = uncasted_payload.castTag(.PointerPayload).?;
if (payload.ptr_token) |_| return true;
}
return false;
}
fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp {
var cur = node;
while (true) {
switch (cur.tag) {
.Range => return @fieldParentPtr(ast.Node.SimpleInfixOp, "base", cur),
.GroupedExpression => cur = @fieldParentPtr(ast.Node.GroupedExpression, "base", cur).expr,
else => return null,
}
}
}
fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst {
const tree = scope.tree();
const switch_src = tree.token_locs[switch_node.switch_token].start;
const use_ref = switchCaseUsesRef(switch_node);
var block_scope: Scope.GenZIR = .{
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
.force_comptime = scope.isComptime(),
.instructions = .{},
};
setBlockResultLoc(&block_scope, rl);
defer block_scope.instructions.deinit(mod.gpa);
var items = std.ArrayList(*zir.Inst).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;
for (switch_node.cases()) |uncasted_case| {
const case = uncasted_case.castTag(.SwitchCase).?;
const case_src = tree.token_locs[case.firstToken()].start;
assert(case.items_len != 0);
// Check for else/_ prong, those are handled last.
if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) {
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.items_len == 1 and case.items()[0].tag == .Identifier and
mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_"))
{
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,
switch_src,
"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.items_len == 1 and getRangeNode(case.items()[0]) == null) simple_case_count += 1;
// generate all the switch items as comptime expressions
for (case.items()) |item| {
if (getRangeNode(item)) |range| {
const start = try comptimeExpr(mod, &block_scope.base, .none, range.lhs);
const end = try comptimeExpr(mod, &block_scope.base, .none, range.rhs);
const range_src = tree.token_locs[range.op_token].start;
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(mod, &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 target_ptr = if (use_ref) try expr(mod, &block_scope.base, .ref, switch_node.expr) else null;
const target = if (target_ptr) |some|
try addZIRUnOp(mod, &block_scope.base, some.src, .deref, some)
else
try expr(mod, &block_scope.base, .none, switch_node.expr);
const switch_inst = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{
.target = target,
.cases = cases,
.items = try block_scope.arena.dupe(*zir.Inst, 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, block_scope.instructions.items),
});
var case_scope: Scope.GenZIR = .{
.parent = scope,
.decl = block_scope.decl,
.arena = block_scope.arena,
.force_comptime = block_scope.force_comptime,
.instructions = .{},
};
defer case_scope.instructions.deinit(mod.gpa);
var else_scope: 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.Node.SwitchCase = null;
var items_index: usize = 0;
var case_index: usize = 0;
for (switch_node.cases()) |uncasted_case| {
const case = uncasted_case.castTag(.SwitchCase).?;
const case_src = tree.token_locs[case.firstToken()].start;
// reset without freeing to reduce allocations.
case_scope.instructions.items.len = 0;
// Check for else/_ prong, those are handled last.
if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) {
special_case = case;
continue;
} else if (case.items_len == 1 and case.items()[0].tag == .Identifier and
mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_"))
{
special_case = case;
continue;
}
// If this is a simple one item prong then it is handled by the switchbr.
if (case.items_len == 1 and getRangeNode(case.items()[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, target_ptr);
cases[case_index] = .{
.item = item,
.body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items) },
};
case_index += 1;
continue;
}
// TODO if the case has few items and no ranges it might be better
// to just handle them as switch prongs.
// 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)
var any_ok: ?*zir.Inst = null;
for (case.items()) |item| {
if (getRangeNode(item)) |range| {
const range_src = tree.token_locs[range.op_token].start;
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 scope.arena().dupe(*zir.Inst, 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, target_ptr);
condbr.positionals.then_body = .{
.instructions = try scope.arena().dupe(*zir.Inst, 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 scope.arena().dupe(*zir.Inst, 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, target_ptr);
} else {
// Not handling all possible cases is a compile error.
_ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreachable_unsafe);
}
switch_inst.castTag(.switchbr).?.positionals.else_body = .{
.instructions = try block_scope.arena.dupe(*zir.Inst, else_scope.instructions.items),
};
return &block.base;
}
fn switchCaseExpr(
mod: *Module,
scope: *Scope,
rl: ResultLoc,
block: *zir.Inst.Block,
case: *ast.Node.SwitchCase,
target: *zir.Inst,
target_ptr: ?*zir.Inst,
) !void {
const tree = scope.tree();
const case_src = tree.token_locs[case.firstToken()].start;
const sub_scope = blk: {
const uncasted_payload = case.payload orelse break :blk scope;
const payload = uncasted_payload.castTag(.PointerPayload).?;
const is_ptr = payload.ptr_token != null;
const value_name = tree.tokenSlice(payload.value_symbol.firstToken());
if (mem.eql(u8, value_name, "_")) {
if (is_ptr) {
return mod.failTok(scope, payload.ptr_token.?, "pointer modifier invalid on discard", .{});
}
break :blk scope;
}
return mod.failNode(scope, payload.value_symbol, "TODO implement switch value payload", .{});
};
const case_body = try expr(mod, sub_scope, rl, case.expr);
if (!case_body.tag.isNoReturn()) {
_ = try addZIRInst(mod, sub_scope, case_src, zir.Inst.Break, .{
.block = block,
.operand = case_body,
}, .{});
}
}
fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[cfe.ltoken].start;

View File

@ -414,11 +414,13 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
.loop => try genLoop(o, inst.castTag(.loop).?),
.condbr => try genCondBr(o, inst.castTag(.condbr).?),
.br => try genBr(o, inst.castTag(.br).?),
.brvoid => try genBrVoid(o, inst.castTag(.brvoid).?.block),
.br_void => try genBrVoid(o, inst.castTag(.br_void).?.block),
.switchbr => try genSwitchBr(o, inst.castTag(.switchbr).?),
// booland and boolor are non-short-circuit operations
.booland, .bitand => try genBinOp(o, inst.castTag(.booland).?, " & "),
.boolor, .bitor => try genBinOp(o, inst.castTag(.boolor).?, " | "),
// bool_and and bool_or are non-short-circuit operations
.bool_and => try genBinOp(o, inst.castTag(.bool_and).?, " & "),
.bool_or => try genBinOp(o, inst.castTag(.bool_or).?, " | "),
.bit_and => try genBinOp(o, inst.castTag(.bit_and).?, " & "),
.bit_or => try genBinOp(o, inst.castTag(.bit_or).?, " | "),
.xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "),
.not => try genUnOp(o, inst.castTag(.not).?, "!"),
else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}),

View File

@ -338,6 +338,12 @@ pub const Inst = struct {
enum_type,
/// Does nothing; returns a void value.
void_value,
/// A switch expression.
switchbr,
/// A range in a switch case, `lhs...rhs`.
/// Only checks that `lhs >= rhs` if they are ints, everything else is
/// validated by the .switch instruction.
switch_range,
pub fn Type(tag: Tag) type {
return switch (tag) {
@ -435,6 +441,7 @@ pub const Inst = struct {
.error_union_type,
.merge_error_sets,
.slice_start,
.switch_range,
=> BinOp,
.block,
@ -478,6 +485,7 @@ pub const Inst = struct {
.enum_type => EnumType,
.union_type => UnionType,
.struct_type => StructType,
.switchbr => SwitchBr,
};
}
@ -605,6 +613,8 @@ pub const Inst = struct {
.union_type,
.struct_type,
.void_value,
.switch_range,
.switchbr,
=> false,
.@"break",
@ -1171,6 +1181,36 @@ pub const Inst = struct {
none,
};
};
pub const SwitchBr = struct {
pub const base_tag = Tag.switchbr;
base: Inst,
positionals: struct {
target: *Inst,
/// List of all individual items and ranges
items: []*Inst,
cases: []Case,
else_body: Body,
},
kw_args: struct {
/// Pointer to first range if such exists.
range: ?*Inst = null,
special_prong: SpecialProng = .none,
},
// Not anonymous due to stage1 limitations
pub const SpecialProng = enum {
none,
@"else",
underscore,
};
pub const Case = struct {
item: *Inst,
body: Body,
};
};
};
pub const ErrorMsg = struct {
@ -1431,6 +1471,26 @@ const Writer = struct {
}
try stream.writeByte(']');
},
[]Inst.SwitchBr.Case => {
if (param.len == 0) {
return stream.writeAll("{}");
}
try stream.writeAll("{\n");
for (param) |*case, i| {
if (i != 0) {
try stream.writeAll(",\n");
}
try stream.writeByteNTimes(' ', self.indent);
self.indent += 2;
try self.writeParamToStream(stream, &case.item);
try stream.writeAll(" => ");
try self.writeParamToStream(stream, &case.body);
self.indent -= 2;
}
try stream.writeByte('\n');
try stream.writeByteNTimes(' ', self.indent - 2);
try stream.writeByte('}');
},
else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)),
}
}

View File

@ -154,6 +154,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
.bool_and => return zirBoolOp(mod, scope, old_inst.castTag(.bool_and).?),
.bool_or => return zirBoolOp(mod, scope, old_inst.castTag(.bool_or).?),
.void_value => return mod.constVoid(scope, old_inst.src),
.switchbr => return zirSwitchBr(mod, scope, old_inst.castTag(.switchbr).?),
.switch_range => return zirSwitchRange(mod, scope, old_inst.castTag(.switch_range).?),
.container_field_named,
.container_field_typed,
@ -1535,6 +1537,232 @@ fn zirSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!
return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null);
}
fn zirSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
const start = try resolveInst(mod, scope, inst.positionals.lhs);
const end = try resolveInst(mod, scope, inst.positionals.rhs);
switch (start.ty.zigTypeTag()) {
.Int, .ComptimeInt => {},
else => return mod.constVoid(scope, inst.base.src),
}
switch (end.ty.zigTypeTag()) {
.Int, .ComptimeInt => {},
else => return mod.constVoid(scope, inst.base.src),
}
// .switch_range must be inside a comptime scope
const start_val = start.value().?;
const end_val = end.value().?;
if (start_val.compare(.gte, end_val)) {
return mod.fail(scope, inst.base.src, "range start value must be smaller than the end value", .{});
}
return mod.constVoid(scope, inst.base.src);
}
fn zirSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
const target = try resolveInst(mod, scope, inst.positionals.target);
try validateSwitch(mod, scope, target, inst);
if (try mod.resolveDefinedValue(scope, target)) |target_val| {
for (inst.positionals.cases) |case| {
const resolved = try resolveInst(mod, scope, case.item);
const casted = try mod.coerce(scope, target.ty, resolved);
const item = try mod.resolveConstValue(scope, casted);
if (target_val.eql(item)) {
try analyzeBody(mod, scope.cast(Scope.Block).?, case.body);
return mod.constNoReturn(scope, inst.base.src);
}
}
try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body);
return mod.constNoReturn(scope, inst.base.src);
}
if (inst.positionals.cases.len == 0) {
// no cases just analyze else_branch
try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body);
return mod.constNoReturn(scope, inst.base.src);
}
const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src);
const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len);
var case_block: Scope.Block = .{
.parent = parent_block,
.inst_table = parent_block.inst_table,
.func = parent_block.func,
.owner_decl = parent_block.owner_decl,
.src_decl = parent_block.src_decl,
.instructions = .{},
.arena = parent_block.arena,
.inlining = parent_block.inlining,
.is_comptime = parent_block.is_comptime,
.branch_quota = parent_block.branch_quota,
};
defer case_block.instructions.deinit(mod.gpa);
for (inst.positionals.cases) |case, i| {
// Reset without freeing.
case_block.instructions.items.len = 0;
const resolved = try resolveInst(mod, scope, case.item);
const casted = try mod.coerce(scope, target.ty, resolved);
const item = try mod.resolveConstValue(scope, casted);
try analyzeBody(mod, &case_block, case.body);
cases[i] = .{
.item = item,
.body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) },
};
}
case_block.instructions.items.len = 0;
try analyzeBody(mod, &case_block, inst.positionals.else_body);
const else_body: ir.Body = .{
.instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items),
};
return mod.addSwitchBr(parent_block, inst.base.src, target, cases, else_body);
}
fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void {
// validate usage of '_' prongs
if (inst.kw_args.special_prong == .underscore and target.ty.zigTypeTag() != .Enum) {
return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{});
// TODO notes "'_' prong here" inst.positionals.cases[last].src
}
// check that target type supports ranges
if (inst.kw_args.range) |range_inst| {
switch (target.ty.zigTypeTag()) {
.Int, .ComptimeInt => {},
else => {
return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty});
// TODO notes "range used here" range_inst.src
},
}
}
// validate for duplicate items/missing else prong
switch (target.ty.zigTypeTag()) {
.Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}),
.ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}),
.Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}),
.Int, .ComptimeInt => {
var range_set = @import("RangeSet.zig").init(mod.gpa);
defer range_set.deinit();
for (inst.positionals.items) |item| {
const maybe_src = if (item.castTag(.switch_range)) |range| blk: {
const start_resolved = try resolveInst(mod, scope, range.positionals.lhs);
const start_casted = try mod.coerce(scope, target.ty, start_resolved);
const end_resolved = try resolveInst(mod, scope, range.positionals.rhs);
const end_casted = try mod.coerce(scope, target.ty, end_resolved);
break :blk try range_set.add(
try mod.resolveConstValue(scope, start_casted),
try mod.resolveConstValue(scope, end_casted),
item.src,
);
} else blk: {
const resolved = try resolveInst(mod, scope, item);
const casted = try mod.coerce(scope, target.ty, resolved);
const value = try mod.resolveConstValue(scope, casted);
break :blk try range_set.add(value, value, item.src);
};
if (maybe_src) |previous_src| {
return mod.fail(scope, item.src, "duplicate switch value", .{});
// TODO notes "previous value is here" previous_src
}
}
if (target.ty.zigTypeTag() == .Int) {
var arena = std.heap.ArenaAllocator.init(mod.gpa);
defer arena.deinit();
const start = try target.ty.minInt(&arena, mod.getTarget());
const end = try target.ty.maxInt(&arena, mod.getTarget());
if (try range_set.spans(start, end)) {
if (inst.kw_args.special_prong == .@"else") {
return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
}
return;
}
}
if (inst.kw_args.special_prong != .@"else") {
return mod.fail(scope, inst.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 resolveInst(mod, scope, item);
const casted = try mod.coerce(scope, Type.initTag(.bool), resolved);
if ((try mod.resolveConstValue(scope, casted)).toBool()) {
true_count += 1;
} else {
false_count += 1;
}
if (true_count + false_count > 2) {
return mod.fail(scope, item.src, "duplicate switch value", .{});
}
}
if ((true_count + false_count < 2) and inst.kw_args.special_prong != .@"else") {
return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
}
if ((true_count + false_count == 2) and inst.kw_args.special_prong == .@"else") {
return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
}
},
.EnumLiteral, .Void, .Fn, .Pointer, .Type => {
if (inst.kw_args.special_prong != .@"else") {
return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty});
}
var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa);
defer seen_values.deinit();
for (inst.positionals.items) |item| {
const resolved = try resolveInst(mod, scope, item);
const casted = try mod.coerce(scope, target.ty, resolved);
const val = try mod.resolveConstValue(scope, casted);
if (try seen_values.fetchPut(val, item.src)) |prev| {
return mod.fail(scope, item.src, "duplicate switch value", .{});
// TODO notes "previous value here" prev.value
}
}
},
.ErrorUnion,
.NoReturn,
.Array,
.Struct,
.Undefined,
.Null,
.Optional,
.BoundFn,
.Opaque,
.Vector,
.Frame,
.AnyFrame,
.ComptimeFloat,
.Float,
=> {
return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty});
},
}
}
fn zirImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();