Merge pull request #12979 from Vexu/inline-switch

Implement inline switch cases
This commit is contained in:
Andrew Kelley 2022-10-03 23:43:09 -04:00 committed by GitHub
commit ff534d2267
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 964 additions and 222 deletions

View File

@ -4255,6 +4255,134 @@ test "enum literals with switch" {
}
{#code_end#}
{#header_close#}
{#header_open|Inline switch#}
<p>
Switch prongs can be marked as {#syntax#}inline{#endsyntax#} to generate
the prong's body for each possible value it could have:
</p>
{#code_begin|test|test_inline_switch#}
const std = @import("std");
const expect = std.testing.expect;
const expectError = std.testing.expectError;
fn isFieldOptional(comptime T: type, field_index: usize) !bool {
const fields = @typeInfo(T).Struct.fields;
return switch (field_index) {
// This prong is analyzed `fields.len - 1` times with `idx` being an
// unique comptime known value each time.
inline 0...fields.len - 1 => |idx| @typeInfo(fields[idx].field_type) == .Optional,
else => return error.IndexOutOfBounds,
};
}
const Struct1 = struct { a: u32, b: ?u32 };
test "using @typeInfo with runtime values" {
var index: usize = 0;
try expect(!try isFieldOptional(Struct1, index));
index += 1;
try expect(try isFieldOptional(Struct1, index));
index += 1;
try expectError(error.IndexOutOfBounds, isFieldOptional(Struct1, index));
}
// Calls to `isFieldOptional` on `Struct1` get unrolled to an equivalent
// of this function:
fn isFieldOptionalUnrolled(field_index: usize) !bool {
return switch (field_index) {
0 => false,
1 => true,
else => return error.IndexOutOfBounds,
};
}
{#code_end#}
<p>
{#syntax#}inline else{#endsyntax#} prongs can be used as a type safe
alternative to {#syntax#}inline for{#endsyntax#} loops:
</p>
{#code_begin|test|test_inline_else#}
const std = @import("std");
const expect = std.testing.expect;
const SliceTypeA = extern struct {
len: usize,
ptr: [*]u32,
};
const SliceTypeB = extern struct {
ptr: [*]SliceTypeA,
len: usize,
};
const AnySlice = union(enum) {
a: SliceTypeA,
b: SliceTypeB,
c: []const u8,
d: []AnySlice,
};
fn withFor(any: AnySlice) usize {
const Tag = @typeInfo(AnySlice).Union.tag_type.?;
inline for (@typeInfo(Tag).Enum.fields) |field| {
// With `inline for` the function gets generated as
// a series of `if` statements relying on the optimizer
// to convert it to a switch.
if (field.value == @enumToInt(any)) {
return @field(any, field.name).len;
}
}
// When using `inline for` the compiler doesn't know that every
// possible case has been handled requiring an explicit `unreachable`.
unreachable;
}
fn withSwitch(any: AnySlice) usize {
return switch (any) {
// With `inline else` the function is explicitly generated
// as the desired switch and the compiler can check that
// every possible case is handled.
inline else => |slice| slice.len,
};
}
test "inline for and inline else similarity" {
var any = AnySlice{ .c = "hello" };
try expect(withFor(any) == 5);
try expect(withSwitch(any) == 5);
}
{#code_end#}
<p>
When using an inline prong switching on an union an additional
capture can be used to obtain the union's enum tag value.
</p>
{#code_begin|test|test_inline_switch_union_tag#}
const std = @import("std");
const expect = std.testing.expect;
const U = union(enum) {
a: u32,
b: f32,
};
fn getNum(u: U) u32 {
switch (u) {
// Here `num` is a runtime known value that is either
// `u.a` or `u.b` and `tag` is `u`'s comptime known tag value.
inline else => |num, tag| {
if (tag == .b) {
return @floatToInt(u32, num);
}
return num;
}
}
}
test "test" {
var u = U{ .b = 42 };
try expect(getNum(u) == 42);
}
{#code_end#}
{#see_also|inline while|inline for#}
{#header_close#}
{#header_close#}
{#header_open|while#}

View File

@ -643,11 +643,23 @@ pub fn firstToken(tree: Ast, node: Node.Index) TokenIndex {
n = datas[n].lhs;
}
},
.switch_case_inline_one => {
if (datas[n].lhs == 0) {
return main_tokens[n] - 2 - end_offset; // else token
} else {
return firstToken(tree, datas[n].lhs) - 1;
}
},
.switch_case => {
const extra = tree.extraData(datas[n].lhs, Node.SubRange);
assert(extra.end - extra.start > 0);
n = tree.extra_data[extra.start];
},
.switch_case_inline => {
const extra = tree.extraData(datas[n].lhs, Node.SubRange);
assert(extra.end - extra.start > 0);
return firstToken(tree, tree.extra_data[extra.start]) - 1;
},
.asm_output, .asm_input => {
assert(token_tags[main_tokens[n] - 1] == .l_bracket);
@ -763,7 +775,9 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex {
.ptr_type_bit_range,
.array_type,
.switch_case_one,
.switch_case_inline_one,
.switch_case,
.switch_case_inline,
.switch_range,
=> n = datas[n].rhs,
@ -1755,7 +1769,7 @@ pub fn switchCaseOne(tree: Ast, node: Node.Index) full.SwitchCase {
.values = if (data.lhs == 0) values[0..0] else values[0..1],
.arrow_token = tree.nodes.items(.main_token)[node],
.target_expr = data.rhs,
});
}, node);
}
pub fn switchCase(tree: Ast, node: Node.Index) full.SwitchCase {
@ -1765,7 +1779,7 @@ pub fn switchCase(tree: Ast, node: Node.Index) full.SwitchCase {
.values = tree.extra_data[extra.start..extra.end],
.arrow_token = tree.nodes.items(.main_token)[node],
.target_expr = data.rhs,
});
}, node);
}
pub fn asmSimple(tree: Ast, node: Node.Index) full.Asm {
@ -2038,15 +2052,21 @@ fn fullContainerDecl(tree: Ast, info: full.ContainerDecl.Components) full.Contai
return result;
}
fn fullSwitchCase(tree: Ast, info: full.SwitchCase.Components) full.SwitchCase {
fn fullSwitchCase(tree: Ast, info: full.SwitchCase.Components, node: Node.Index) full.SwitchCase {
const token_tags = tree.tokens.items(.tag);
const node_tags = tree.nodes.items(.tag);
var result: full.SwitchCase = .{
.ast = info,
.payload_token = null,
.inline_token = null,
};
if (token_tags[info.arrow_token + 1] == .pipe) {
result.payload_token = info.arrow_token + 2;
}
switch (node_tags[node]) {
.switch_case_inline, .switch_case_inline_one => result.inline_token = firstToken(tree, node),
else => {},
}
return result;
}
@ -2454,6 +2474,7 @@ pub const full = struct {
};
pub const SwitchCase = struct {
inline_token: ?TokenIndex,
/// Points to the first token after the `|`. Will either be an identifier or
/// a `*` (with an identifier immediately after it).
payload_token: ?TokenIndex,
@ -2847,9 +2868,13 @@ pub const Node = struct {
/// `lhs => rhs`. If lhs is omitted it means `else`.
/// main_token is the `=>`
switch_case_one,
/// Same ast `switch_case_one` but the case is inline
switch_case_inline_one,
/// `a, b, c => rhs`. `SubRange[lhs]`.
/// main_token is the `=>`
switch_case,
/// Same ast `switch_case` but the case is inline
switch_case_inline,
/// `lhs...rhs`.
switch_range,
/// `while (lhs) rhs`.

View File

@ -3100,7 +3100,7 @@ const Parser = struct {
return identifier;
}
/// SwitchProng <- SwitchCase EQUALRARROW PtrPayload? AssignExpr
/// SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrIndexPayload? AssignExpr
/// SwitchCase
/// <- SwitchItem (COMMA SwitchItem)* COMMA?
/// / KEYWORD_else
@ -3108,6 +3108,8 @@ const Parser = struct {
const scratch_top = p.scratch.items.len;
defer p.scratch.shrinkRetainingCapacity(scratch_top);
const is_inline = p.eatToken(.keyword_inline) != null;
if (p.eatToken(.keyword_else) == null) {
while (true) {
const item = try p.parseSwitchItem();
@ -3115,15 +3117,18 @@ const Parser = struct {
try p.scratch.append(p.gpa, item);
if (p.eatToken(.comma) == null) break;
}
if (scratch_top == p.scratch.items.len) return null_node;
if (scratch_top == p.scratch.items.len) {
if (is_inline) p.tok_i -= 1;
return null_node;
}
}
const arrow_token = try p.expectToken(.equal_angle_bracket_right);
_ = try p.parsePtrPayload();
_ = try p.parsePtrIndexPayload();
const items = p.scratch.items[scratch_top..];
switch (items.len) {
0 => return p.addNode(.{
.tag = .switch_case_one,
.tag = if (is_inline) .switch_case_inline_one else .switch_case_one,
.main_token = arrow_token,
.data = .{
.lhs = 0,
@ -3131,7 +3136,7 @@ const Parser = struct {
},
}),
1 => return p.addNode(.{
.tag = .switch_case_one,
.tag = if (is_inline) .switch_case_inline_one else .switch_case_one,
.main_token = arrow_token,
.data = .{
.lhs = items[0],
@ -3139,7 +3144,7 @@ const Parser = struct {
},
}),
else => return p.addNode(.{
.tag = .switch_case,
.tag = if (is_inline) .switch_case_inline else .switch_case,
.main_token = arrow_token,
.data = .{
.lhs = try p.addExtra(try p.listToSpan(items)),

View File

@ -3276,6 +3276,8 @@ test "zig fmt: switch" {
\\ switch (u) {
\\ Union.Int => |int| {},
\\ Union.Float => |*float| unreachable,
\\ 1 => |a, b| unreachable,
\\ 2 => |*a, b| unreachable,
\\ }
\\}
\\

View File

@ -685,8 +685,8 @@ fn renderExpression(gpa: Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index,
return renderToken(ais, tree, tree.lastToken(node), space); // rbrace
},
.switch_case_one => return renderSwitchCase(gpa, ais, tree, tree.switchCaseOne(node), space),
.switch_case => return renderSwitchCase(gpa, ais, tree, tree.switchCase(node), space),
.switch_case_one, .switch_case_inline_one => return renderSwitchCase(gpa, ais, tree, tree.switchCaseOne(node), space),
.switch_case, .switch_case_inline => return renderSwitchCase(gpa, ais, tree, tree.switchCase(node), space),
.while_simple => return renderWhile(gpa, ais, tree, tree.whileSimple(node), space),
.while_cont => return renderWhile(gpa, ais, tree, tree.whileCont(node), space),
@ -1509,6 +1509,11 @@ fn renderSwitchCase(
break :blk hasComment(tree, tree.firstToken(switch_case.ast.values[0]), switch_case.ast.arrow_token);
};
// render inline keyword
if (switch_case.inline_token) |some| {
try renderToken(ais, tree, some, .space);
}
// Render everything before the arrow
if (switch_case.ast.values.len == 0) {
try renderToken(ais, tree, switch_case.ast.arrow_token - 1, .space); // else keyword
@ -1536,13 +1541,17 @@ fn renderSwitchCase(
if (switch_case.payload_token) |payload_token| {
try renderToken(ais, tree, payload_token - 1, .none); // pipe
const ident = payload_token + @boolToInt(token_tags[payload_token] == .asterisk);
if (token_tags[payload_token] == .asterisk) {
try renderToken(ais, tree, payload_token, .none); // asterisk
try renderToken(ais, tree, payload_token + 1, .none); // identifier
try renderToken(ais, tree, payload_token + 2, pre_target_space); // pipe
}
try renderToken(ais, tree, ident, .none); // identifier
if (token_tags[ident + 1] == .comma) {
try renderToken(ais, tree, ident + 1, .space); // ,
try renderToken(ais, tree, ident + 2, .none); // identifier
try renderToken(ais, tree, ident + 3, pre_target_space); // pipe
} else {
try renderToken(ais, tree, payload_token, .none); // identifier
try renderToken(ais, tree, payload_token + 1, pre_target_space); // pipe
try renderToken(ais, tree, ident + 1, pre_target_space); // pipe
}
}

View File

@ -386,7 +386,9 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins
.simple_var_decl => unreachable,
.aligned_var_decl => unreachable,
.switch_case => unreachable,
.switch_case_inline => unreachable,
.switch_case_one => unreachable,
.switch_case_inline_one => unreachable,
.container_field_init => unreachable,
.container_field_align => unreachable,
.container_field => unreachable,
@ -600,7 +602,9 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
.@"errdefer" => unreachable, // Handled in `blockExpr`.
.switch_case => unreachable, // Handled in `switchExpr`.
.switch_case_inline => unreachable, // Handled in `switchExpr`.
.switch_case_one => unreachable, // Handled in `switchExpr`.
.switch_case_inline_one => unreachable, // Handled in `switchExpr`.
.switch_range => unreachable, // Handled in `switchExpr`.
.asm_output => unreachable, // Handled in `asmExpr`.
@ -2369,6 +2373,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.switch_capture_ref,
.switch_capture_multi,
.switch_capture_multi_ref,
.switch_capture_tag,
.struct_init_empty,
.struct_init,
.struct_init_ref,
@ -6213,14 +6218,15 @@ fn switchExpr(
var any_payload_is_ref = false;
var scalar_cases_len: u32 = 0;
var multi_cases_len: u32 = 0;
var inline_cases_len: u32 = 0;
var special_prong: Zir.SpecialProng = .none;
var special_node: Ast.Node.Index = 0;
var else_src: ?Ast.TokenIndex = null;
var underscore_src: ?Ast.TokenIndex = null;
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),
.switch_case_one, .switch_case_inline_one => tree.switchCaseOne(case_node),
.switch_case, .switch_case_inline => tree.switchCase(case_node),
else => unreachable,
};
if (case.payload_token) |payload_token| {
@ -6304,6 +6310,9 @@ fn switchExpr(
},
);
}
if (case.inline_token != null) {
return astgen.failTok(case_src, "cannot inline '_' prong", .{});
}
special_node = case_node;
special_prong = .under;
underscore_src = case_src;
@ -6315,6 +6324,9 @@ fn switchExpr(
} else {
multi_cases_len += 1;
}
if (case.inline_token != null) {
inline_cases_len += 1;
}
}
const operand_rl: ResultLoc = if (any_payload_is_ref) .ref else .none;
@ -6354,8 +6366,8 @@ fn switchExpr(
var scalar_case_index: u32 = 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),
.switch_case_one, .switch_case_inline_one => tree.switchCaseOne(case_node),
.switch_case, .switch_case_inline => tree.switchCase(case_node),
else => unreachable,
};
@ -6364,8 +6376,12 @@ fn switchExpr(
var dbg_var_name: ?u32 = null;
var dbg_var_inst: Zir.Inst.Ref = undefined;
var dbg_var_tag_name: ?u32 = null;
var dbg_var_tag_inst: Zir.Inst.Ref = undefined;
var capture_inst: Zir.Inst.Index = 0;
var tag_inst: Zir.Inst.Index = 0;
var capture_val_scope: Scope.LocalVal = undefined;
var tag_scope: Scope.LocalVal = undefined;
const sub_scope = blk: {
const payload_token = case.payload_token orelse break :blk &case_scope.base;
const ident = if (token_tags[payload_token] == .asterisk)
@ -6373,59 +6389,96 @@ fn switchExpr(
else
payload_token;
const is_ptr = ident != payload_token;
if (mem.eql(u8, tree.tokenSlice(ident), "_")) {
const ident_slice = tree.tokenSlice(ident);
var payload_sub_scope: *Scope = undefined;
if (mem.eql(u8, ident_slice, "_")) {
if (is_ptr) {
return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{});
}
break :blk &case_scope.base;
}
if (case_node == special_node) {
const capture_tag: Zir.Inst.Tag = if (is_ptr)
.switch_capture_ref
else
.switch_capture;
capture_inst = @intCast(Zir.Inst.Index, astgen.instructions.len);
try astgen.instructions.append(gpa, .{
.tag = capture_tag,
.data = .{
.switch_capture = .{
.switch_inst = switch_block,
// Max int communicates that this is the else/underscore prong.
.prong_index = std.math.maxInt(u32),
},
},
});
payload_sub_scope = &case_scope.base;
} else {
const is_multi_case_bits: u2 = @boolToInt(is_multi_case);
const is_ptr_bits: u2 = @boolToInt(is_ptr);
const capture_tag: Zir.Inst.Tag = switch ((is_multi_case_bits << 1) | is_ptr_bits) {
0b00 => .switch_capture,
0b01 => .switch_capture_ref,
0b10 => .switch_capture_multi,
0b11 => .switch_capture_multi_ref,
if (case_node == special_node) {
const capture_tag: Zir.Inst.Tag = if (is_ptr)
.switch_capture_ref
else
.switch_capture;
capture_inst = @intCast(Zir.Inst.Index, astgen.instructions.len);
try astgen.instructions.append(gpa, .{
.tag = capture_tag,
.data = .{
.switch_capture = .{
.switch_inst = switch_block,
// Max int communicates that this is the else/underscore prong.
.prong_index = std.math.maxInt(u32),
},
},
});
} else {
const is_multi_case_bits: u2 = @boolToInt(is_multi_case);
const is_ptr_bits: u2 = @boolToInt(is_ptr);
const capture_tag: Zir.Inst.Tag = switch ((is_multi_case_bits << 1) | is_ptr_bits) {
0b00 => .switch_capture,
0b01 => .switch_capture_ref,
0b10 => .switch_capture_multi,
0b11 => .switch_capture_multi_ref,
};
const capture_index = if (is_multi_case) multi_case_index else scalar_case_index;
capture_inst = @intCast(Zir.Inst.Index, astgen.instructions.len);
try astgen.instructions.append(gpa, .{
.tag = capture_tag,
.data = .{ .switch_capture = .{
.switch_inst = switch_block,
.prong_index = capture_index,
} },
});
}
const capture_name = try astgen.identAsString(ident);
try astgen.detectLocalShadowing(&case_scope.base, capture_name, ident, ident_slice);
capture_val_scope = .{
.parent = &case_scope.base,
.gen_zir = &case_scope,
.name = capture_name,
.inst = indexToRef(capture_inst),
.token_src = payload_token,
.id_cat = .@"capture",
};
const capture_index = if (is_multi_case) multi_case_index else scalar_case_index;
capture_inst = @intCast(Zir.Inst.Index, astgen.instructions.len);
try astgen.instructions.append(gpa, .{
.tag = capture_tag,
.data = .{ .switch_capture = .{
.switch_inst = switch_block,
.prong_index = capture_index,
} },
});
dbg_var_name = capture_name;
dbg_var_inst = indexToRef(capture_inst);
payload_sub_scope = &capture_val_scope.base;
}
const capture_name = try astgen.identAsString(ident);
capture_val_scope = .{
.parent = &case_scope.base,
const tag_token = if (token_tags[ident + 1] == .comma)
ident + 2
else
break :blk payload_sub_scope;
const tag_slice = tree.tokenSlice(tag_token);
if (mem.eql(u8, tag_slice, "_")) {
return astgen.failTok(tag_token, "discard of tag capture; omit it instead", .{});
} else if (case.inline_token == null) {
return astgen.failTok(tag_token, "tag capture on non-inline prong", .{});
}
const tag_name = try astgen.identAsString(tag_token);
try astgen.detectLocalShadowing(payload_sub_scope, tag_name, tag_token, tag_slice);
tag_inst = @intCast(Zir.Inst.Index, astgen.instructions.len);
try astgen.instructions.append(gpa, .{
.tag = .switch_capture_tag,
.data = .{ .un_tok = .{
.operand = cond,
.src_tok = case_scope.tokenIndexToRelative(tag_token),
} },
});
tag_scope = .{
.parent = payload_sub_scope,
.gen_zir = &case_scope,
.name = capture_name,
.inst = indexToRef(capture_inst),
.token_src = payload_token,
.id_cat = .@"capture",
.name = tag_name,
.inst = indexToRef(tag_inst),
.token_src = tag_token,
.id_cat = .@"switch tag capture",
};
dbg_var_name = capture_name;
dbg_var_inst = indexToRef(capture_inst);
break :blk &capture_val_scope.base;
dbg_var_tag_name = tag_name;
dbg_var_tag_inst = indexToRef(tag_inst);
break :blk &tag_scope.base;
};
const header_index = @intCast(u32, payloads.items.len);
@ -6480,10 +6533,14 @@ fn switchExpr(
defer case_scope.unstack();
if (capture_inst != 0) try case_scope.instructions.append(gpa, capture_inst);
if (tag_inst != 0) try case_scope.instructions.append(gpa, tag_inst);
try case_scope.addDbgBlockBegin();
if (dbg_var_name) |some| {
try case_scope.addDbgVar(.dbg_var_val, some, dbg_var_inst);
}
if (dbg_var_tag_name) |some| {
try case_scope.addDbgVar(.dbg_var_val, some, dbg_var_tag_inst);
}
const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr);
try checkUsed(parent_gz, &case_scope.base, sub_scope);
try case_scope.addDbgBlockEnd();
@ -6495,7 +6552,8 @@ fn switchExpr(
const case_slice = case_scope.instructionsSlice();
const body_len = astgen.countBodyLenAfterFixups(case_slice);
try payloads.ensureUnusedCapacity(gpa, body_len);
payloads.items[body_len_index] = body_len;
const inline_bit = @as(u32, @boolToInt(case.inline_token != null)) << 31;
payloads.items[body_len_index] = body_len | inline_bit;
appendBodyWithFixupsArrayList(astgen, payloads, case_slice);
}
}
@ -6509,7 +6567,6 @@ fn switchExpr(
const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{
.operand = cond,
.bits = Zir.Inst.SwitchBlock.Bits{
.is_ref = any_payload_is_ref,
.has_multi_cases = multi_cases_len != 0,
.has_else = special_prong == .@"else",
.has_under = special_prong == .under,
@ -6543,7 +6600,7 @@ fn switchExpr(
end_index += 3 + items_len + 2 * ranges_len;
}
const body_len = payloads.items[body_len_index];
const body_len = @truncate(u31, payloads.items[body_len_index]);
end_index += body_len;
switch (strat.tag) {
@ -8433,7 +8490,9 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index, have_
.@"usingnamespace",
.test_decl,
.switch_case,
.switch_case_inline,
.switch_case_one,
.switch_case_inline_one,
.container_field_init,
.container_field_align,
.container_field,
@ -8665,7 +8724,9 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) BuiltinFn.Ev
.@"usingnamespace",
.test_decl,
.switch_case,
.switch_case_inline,
.switch_case_one,
.switch_case_inline_one,
.container_field_init,
.container_field_align,
.container_field,
@ -8876,7 +8937,9 @@ fn nodeImpliesMoreThanOnePossibleValue(tree: *const Ast, start_node: Ast.Node.In
.@"usingnamespace",
.test_decl,
.switch_case,
.switch_case_inline,
.switch_case_one,
.switch_case_inline_one,
.container_field_init,
.container_field_align,
.container_field,
@ -9118,7 +9181,9 @@ fn nodeImpliesComptimeOnly(tree: *const Ast, start_node: Ast.Node.Index) bool {
.@"usingnamespace",
.test_decl,
.switch_case,
.switch_case_inline,
.switch_case_one,
.switch_case_inline_one,
.container_field_init,
.container_field_align,
.container_field,
@ -10051,6 +10116,7 @@ const Scope = struct {
@"local constant",
@"local variable",
@"loop index capture",
@"switch tag capture",
@"capture",
};

View File

@ -2445,8 +2445,8 @@ pub const SrcLoc = struct {
const case_nodes = tree.extra_data[extra.start..extra.end];
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),
.switch_case_one, .switch_case_inline_one => tree.switchCaseOne(case_node),
.switch_case, .switch_case_inline => tree.switchCase(case_node),
else => unreachable,
};
const is_special = (case.ast.values.len == 0) or
@ -2469,8 +2469,8 @@ pub const SrcLoc = struct {
const case_nodes = tree.extra_data[extra.start..extra.end];
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),
.switch_case_one, .switch_case_inline_one => tree.switchCaseOne(case_node),
.switch_case, .switch_case_inline => tree.switchCase(case_node),
else => unreachable,
};
const is_special = (case.ast.values.len == 0) or
@ -2491,8 +2491,8 @@ pub const SrcLoc = struct {
const case_node = src_loc.declRelativeToNodeIndex(node_off);
const node_tags = tree.nodes.items(.tag);
const case = switch (node_tags[case_node]) {
.switch_case_one => tree.switchCaseOne(case_node),
.switch_case => tree.switchCase(case_node),
.switch_case_one, .switch_case_inline_one => tree.switchCaseOne(case_node),
.switch_case, .switch_case_inline => tree.switchCase(case_node),
else => unreachable,
};
const start_tok = case.payload_token.?;
@ -5940,8 +5940,8 @@ pub const SwitchProngSrc = union(enum) {
var scalar_i: u32 = 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),
.switch_case_one, .switch_case_inline_one => tree.switchCaseOne(case_node),
.switch_case, .switch_case_inline => tree.switchCase(case_node),
else => unreachable,
};
if (case.ast.values.len == 0)

View File

@ -162,6 +162,9 @@ pub const Block = struct {
/// type of `err` in `else => |err|`
switch_else_err_ty: ?Type = null,
/// Value for switch_capture in an inline case
inline_case_capture: Air.Inst.Ref = .none,
const Param = struct {
/// `noreturn` means `anytype`.
ty: Type,
@ -603,6 +606,21 @@ fn resolveBody(
return try sema.resolveInst(break_data.operand);
}
fn analyzeBodyRuntimeBreak(sema: *Sema, block: *Block, body: []const Zir.Inst.Index) !void {
_ = sema.analyzeBodyInner(block, body) catch |err| switch (err) {
error.ComptimeBreak => {
const zir_datas = sema.code.instructions.items(.data);
const break_data = zir_datas[sema.comptime_break_inst].@"break";
try sema.addRuntimeBreak(block, .{
.block_inst = break_data.block_inst,
.operand = break_data.operand,
.inst = sema.comptime_break_inst,
});
},
else => |e| return e,
};
}
pub fn analyzeBody(
sema: *Sema,
block: *Block,
@ -796,6 +814,7 @@ fn analyzeBodyInner(
.switch_capture_ref => try sema.zirSwitchCapture(block, inst, false, true),
.switch_capture_multi => try sema.zirSwitchCapture(block, inst, true, false),
.switch_capture_multi_ref => try sema.zirSwitchCapture(block, inst, true, true),
.switch_capture_tag => try sema.zirSwitchCaptureTag(block, inst),
.type_info => try sema.zirTypeInfo(block, inst),
.size_of => try sema.zirSizeOf(block, inst),
.bit_size_of => try sema.zirBitSizeOf(block, inst),
@ -9030,13 +9049,38 @@ fn zirSwitchCapture(
const switch_info = zir_datas[capture_info.switch_inst].pl_node;
const switch_extra = sema.code.extraData(Zir.Inst.SwitchBlock, switch_info.payload_index);
const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = switch_info.src_node };
const operand_is_ref = switch_extra.data.bits.is_ref;
const cond_inst = Zir.refToIndex(switch_extra.data.operand).?;
const cond_info = sema.code.instructions.items(.data)[cond_inst].un_node;
const cond_info = zir_datas[cond_inst].un_node;
const cond_tag = sema.code.instructions.items(.tag)[cond_inst];
const operand_is_ref = cond_tag == .switch_cond_ref;
const operand_ptr = try sema.resolveInst(cond_info.operand);
const operand_ptr_ty = sema.typeOf(operand_ptr);
const operand_ty = if (operand_is_ref) operand_ptr_ty.childType() else operand_ptr_ty;
if (block.inline_case_capture != .none) {
const item_val = sema.resolveConstValue(block, .unneeded, block.inline_case_capture, undefined) catch unreachable;
if (operand_ty.zigTypeTag() == .Union) {
const field_index = @intCast(u32, operand_ty.unionTagFieldIndex(item_val, sema.mod).?);
const union_obj = operand_ty.cast(Type.Payload.Union).?.data;
const field_ty = union_obj.fields.values()[field_index].ty;
if (is_ref) {
const ptr_field_ty = try Type.ptr(sema.arena, sema.mod, .{
.pointee_type = field_ty,
.mutable = operand_ptr_ty.ptrIsMutable(),
.@"volatile" = operand_ptr_ty.isVolatilePtr(),
.@"addrspace" = operand_ptr_ty.ptrAddressSpace(),
});
return block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);
} else {
return block.addStructFieldVal(operand_ptr, field_index, field_ty);
}
} else if (is_ref) {
return sema.addConstantMaybeRef(block, operand_src, operand_ty, item_val, true);
} else {
return block.inline_case_capture;
}
}
const operand = if (operand_is_ref)
try sema.analyzeLoad(block, operand_src, operand_ptr, operand_src)
else
@ -9045,7 +9089,6 @@ fn zirSwitchCapture(
if (capture_info.prong_index == std.math.maxInt(@TypeOf(capture_info.prong_index))) {
// It is the else/`_` prong.
if (is_ref) {
assert(operand_is_ref);
return operand_ptr;
}
@ -9105,8 +9148,6 @@ fn zirSwitchCapture(
}
if (is_ref) {
assert(operand_is_ref);
const field_ty_ptr = try Type.ptr(sema.arena, sema.mod, .{
.pointee_type = first_field.ty,
.@"addrspace" = .generic,
@ -9167,7 +9208,6 @@ fn zirSwitchCapture(
// In this case the capture value is just the passed-through value of the
// switch condition.
if (is_ref) {
assert(operand_is_ref);
return operand_ptr;
} else {
return operand;
@ -9176,6 +9216,33 @@ fn zirSwitchCapture(
}
}
fn zirSwitchCaptureTag(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const zir_datas = sema.code.instructions.items(.data);
const inst_data = zir_datas[inst].un_tok;
const src = inst_data.src();
const switch_tag = sema.code.instructions.items(.tag)[Zir.refToIndex(inst_data.operand).?];
const is_ref = switch_tag == .switch_cond_ref;
const cond_data = zir_datas[Zir.refToIndex(inst_data.operand).?].un_node;
const operand_ptr = try sema.resolveInst(cond_data.operand);
const operand_ptr_ty = sema.typeOf(operand_ptr);
const operand_ty = if (is_ref) operand_ptr_ty.childType() else operand_ptr_ty;
if (operand_ty.zigTypeTag() != .Union) {
const msg = msg: {
const msg = try sema.errMsg(block, src, "cannot capture tag of non-union type '{}'", .{
operand_ty.fmt(sema.mod),
});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, operand_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(msg);
}
return block.inline_case_capture;
}
fn zirSwitchCond(
sema: *Sema,
block: *Block,
@ -9273,14 +9340,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
} else 0;
const special_prong = extra.data.bits.specialProng();
const special: struct { body: []const Zir.Inst.Index, end: usize } = switch (special_prong) {
.none => .{ .body = &.{}, .end = header_extra_index },
const special: struct { body: []const Zir.Inst.Index, end: usize, is_inline: bool } = switch (special_prong) {
.none => .{ .body = &.{}, .end = header_extra_index, .is_inline = false },
.under, .@"else" => blk: {
const body_len = sema.code.extra[header_extra_index];
const body_len = @truncate(u31, sema.code.extra[header_extra_index]);
const extra_body_start = header_extra_index + 1;
break :blk .{
.body = sema.code.extra[extra_body_start..][0..body_len],
.end = extra_body_start + body_len,
.is_inline = sema.code.extra[header_extra_index] >> 31 != 0,
};
},
};
@ -9292,8 +9360,19 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
break :blk sema.typeOf(raw_operand);
};
const union_originally = maybe_union_ty.zigTypeTag() == .Union;
var seen_union_fields: []?Module.SwitchProngSrc = &.{};
defer gpa.free(seen_union_fields);
// Duplicate checking variables later also used for `inline else`.
var seen_enum_fields: []?Module.SwitchProngSrc = &.{};
var seen_errors = SwitchErrorSet.init(gpa);
var range_set = RangeSet.init(gpa, sema.mod);
var true_count: u8 = 0;
var false_count: u8 = 0;
defer {
range_set.deinit();
gpa.free(seen_enum_fields);
seen_errors.deinit();
}
var empty_enum = false;
@ -9330,15 +9409,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
switch (operand_ty.zigTypeTag()) {
.Union => unreachable, // handled in zirSwitchCond
.Enum => {
var seen_fields = try gpa.alloc(?Module.SwitchProngSrc, operand_ty.enumFieldCount());
empty_enum = seen_fields.len == 0 and !operand_ty.isNonexhaustiveEnum();
defer if (!union_originally) gpa.free(seen_fields);
if (union_originally) seen_union_fields = seen_fields;
mem.set(?Module.SwitchProngSrc, seen_fields, null);
// This is used for non-exhaustive enum values that do not correspond to any tags.
var range_set = RangeSet.init(gpa, sema.mod);
defer range_set.deinit();
seen_enum_fields = try gpa.alloc(?Module.SwitchProngSrc, operand_ty.enumFieldCount());
empty_enum = seen_enum_fields.len == 0 and !operand_ty.isNonexhaustiveEnum();
mem.set(?Module.SwitchProngSrc, seen_enum_fields, null);
// `range_set` is used for non-exhaustive enum values that do not correspond to any tags.
var extra_index: usize = special.end;
{
@ -9346,13 +9420,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
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];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
extra_index += 1;
extra_index += body_len;
try sema.validateSwitchItemEnum(
block,
seen_fields,
seen_enum_fields,
&range_set,
item_ref,
src_node_offset,
@ -9367,7 +9441,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
const body_len = sema.code.extra[extra_index];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
extra_index += 1;
const items = sema.code.refSlice(extra_index, items_len);
extra_index += items_len + body_len;
@ -9375,7 +9449,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
for (items) |item_ref, item_i| {
try sema.validateSwitchItemEnum(
block,
seen_fields,
seen_enum_fields,
&range_set,
item_ref,
src_node_offset,
@ -9386,7 +9460,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);
}
}
const all_tags_handled = for (seen_fields) |seen_src| {
const all_tags_handled = for (seen_enum_fields) |seen_src| {
if (seen_src == null) break false;
} else true;
@ -9406,7 +9480,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
.{},
);
errdefer msg.destroy(sema.gpa);
for (seen_fields) |seen_src, i| {
for (seen_enum_fields) |seen_src, i| {
if (seen_src != null) continue;
const field_name = operand_ty.enumFieldName(i);
@ -9437,16 +9511,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
}
},
.ErrorSet => {
var seen_errors = SwitchErrorSet.init(gpa);
defer seen_errors.deinit();
var extra_index: usize = special.end;
{
var scalar_i: u32 = 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];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
extra_index += 1;
extra_index += body_len;
@ -9466,7 +9537,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
const body_len = sema.code.extra[extra_index];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
extra_index += 1;
const items = sema.code.refSlice(extra_index, items_len);
extra_index += items_len + body_len;
@ -9579,16 +9650,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
}
},
.Int, .ComptimeInt => {
var range_set = RangeSet.init(gpa, sema.mod);
defer range_set.deinit();
var extra_index: usize = special.end;
{
var scalar_i: u32 = 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];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
extra_index += 1;
extra_index += body_len;
@ -9609,7 +9677,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
const body_len = sema.code.extra[extra_index];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
extra_index += 1;
const items = sema.code.refSlice(extra_index, items_len);
extra_index += items_len;
@ -9677,16 +9745,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
}
},
.Bool => {
var true_count: u8 = 0;
var false_count: u8 = 0;
var extra_index: usize = special.end;
{
var scalar_i: u32 = 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];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
extra_index += 1;
extra_index += body_len;
@ -9707,7 +9772,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
const body_len = sema.code.extra[extra_index];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
extra_index += 1;
const items = sema.code.refSlice(extra_index, items_len);
extra_index += items_len + body_len;
@ -9771,7 +9836,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
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];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
extra_index += 1;
extra_index += body_len;
@ -9791,7 +9856,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
const body_len = sema.code.extra[extra_index];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
extra_index += 1;
const items = sema.code.refSlice(extra_index, items_len);
extra_index += items_len + body_len;
@ -9871,7 +9936,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
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];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
extra_index += 1;
const body = sema.code.extra[extra_index..][0..body_len];
extra_index += body_len;
@ -9892,7 +9957,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
const body_len = sema.code.extra[extra_index];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
extra_index += 1;
const items = sema.code.refSlice(extra_index, items_len);
extra_index += items_len;
@ -9933,7 +9998,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
return sema.resolveBlockBody(block, src, &child_block, special.body, inst, merges);
}
if (scalar_cases_len + multi_cases_len == 0) {
if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline) {
if (empty_enum) {
return Air.Inst.Ref.void_value;
}
@ -9965,7 +10030,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
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];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
const is_inline = sema.code.extra[extra_index] >> 31 != 0;
extra_index += 1;
const body = sema.code.extra[extra_index..][0..body_len];
extra_index += body_len;
@ -9975,8 +10041,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
case_block.instructions.shrinkRetainingCapacity(0);
case_block.wip_capture_scope = wip_captures.scope;
case_block.inline_case_capture = .none;
const item = try sema.resolveInst(item_ref);
if (is_inline) case_block.inline_case_capture = item;
// `item` is already guaranteed to be constant known.
const analyze_body = if (union_originally) blk: {
@ -9988,18 +10056,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) {
// nothing to do here
} else if (analyze_body) {
_ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) {
error.ComptimeBreak => {
const zir_datas = sema.code.instructions.items(.data);
const break_data = zir_datas[sema.comptime_break_inst].@"break";
try sema.addRuntimeBreak(&case_block, .{
.block_inst = break_data.block_inst,
.operand = break_data.operand,
.inst = sema.comptime_break_inst,
});
},
else => |e| return e,
};
try sema.analyzeBodyRuntimeBreak(&case_block, body);
} else {
_ = try case_block.addNoOp(.unreach);
}
@ -10021,19 +10078,115 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
defer gpa.free(prev_then_body);
var cases_len = scalar_cases_len;
var multi_i: usize = 0;
var multi_i: u32 = 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];
const body_len = @truncate(u31, sema.code.extra[extra_index]);
const is_inline = sema.code.extra[extra_index] >> 31 != 0;
extra_index += 1;
const items = sema.code.refSlice(extra_index, items_len);
extra_index += items_len;
case_block.instructions.shrinkRetainingCapacity(0);
case_block.wip_capture_scope = child_block.wip_capture_scope;
case_block.inline_case_capture = .none;
// Generate all possible cases as scalar prongs.
if (is_inline) {
const body_start = extra_index + 2 * ranges_len;
const body = sema.code.extra[body_start..][0..body_len];
var emit_bb = false;
var range_i: u32 = 0;
while (range_i < ranges_len) : (range_i += 1) {
const first_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
extra_index += 1;
const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
extra_index += 1;
const item_first_ref = try sema.resolveInst(first_ref);
var item = sema.resolveConstValue(block, .unneeded, item_first_ref, undefined) catch unreachable;
const item_last_ref = try sema.resolveInst(last_ref);
const item_last = sema.resolveConstValue(block, .unneeded, item_last_ref, undefined) catch unreachable;
while (item.compare(.lte, item_last, operand_ty, sema.mod)) : ({
// Previous validation has resolved any possible lazy values.
item = try sema.intAddScalar(block, .unneeded, item, Value.one);
}) {
cases_len += 1;
const item_ref = try sema.addConstant(operand_ty, item);
case_block.inline_case_capture = item_ref;
case_block.instructions.shrinkRetainingCapacity(0);
case_block.wip_capture_scope = child_block.wip_capture_scope;
if (emit_bb) sema.emitBackwardBranch(block, .unneeded) catch |err| switch (err) {
error.NeededSourceLocation => {
const case_src = Module.SwitchProngSrc{ .range = .{ .prong = multi_i, .item = range_i } };
const decl = sema.mod.declPtr(case_block.src_decl);
try sema.emitBackwardBranch(block, case_src.resolve(sema.gpa, decl, src_node_offset, .none));
return error.AnalysisFail;
},
else => return err,
};
emit_bb = true;
try sema.analyzeBodyRuntimeBreak(&case_block, body);
try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
cases_extra.appendAssumeCapacity(1); // items_len
cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture));
cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
}
}
for (items) |item_ref, item_i| {
cases_len += 1;
const item = try sema.resolveInst(item_ref);
case_block.inline_case_capture = item;
case_block.instructions.shrinkRetainingCapacity(0);
case_block.wip_capture_scope = child_block.wip_capture_scope;
const analyze_body = if (union_originally) blk: {
const item_val = sema.resolveConstValue(block, .unneeded, item, undefined) catch unreachable;
const field_ty = maybe_union_ty.unionFieldType(item_val, sema.mod);
break :blk field_ty.zigTypeTag() != .NoReturn;
} else true;
if (emit_bb) sema.emitBackwardBranch(block, .unneeded) catch |err| switch (err) {
error.NeededSourceLocation => {
const case_src = Module.SwitchProngSrc{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } };
const decl = sema.mod.declPtr(case_block.src_decl);
try sema.emitBackwardBranch(block, case_src.resolve(sema.gpa, decl, src_node_offset, .none));
return error.AnalysisFail;
},
else => return err,
};
emit_bb = true;
if (analyze_body) {
try sema.analyzeBodyRuntimeBreak(&case_block, body);
} else {
_ = try case_block.addNoOp(.unreach);
}
try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
cases_extra.appendAssumeCapacity(1); // items_len
cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture));
cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
}
extra_index += body_len;
continue;
}
var any_ok: Air.Inst.Ref = .none;
@ -10058,18 +10211,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) {
// nothing to do here
} else if (analyze_body) {
_ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) {
error.ComptimeBreak => {
const zir_datas = sema.code.instructions.items(.data);
const break_data = zir_datas[sema.comptime_break_inst].@"break";
try sema.addRuntimeBreak(&case_block, .{
.block_inst = break_data.block_inst,
.operand = break_data.operand,
.inst = sema.comptime_break_inst,
});
},
else => |e| return e,
};
try sema.analyzeBodyRuntimeBreak(&case_block, body);
} else {
_ = try case_block.addNoOp(.unreach);
}
@ -10150,18 +10292,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) {
// nothing to do here
} else {
_ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) {
error.ComptimeBreak => {
const zir_datas = sema.code.instructions.items(.data);
const break_data = zir_datas[sema.comptime_break_inst].@"break";
try sema.addRuntimeBreak(&case_block, .{
.block_inst = break_data.block_inst,
.operand = break_data.operand,
.inst = sema.comptime_break_inst,
});
},
else => |e| return e,
};
try sema.analyzeBodyRuntimeBreak(&case_block, body);
}
try wip_captures.finalize();
@ -10192,14 +10323,150 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
var final_else_body: []const Air.Inst.Index = &.{};
if (special.body.len != 0 or !is_first or case_block.wantSafety()) {
var emit_bb = false;
if (special.is_inline) switch (operand_ty.zigTypeTag()) {
.Enum => {
if (operand_ty.isNonexhaustiveEnum() and !union_originally) {
return sema.fail(block, special_prong_src, "cannot enumerate values of type '{}' for 'inline else'", .{
operand_ty.fmt(sema.mod),
});
}
for (seen_enum_fields) |f, i| {
if (f != null) continue;
cases_len += 1;
const item_val = try Value.Tag.enum_field_index.create(sema.arena, @intCast(u32, i));
const item_ref = try sema.addConstant(operand_ty, item_val);
case_block.inline_case_capture = item_ref;
case_block.instructions.shrinkRetainingCapacity(0);
case_block.wip_capture_scope = child_block.wip_capture_scope;
const analyze_body = if (union_originally) blk: {
const field_ty = maybe_union_ty.unionFieldType(item_val, sema.mod);
break :blk field_ty.zigTypeTag() != .NoReturn;
} else true;
if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src);
emit_bb = true;
if (analyze_body) {
try sema.analyzeBodyRuntimeBreak(&case_block, special.body);
} else {
_ = try case_block.addNoOp(.unreach);
}
try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
cases_extra.appendAssumeCapacity(1); // items_len
cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture));
cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
}
},
.ErrorSet => {
if (operand_ty.isAnyError()) {
return sema.fail(block, special_prong_src, "cannot enumerate values of type '{}' for 'inline else'", .{
operand_ty.fmt(sema.mod),
});
}
for (operand_ty.errorSetNames()) |error_name| {
if (seen_errors.contains(error_name)) continue;
cases_len += 1;
const item_val = try Value.Tag.@"error".create(sema.arena, .{ .name = error_name });
const item_ref = try sema.addConstant(operand_ty, item_val);
case_block.inline_case_capture = item_ref;
case_block.instructions.shrinkRetainingCapacity(0);
case_block.wip_capture_scope = child_block.wip_capture_scope;
if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src);
emit_bb = true;
try sema.analyzeBodyRuntimeBreak(&case_block, special.body);
try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
cases_extra.appendAssumeCapacity(1); // items_len
cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture));
cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
}
},
.Int => {
var it = try RangeSetUnhandledIterator.init(sema, block, special_prong_src, operand_ty, range_set);
while (try it.next()) |cur| {
cases_len += 1;
const item_ref = try sema.addConstant(operand_ty, cur);
case_block.inline_case_capture = item_ref;
case_block.instructions.shrinkRetainingCapacity(0);
case_block.wip_capture_scope = child_block.wip_capture_scope;
if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src);
emit_bb = true;
try sema.analyzeBodyRuntimeBreak(&case_block, special.body);
try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
cases_extra.appendAssumeCapacity(1); // items_len
cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture));
cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
}
},
.Bool => {
if (true_count == 0) {
cases_len += 1;
case_block.inline_case_capture = Air.Inst.Ref.bool_true;
case_block.instructions.shrinkRetainingCapacity(0);
case_block.wip_capture_scope = child_block.wip_capture_scope;
if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src);
emit_bb = true;
try sema.analyzeBodyRuntimeBreak(&case_block, special.body);
try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
cases_extra.appendAssumeCapacity(1); // items_len
cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture));
cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
}
if (false_count == 0) {
cases_len += 1;
case_block.inline_case_capture = Air.Inst.Ref.bool_false;
case_block.instructions.shrinkRetainingCapacity(0);
case_block.wip_capture_scope = child_block.wip_capture_scope;
if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src);
emit_bb = true;
try sema.analyzeBodyRuntimeBreak(&case_block, special.body);
try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
cases_extra.appendAssumeCapacity(1); // items_len
cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture));
cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
}
},
else => return sema.fail(block, special_prong_src, "cannot enumerate values of type '{}' for 'inline else'", .{
operand_ty.fmt(sema.mod),
}),
};
var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, child_block.wip_capture_scope);
defer wip_captures.deinit();
case_block.instructions.shrinkRetainingCapacity(0);
case_block.wip_capture_scope = wip_captures.scope;
case_block.inline_case_capture = .none;
const analyze_body = if (union_originally)
for (seen_union_fields) |seen_field, index| {
const analyze_body = if (union_originally and !special.is_inline)
for (seen_enum_fields) |seen_field, index| {
if (seen_field != null) continue;
const union_obj = maybe_union_ty.cast(Type.Payload.Union).?.data;
const field_ty = union_obj.fields.values()[index].ty;
@ -10211,19 +10478,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
try sema.maybeErrorUnwrap(&case_block, special.body, operand))
{
// nothing to do here
} else if (special.body.len != 0 and analyze_body) {
_ = sema.analyzeBodyInner(&case_block, special.body) catch |err| switch (err) {
error.ComptimeBreak => {
const zir_datas = sema.code.instructions.items(.data);
const break_data = zir_datas[sema.comptime_break_inst].@"break";
try sema.addRuntimeBreak(&case_block, .{
.block_inst = break_data.block_inst,
.operand = break_data.operand,
.inst = sema.comptime_break_inst,
});
},
else => |e| return e,
};
} else if (special.body.len != 0 and analyze_body and !special.is_inline) {
try sema.analyzeBodyRuntimeBreak(&case_block, special.body);
} else {
// We still need a terminator in this block, but we have proven
// that it is unreachable.
@ -10269,6 +10525,55 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
return sema.analyzeBlockBody(block, src, &child_block, merges);
}
const RangeSetUnhandledIterator = struct {
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ty: Type,
cur: Value,
max: Value,
ranges: []const RangeSet.Range,
range_i: usize = 0,
first: bool = true,
fn init(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type, range_set: RangeSet) !RangeSetUnhandledIterator {
const target = sema.mod.getTarget();
const min = try ty.minInt(sema.arena, target);
const max = try ty.maxInt(sema.arena, target);
return RangeSetUnhandledIterator{
.sema = sema,
.block = block,
.src = src,
.ty = ty,
.cur = min,
.max = max,
.ranges = range_set.ranges.items,
};
}
fn next(it: *RangeSetUnhandledIterator) !?Value {
while (it.range_i < it.ranges.len) : (it.range_i += 1) {
if (!it.first) {
it.cur = try it.sema.intAdd(it.block, it.src, it.cur, Value.one, it.ty);
}
it.first = false;
if (it.cur.compare(.lt, it.ranges[it.range_i].first, it.ty, it.sema.mod)) {
return it.cur;
}
it.cur = it.ranges[it.range_i].last;
}
if (!it.first) {
it.cur = try it.sema.intAdd(it.block, it.src, it.cur, Value.one, it.ty);
}
it.first = false;
if (it.cur.compare(.lte, it.max, it.ty, it.sema.mod)) {
return it.cur;
}
return null;
}
};
fn resolveSwitchItemVal(
sema: *Sema,
block: *Block,
@ -15351,18 +15656,7 @@ fn zirCondbr(
sub_block.runtime_index.increment();
defer sub_block.instructions.deinit(gpa);
_ = sema.analyzeBodyInner(&sub_block, then_body) catch |err| switch (err) {
error.ComptimeBreak => {
const zir_datas = sema.code.instructions.items(.data);
const break_data = zir_datas[sema.comptime_break_inst].@"break";
try sema.addRuntimeBreak(&sub_block, .{
.block_inst = break_data.block_inst,
.operand = break_data.operand,
.inst = sema.comptime_break_inst,
});
},
else => |e| return e,
};
try sema.analyzeBodyRuntimeBreak(&sub_block, then_body);
const true_instructions = sub_block.instructions.toOwnedSlice(gpa);
defer gpa.free(true_instructions);
@ -15381,18 +15675,7 @@ fn zirCondbr(
if (err_cond != null and try sema.maybeErrorUnwrap(&sub_block, else_body, err_cond.?)) {
// nothing to do
} else {
_ = sema.analyzeBodyInner(&sub_block, else_body) catch |err| switch (err) {
error.ComptimeBreak => {
const zir_datas = sema.code.instructions.items(.data);
const break_data = zir_datas[sema.comptime_break_inst].@"break";
try sema.addRuntimeBreak(&sub_block, .{
.block_inst = break_data.block_inst,
.operand = break_data.operand,
.inst = sema.comptime_break_inst,
});
},
else => |e| return e,
};
try sema.analyzeBodyRuntimeBreak(&sub_block, else_body);
}
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len +
true_instructions.len + sub_block.instructions.items.len);

View File

@ -683,6 +683,9 @@ pub const Inst = struct {
/// Result is a pointer to the value.
/// Uses the `switch_capture` field.
switch_capture_multi_ref,
/// Produces the capture value for an inline switch prong tag capture.
/// Uses the `un_tok` field.
switch_capture_tag,
/// Given a
/// *A returns *A
/// *E!A returns *A
@ -1128,6 +1131,7 @@ pub const Inst = struct {
.switch_capture_ref,
.switch_capture_multi,
.switch_capture_multi_ref,
.switch_capture_tag,
.switch_block,
.switch_cond,
.switch_cond_ref,
@ -1422,6 +1426,7 @@ pub const Inst = struct {
.switch_capture_ref,
.switch_capture_multi,
.switch_capture_multi_ref,
.switch_capture_tag,
.switch_block,
.switch_cond,
.switch_cond_ref,
@ -1681,6 +1686,7 @@ pub const Inst = struct {
.switch_capture_ref = .switch_capture,
.switch_capture_multi = .switch_capture,
.switch_capture_multi_ref = .switch_capture,
.switch_capture_tag = .un_tok,
.array_base_ptr = .un_node,
.field_base_ptr = .un_node,
.validate_array_init_ty = .pl_node,
@ -2952,12 +2958,9 @@ pub const Inst = struct {
has_else: bool,
/// If true, there is an underscore prong. This is mutually exclusive with `has_else`.
has_under: bool,
/// If true, the `operand` is a pointer to the value being switched on.
/// TODO this flag is redundant with the tag of operand and can be removed.
is_ref: bool,
scalar_cases_len: ScalarCasesLen,
pub const ScalarCasesLen = u28;
pub const ScalarCasesLen = u29;
pub fn specialProng(bits: Bits) SpecialProng {
const has_else: u2 = @boolToInt(bits.has_else);
@ -2993,7 +2996,7 @@ pub const Inst = struct {
}
if (self.bits.specialProng() != .none) {
const body_len = zir.extra[extra_index];
const body_len = @truncate(u31, zir.extra[extra_index]);
extra_index += 1;
const body = zir.extra[extra_index..][0..body_len];
extra_index += body.len;
@ -3003,7 +3006,7 @@ pub const Inst = struct {
while (true) : (scalar_i += 1) {
const item = @intToEnum(Ref, zir.extra[extra_index]);
extra_index += 1;
const body_len = zir.extra[extra_index];
const body_len = @truncate(u31, zir.extra[extra_index]);
extra_index += 1;
const body = zir.extra[extra_index..][0..body_len];
extra_index += body.len;
@ -3032,7 +3035,7 @@ pub const Inst = struct {
var extra_index: usize = extra_end + 1;
if (self.bits.specialProng() != .none) {
const body_len = zir.extra[extra_index];
const body_len = @truncate(u31, zir.extra[extra_index]);
extra_index += 1;
const body = zir.extra[extra_index..][0..body_len];
extra_index += body.len;
@ -3041,7 +3044,7 @@ pub const Inst = struct {
var scalar_i: usize = 0;
while (scalar_i < self.bits.scalar_cases_len) : (scalar_i += 1) {
extra_index += 1;
const body_len = zir.extra[extra_index];
const body_len = @truncate(u31, zir.extra[extra_index]);
extra_index += 1;
extra_index += body_len;
}
@ -3049,7 +3052,7 @@ pub const Inst = struct {
while (true) : (multi_i += 1) {
const items_len = zir.extra[extra_index];
extra_index += 2;
const body_len = zir.extra[extra_index];
const body_len = @truncate(u31, zir.extra[extra_index]);
extra_index += 1;
const items = zir.refSlice(extra_index, items_len);
extra_index += items_len;
@ -3861,7 +3864,7 @@ fn findDeclsSwitch(
const special_prong = extra.data.bits.specialProng();
if (special_prong != .none) {
const body_len = zir.extra[extra_index];
const body_len = @truncate(u31, zir.extra[extra_index]);
extra_index += 1;
const body = zir.extra[extra_index..][0..body_len];
extra_index += body.len;
@ -3874,7 +3877,7 @@ fn findDeclsSwitch(
var scalar_i: usize = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
extra_index += 1;
const body_len = zir.extra[extra_index];
const body_len = @truncate(u31, zir.extra[extra_index]);
extra_index += 1;
const body = zir.extra[extra_index..][0..body_len];
extra_index += body_len;
@ -3889,7 +3892,7 @@ fn findDeclsSwitch(
extra_index += 1;
const ranges_len = zir.extra[extra_index];
extra_index += 1;
const body_len = zir.extra[extra_index];
const body_len = @truncate(u31, zir.extra[extra_index]);
extra_index += 1;
const items = zir.refSlice(extra_index, items_len);
extra_index += items_len;

View File

@ -2159,7 +2159,7 @@ const RegisterOrMemory = union(enum) {
/// Returns size in bits.
fn size(reg_or_mem: RegisterOrMemory) u64 {
return switch (reg_or_mem) {
.register => |reg| reg.size(),
.register => |register| register.size(),
.memory => |memory| memory.size(),
};
}

View File

@ -237,6 +237,7 @@ const Writer = struct {
.ret_tok,
.ensure_err_payload_void,
.closure_capture,
.switch_capture_tag,
=> try self.writeUnTok(stream, inst),
.bool_br_and,
@ -1857,7 +1858,6 @@ const Writer = struct {
} else 0;
try self.writeInstRef(stream, extra.data.operand);
try self.writeFlag(stream, ", ref", extra.data.bits.is_ref);
self.indent += 2;
@ -1869,14 +1869,15 @@ const Writer = struct {
else => break :else_prong,
};
const body_len = self.code.extra[extra_index];
const body_len = @truncate(u31, self.code.extra[extra_index]);
const inline_text = if (self.code.extra[extra_index] >> 31 != 0) "inline " else "";
extra_index += 1;
const body = self.code.extra[extra_index..][0..body_len];
extra_index += body.len;
try stream.writeAll(",\n");
try stream.writeByteNTimes(' ', self.indent);
try stream.print("{s} => ", .{prong_name});
try stream.print("{s}{s} => ", .{ inline_text, prong_name });
try self.writeBracedBody(stream, body);
}
@ -1886,13 +1887,15 @@ const Writer = struct {
while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
const item_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
extra_index += 1;
const body_len = self.code.extra[extra_index];
const body_len = @truncate(u31, self.code.extra[extra_index]);
const is_inline = self.code.extra[extra_index] >> 31 != 0;
extra_index += 1;
const body = self.code.extra[extra_index..][0..body_len];
extra_index += body_len;
try stream.writeAll(",\n");
try stream.writeByteNTimes(' ', self.indent);
if (is_inline) try stream.writeAll("inline ");
try self.writeInstRef(stream, item_ref);
try stream.writeAll(" => ");
try self.writeBracedBody(stream, body);
@ -1905,13 +1908,15 @@ const Writer = struct {
extra_index += 1;
const ranges_len = self.code.extra[extra_index];
extra_index += 1;
const body_len = self.code.extra[extra_index];
const body_len = @truncate(u31, self.code.extra[extra_index]);
const is_inline = self.code.extra[extra_index] >> 31 != 0;
extra_index += 1;
const items = self.code.refSlice(extra_index, items_len);
extra_index += items_len;
try stream.writeAll(",\n");
try stream.writeByteNTimes(' ', self.indent);
if (is_inline) try stream.writeAll("inline ");
for (items) |item_ref, item_i| {
if (item_i != 0) try stream.writeAll(", ");

View File

@ -1039,6 +1039,7 @@ struct AstNodeSwitchProng {
AstNode *expr;
bool var_is_ptr;
bool any_items_are_range;
bool is_inline;
};
struct AstNodeSwitchRange {

View File

@ -6987,6 +6987,12 @@ static bool astgen_switch_prong_expr(Stage1AstGen *ag, Scope *scope, AstNode *sw
assert(switch_node->type == NodeTypeSwitchExpr);
assert(prong_node->type == NodeTypeSwitchProng);
if (prong_node->data.switch_prong.is_inline) {
exec_add_error_node(ag->codegen, ag->exec, prong_node,
buf_sprintf("inline switch cases not supported by stage1"));
return ag->codegen->invalid_inst_src;
}
AstNode *expr_node = prong_node->data.switch_prong.expr;
AstNode *var_symbol_node = prong_node->data.switch_prong.var_symbol;
Scope *child_scope;

View File

@ -2306,17 +2306,17 @@ static Optional<PtrIndexPayload> ast_parse_ptr_index_payload(ParseContext *pc) {
return Optional<PtrIndexPayload>::some(res);
}
// SwitchProng <- SwitchCase EQUALRARROW PtrPayload? AssignExpr
// SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrIndexPayload? AssignExpr
static AstNode *ast_parse_switch_prong(ParseContext *pc) {
AstNode *res = ast_parse_switch_case(pc);
if (res == nullptr)
return nullptr;
expect_token(pc, TokenIdFatArrow);
Optional<PtrPayload> opt_payload = ast_parse_ptr_payload(pc);
Optional<PtrIndexPayload> opt_payload = ast_parse_ptr_index_payload(pc);
AstNode *expr = ast_expect(pc, ast_parse_assign_expr);
PtrPayload payload;
PtrIndexPayload payload;
assert(res->type == NodeTypeSwitchProng);
res->data.switch_prong.expr = expr;
if (opt_payload.unwrap(&payload)) {
@ -2331,9 +2331,11 @@ static AstNode *ast_parse_switch_prong(ParseContext *pc) {
// <- SwitchItem (COMMA SwitchItem)* COMMA?
// / KEYWORD_else
static AstNode *ast_parse_switch_case(ParseContext *pc) {
bool is_inline = eat_token_if(pc, TokenIdKeywordInline) != 0;
AstNode *first = ast_parse_switch_item(pc);
if (first != nullptr) {
AstNode *res = ast_create_node_copy_line_info(pc, NodeTypeSwitchProng, first);
res->data.switch_prong.is_inline = is_inline;
res->data.switch_prong.items.append(first);
res->data.switch_prong.any_items_are_range = first->type == NodeTypeSwitchRange;
@ -2350,9 +2352,13 @@ static AstNode *ast_parse_switch_case(ParseContext *pc) {
}
TokenIndex else_token = eat_token_if(pc, TokenIdKeywordElse);
if (else_token != 0)
return ast_create_node(pc, NodeTypeSwitchProng, else_token);
if (else_token != 0) {
AstNode *res = ast_create_node(pc, NodeTypeSwitchProng, else_token);
res->data.switch_prong.is_inline = is_inline;
return res;
}
if (is_inline) pc->current_token -= 1;
return nullptr;
}

View File

@ -182,6 +182,7 @@ test {
_ = @import("behavior/decltest.zig");
_ = @import("behavior/packed_struct_explicit_backing_int.zig");
_ = @import("behavior/empty_union.zig");
_ = @import("behavior/inline_switch.zig");
}
if (builtin.os.tag != .wasi) {

View File

@ -0,0 +1,131 @@
const std = @import("std");
const expect = std.testing.expect;
const builtin = @import("builtin");
test "inline scalar prongs" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
var x: usize = 0;
switch (x) {
10 => |*item| try expect(@TypeOf(item) == *usize),
inline 11 => |*item| {
try expect(@TypeOf(item) == *const usize);
try expect(item.* == 11);
},
else => {},
}
}
test "inline prong ranges" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
var x: usize = 0;
switch (x) {
inline 0...20, 24 => |item| {
if (item > 25) @compileError("bad");
},
else => {},
}
}
const E = enum { a, b, c, d };
test "inline switch enums" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
var x: E = .a;
switch (x) {
inline .a, .b => |aorb| if (aorb != .a and aorb != .b) @compileError("bad"),
inline .c, .d => |cord| if (cord != .c and cord != .d) @compileError("bad"),
}
}
const U = union(E) { a: void, b: u2, c: u3, d: u4 };
test "inline switch unions" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
var x: U = .a;
switch (x) {
inline .a, .b => |aorb, tag| {
if (tag == .a) {
try expect(@TypeOf(aorb) == void);
} else {
try expect(tag == .b);
try expect(@TypeOf(aorb) == u2);
}
},
inline .c, .d => |cord, tag| {
if (tag == .c) {
try expect(@TypeOf(cord) == u3);
} else {
try expect(tag == .d);
try expect(@TypeOf(cord) == u4);
}
},
}
}
test "inline else bool" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
var a = true;
switch (a) {
true => {},
inline else => |val| if (val != false) @compileError("bad"),
}
}
test "inline else error" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
const Err = error{ a, b, c };
var a = Err.a;
switch (a) {
error.a => {},
inline else => |val| comptime if (val == error.a) @compileError("bad"),
}
}
test "inline else enum" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
const E2 = enum(u8) { a = 2, b = 3, c = 4, d = 5 };
var a: E2 = .a;
switch (a) {
.a, .b => {},
inline else => |val| comptime if (@enumToInt(val) < 4) @compileError("bad"),
}
}
test "inline else int with gaps" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
var a: u8 = 0;
switch (a) {
1...125, 128...254 => {},
inline else => |val| {
if (val != 0 and
val != 126 and
val != 127 and
val != 255)
@compileError("bad");
},
}
}
test "inline else int all values" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
var a: u2 = 0;
switch (a) {
inline else => |val| {
if (val != 0 and
val != 1 and
val != 2 and
val != 3)
@compileError("bad");
},
}
}

View File

@ -0,0 +1,15 @@
const E = enum(u8) { a, b, c, d, _ };
pub export fn entry() void {
var x: E = .a;
switch (x) {
inline .a, .b => |aorb| @compileLog(aorb),
.c, .d => |cord| @compileLog(cord),
inline _ => {},
}
}
// error
// backend=stage2
// target=native
//
// :7:16: error: cannot inline '_' prong

View File

@ -0,0 +1,27 @@
pub export fn entry1() void {
var a: anyerror = undefined;
switch (a) {
inline else => {},
}
}
const E = enum(u8) { a, _ };
pub export fn entry2() void {
var a: E = undefined;
switch (a) {
inline else => {},
}
}
pub export fn entry3() void {
var a: *u32 = undefined;
switch (a) {
inline else => {},
}
}
// error
// backend=stage2
// target=native
//
// :4:21: error: cannot enumerate values of type 'anyerror' for 'inline else'
// :11:21: error: cannot enumerate values of type 'tmp.E' for 'inline else'
// :17:21: error: cannot enumerate values of type '*u32' for 'inline else'

View File

@ -0,0 +1,15 @@
const E = enum { a, b, c, d };
pub export fn entry() void {
var x: E = .a;
switch (x) {
inline .a, .b => |aorb, d| @compileLog(aorb, d),
inline .c, .d => |*cord| @compileLog(cord),
}
}
// error
// backend=stage2
// target=native
//
// :5:33: error: cannot capture tag of non-union type 'tmp.E'
// :1:11: note: enum declared here

View File

@ -0,0 +1,14 @@
const E = enum { a, b, c, d };
pub export fn entry() void {
var x: E = .a;
switch (x) {
.a, .b => |aorb, d| @compileLog(aorb, d),
inline .c, .d => |*cord| @compileLog(cord),
}
}
// error
// backend=stage2
// target=native
//
// :5:26: error: tag capture on non-inline prong