From b4d81857f36a155daa884ba80bfd56c43a182663 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sun, 13 Feb 2022 18:23:35 +0200 Subject: [PATCH 01/10] stage1+2: parse inline switch cases --- lib/std/zig/Ast.zig | 31 ++++++++++++++++++++++++++++--- lib/std/zig/parse.zig | 15 ++++++++++----- lib/std/zig/render.zig | 9 +++++++-- src/AstGen.zig | 18 ++++++++++++++++-- src/stage1/all_types.hpp | 1 + src/stage1/parser.cpp | 12 +++++++++--- 6 files changed, 71 insertions(+), 15 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index bfa73dc9ac..62a567387f 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -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`. diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 43b6eda8e0..04b45ad20d 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -3100,7 +3100,7 @@ const Parser = struct { return identifier; } - /// SwitchProng <- SwitchCase EQUALRARROW PtrPayload? AssignExpr + /// SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrPayload? 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,7 +3117,10 @@ 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(); @@ -3123,7 +3128,7 @@ const Parser = struct { 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)), diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 6ef0cfcd6f..db4a092b2a 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -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 diff --git a/src/AstGen.zig b/src/AstGen.zig index 8e6721d7bc..a516043bf2 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -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`. @@ -6216,14 +6220,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| { @@ -6318,6 +6323,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; @@ -8436,7 +8444,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, @@ -8668,7 +8678,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, @@ -8879,7 +8891,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, diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index 5f216fe388..d4a2abece9 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -1039,6 +1039,7 @@ struct AstNodeSwitchProng { AstNode *expr; bool var_is_ptr; bool any_items_are_range; + bool is_inline; }; struct AstNodeSwitchRange { diff --git a/src/stage1/parser.cpp b/src/stage1/parser.cpp index bd778484cb..b7cb6cb297 100644 --- a/src/stage1/parser.cpp +++ b/src/stage1/parser.cpp @@ -2306,7 +2306,7 @@ static Optional ast_parse_ptr_index_payload(ParseContext *pc) { return Optional::some(res); } -// SwitchProng <- SwitchCase EQUALRARROW PtrPayload? AssignExpr +// SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrPayload? AssignExpr static AstNode *ast_parse_switch_prong(ParseContext *pc) { AstNode *res = ast_parse_switch_case(pc); if (res == nullptr) @@ -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; } From 07a7c2f7c86d72bd15e980d098aa2b46f236412f Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 26 Sep 2022 13:54:52 +0300 Subject: [PATCH 02/10] stage2: remove redundant `is_ref` flag from `SwitchBlock.Bits` --- src/AstGen.zig | 1 - src/Sema.zig | 9 +++------ src/Zir.zig | 5 +---- src/print_zir.zig | 1 - 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index a516043bf2..fcf5835f1e 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -6520,7 +6520,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, diff --git a/src/Sema.zig b/src/Sema.zig index 72a5cb518f..f6a84341cc 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8994,9 +8994,10 @@ 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; @@ -9009,7 +9010,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; } @@ -9069,8 +9069,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, @@ -9131,7 +9129,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; diff --git a/src/Zir.zig b/src/Zir.zig index 5b1aefea64..24248a6533 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -2952,12 +2952,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); diff --git a/src/print_zir.zig b/src/print_zir.zig index b273365596..1d7083e8f1 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -1857,7 +1857,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; From cccc4c38273eb3e937c3572952b5609b51010baa Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 26 Sep 2022 13:58:04 +0300 Subject: [PATCH 03/10] AstGen: analyze inline switch cases --- src/AstGen.zig | 14 ++++++--- src/Sema.zig | 30 +++++++++---------- src/Zir.zig | 16 +++++----- src/print_zir.zig | 13 +++++--- src/stage1/astgen.cpp | 6 ++++ .../inline_underscore_prong.zig | 15 ++++++++++ 6 files changed, 63 insertions(+), 31 deletions(-) create mode 100644 test/cases/compile_errors/inline_underscore_prong.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index fcf5835f1e..06557f900b 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -6312,6 +6312,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; @@ -6365,8 +6368,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, }; @@ -6506,7 +6509,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); } } @@ -6553,7 +6557,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) { @@ -9134,7 +9138,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, diff --git a/src/Sema.zig b/src/Sema.zig index f6a84341cc..cf7a2a3036 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9237,7 +9237,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const special: struct { body: []const Zir.Inst.Index, end: usize } = switch (special_prong) { .none => .{ .body = &.{}, .end = header_extra_index }, .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], @@ -9307,7 +9307,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; @@ -9328,7 +9328,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; @@ -9407,7 +9407,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; @@ -9427,7 +9427,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; @@ -9549,7 +9549,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; @@ -9570,7 +9570,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; @@ -9647,7 +9647,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; @@ -9668,7 +9668,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; @@ -9732,7 +9732,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; @@ -9752,7 +9752,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; @@ -9832,7 +9832,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; @@ -9853,7 +9853,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; @@ -9926,7 +9926,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; @@ -9988,7 +9988,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; diff --git a/src/Zir.zig b/src/Zir.zig index 24248a6533..3ce0b3c81a 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -2990,7 +2990,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; @@ -3000,7 +3000,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; @@ -3029,7 +3029,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; @@ -3038,7 +3038,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; } @@ -3046,7 +3046,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; @@ -3858,7 +3858,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; @@ -3871,7 +3871,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; @@ -3886,7 +3886,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; diff --git a/src/print_zir.zig b/src/print_zir.zig index 1d7083e8f1..fcd447f707 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -1868,14 +1868,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); } @@ -1885,13 +1886,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); @@ -1904,13 +1907,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(", "); diff --git a/src/stage1/astgen.cpp b/src/stage1/astgen.cpp index 54d9c969a5..9eea2e650e 100644 --- a/src/stage1/astgen.cpp +++ b/src/stage1/astgen.cpp @@ -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; diff --git a/test/cases/compile_errors/inline_underscore_prong.zig b/test/cases/compile_errors/inline_underscore_prong.zig new file mode 100644 index 0000000000..12e20e65bc --- /dev/null +++ b/test/cases/compile_errors/inline_underscore_prong.zig @@ -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 From 5baaf90e3c10d197131eaf5908da4401b9a07e7b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 26 Sep 2022 15:44:40 +0300 Subject: [PATCH 04/10] Sema: implement non-special inline switch prongs --- src/Module.zig | 16 ++-- src/Sema.zig | 148 +++++++++++++++++++++++++++++++- test/behavior.zig | 1 + test/behavior/inline_switch.zig | 57 ++++++++++++ 4 files changed, 212 insertions(+), 10 deletions(-) create mode 100644 test/behavior/inline_switch.zig diff --git a/src/Module.zig b/src/Module.zig index e756cc3dfd..77bde605a2 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -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.?; @@ -5937,8 +5937,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) diff --git a/src/Sema.zig b/src/Sema.zig index cf7a2a3036..d27c0095ad 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -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, @@ -9002,6 +9005,30 @@ fn zirSwitchCapture( 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 @@ -9234,14 +9261,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 = @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, }; }, }; @@ -9901,6 +9929,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (special_prong == .none) { return sema.fail(block, src, "switch must handle all possibilities", .{}); } + if (special.is_inline) { + return sema.fail(block, src, "TODO special.is_inline", .{}); + } if (err_set and try sema.maybeErrorUnwrap(block, special.body, operand)) { return Air.Inst.Ref.unreachable_value; } @@ -9927,6 +9958,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; 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; @@ -9936,8 +9968,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: { @@ -9989,12 +10023,118 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const ranges_len = sema.code.extra[extra_index]; extra_index += 1; 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]; + const case_src = src; // TODO better source location + var emit_bb = false; + + 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_ref = try sema.resolveInst(first_ref); + var item_first = 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_first.compare(.lte, item_last, operand_ty, sema.mod)) : ({ + item_first = try sema.intAddScalar(block, case_src, item_first, Value.one); + }) { + cases_len += 1; + + const item_ref = try sema.addConstant(operand_ty, item_first); + 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, case_src); + emit_bb = true; + + _ = 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 wip_captures.finalize(); + + 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(item_ref)); + cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); + } + } + + for (items) |item_ref| { + 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) try sema.emitBackwardBranch(block, case_src); + emit_bb = true; + + 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, + }; + } else { + _ = try case_block.addNoOp(.unreach); + } + + // try wip_captures.finalize(); + + 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(item)); + cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); + } + + extra_index += body_len; + continue; + } var any_ok: Air.Inst.Ref = .none; @@ -10158,6 +10298,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; + if (special.is_inline) { + return sema.fail(block, src, "TODO special.is_inline", .{}); + } const analyze_body = if (union_originally) for (seen_union_fields) |seen_field, index| { diff --git a/test/behavior.zig b/test/behavior.zig index 648757d56f..78029b6dd1 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -180,6 +180,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) { diff --git a/test/behavior/inline_switch.zig b/test/behavior/inline_switch.zig new file mode 100644 index 0000000000..d7863f8444 --- /dev/null +++ b/test/behavior/inline_switch.zig @@ -0,0 +1,57 @@ +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| { + try expect(@TypeOf(aorb) == void or @TypeOf(aorb) == u2); + }, + inline .c, .d => |cord| { + try expect(@TypeOf(cord) == u3 or @TypeOf(cord) == u4); + }, + } +} From 0e77259f44307a5d9b1e91723a226f8da6fe97d5 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 26 Sep 2022 17:30:24 +0300 Subject: [PATCH 05/10] add inline switch union tag captures --- lib/std/zig/parse.zig | 4 +- lib/std/zig/parser_test.zig | 2 + lib/std/zig/render.zig | 12 +- src/AstGen.zig | 137 ++++++++++++------ src/Sema.zig | 28 ++++ src/Zir.zig | 6 + src/arch/x86_64/Emit.zig | 2 +- src/print_zir.zig | 1 + src/stage1/parser.cpp | 6 +- test/behavior/inline_switch.zig | 18 ++- .../compile_errors/invalid_tag_capture.zig | 15 ++ .../tag_capture_on_non_inline_prong.zig | 14 ++ 12 files changed, 186 insertions(+), 59 deletions(-) create mode 100644 test/cases/compile_errors/invalid_tag_capture.zig create mode 100644 test/cases/compile_errors/tag_capture_on_non_inline_prong.zig diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 04b45ad20d..db56cef21e 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -3100,7 +3100,7 @@ const Parser = struct { return identifier; } - /// SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrPayload? AssignExpr + /// SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrIndexPayload? AssignExpr /// SwitchCase /// <- SwitchItem (COMMA SwitchItem)* COMMA? /// / KEYWORD_else @@ -3123,7 +3123,7 @@ const Parser = struct { } } 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) { diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 0e1817ffab..4e155df6d8 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -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, \\ } \\} \\ diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index db4a092b2a..ab009f8390 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -1541,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 } } diff --git a/src/AstGen.zig b/src/AstGen.zig index 06557f900b..1920ccacfb 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2373,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, @@ -6378,8 +6379,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) @@ -6387,59 +6392,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); @@ -6494,10 +6536,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(); @@ -10073,6 +10119,7 @@ const Scope = struct { @"local constant", @"local variable", @"loop index capture", + @"switch tag capture", @"capture", }; diff --git a/src/Sema.zig b/src/Sema.zig index d27c0095ad..211135c744 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -799,6 +799,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), @@ -9164,6 +9165,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, diff --git a/src/Zir.zig b/src/Zir.zig index 3ce0b3c81a..add8bad801 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -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, diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 0cdc7a4c5f..c1c00d8303 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -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(), }; } diff --git a/src/print_zir.zig b/src/print_zir.zig index fcd447f707..d383664c16 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -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, diff --git a/src/stage1/parser.cpp b/src/stage1/parser.cpp index b7cb6cb297..ec02e6fa8b 100644 --- a/src/stage1/parser.cpp +++ b/src/stage1/parser.cpp @@ -2306,17 +2306,17 @@ static Optional ast_parse_ptr_index_payload(ParseContext *pc) { return Optional::some(res); } -// SwitchProng <- KEYWORD_inline? 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 opt_payload = ast_parse_ptr_payload(pc); + Optional 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)) { diff --git a/test/behavior/inline_switch.zig b/test/behavior/inline_switch.zig index d7863f8444..11157f20fd 100644 --- a/test/behavior/inline_switch.zig +++ b/test/behavior/inline_switch.zig @@ -47,11 +47,21 @@ test "inline switch unions" { var x: U = .a; switch (x) { - inline .a, .b => |aorb| { - try expect(@TypeOf(aorb) == void or @TypeOf(aorb) == u2); + 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| { - try expect(@TypeOf(cord) == u3 or @TypeOf(cord) == u4); + inline .c, .d => |cord, tag| { + if (tag == .c) { + try expect(@TypeOf(cord) == u3); + } else { + try expect(tag == .d); + try expect(@TypeOf(cord) == u4); + } }, } } diff --git a/test/cases/compile_errors/invalid_tag_capture.zig b/test/cases/compile_errors/invalid_tag_capture.zig new file mode 100644 index 0000000000..2cb9135792 --- /dev/null +++ b/test/cases/compile_errors/invalid_tag_capture.zig @@ -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 diff --git a/test/cases/compile_errors/tag_capture_on_non_inline_prong.zig b/test/cases/compile_errors/tag_capture_on_non_inline_prong.zig new file mode 100644 index 0000000000..b525aa4db3 --- /dev/null +++ b/test/cases/compile_errors/tag_capture_on_non_inline_prong.zig @@ -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 From 950a0e2405fb3de63c860c47d73af80f7f1fda2c Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 27 Sep 2022 14:56:56 +0300 Subject: [PATCH 06/10] Sema: implement `inline else` for errors enums and bools --- src/Sema.zig | 227 +++++++++++++++--- test/behavior/inline_switch.zig | 33 +++ .../invalid_inline_else_type.zig | 27 +++ 3 files changed, 253 insertions(+), 34 deletions(-) create mode 100644 test/cases/compile_errors/invalid_inline_else_type.zig diff --git a/src/Sema.zig b/src/Sema.zig index 211135c744..08deb31714 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9309,8 +9309,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; @@ -9347,15 +9358,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; { @@ -9369,7 +9375,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError try sema.validateSwitchItemEnum( block, - seen_fields, + seen_enum_fields, &range_set, item_ref, src_node_offset, @@ -9392,7 +9398,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, @@ -9403,7 +9409,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; @@ -9423,7 +9429,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); @@ -9454,9 +9460,6 @@ 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; @@ -9596,9 +9599,6 @@ 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; @@ -9694,9 +9694,6 @@ 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; @@ -9950,16 +9947,13 @@ 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; } if (special_prong == .none) { return sema.fail(block, src, "switch must handle all possibilities", .{}); } - if (special.is_inline) { - return sema.fail(block, src, "TODO special.is_inline", .{}); - } if (err_set and try sema.maybeErrorUnwrap(block, special.body, operand)) { return Air.Inst.Ref.unreachable_value; } @@ -10323,16 +10317,181 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (special.body.len != 0 or !is_first or case_block.wantSafety()) { var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, child_block.wip_capture_scope); defer wip_captures.deinit(); + 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), + }); + } + var emit_bb = false; + 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) { + _ = 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 { + _ = try case_block.addNoOp(.unreach); + } + + // try wip_captures.finalize(); + + 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), + }); + } + var emit_bb = false; + 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; + + _ = 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, + }; + + // try wip_captures.finalize(); + + 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 => { + return sema.fail(block, special_prong_src, "TODO 'inline else' Int", .{}); + }, + .Bool => { + var emit_bb = false; + 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; + + _ = 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, + }; + + // try wip_captures.finalize(); + + 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; + + _ = 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, + }; + + // try wip_captures.finalize(); + + 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), + }), + }; case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = wip_captures.scope; case_block.inline_case_capture = .none; - if (special.is_inline) { - return sema.fail(block, src, "TODO special.is_inline", .{}); - } - 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; @@ -10344,7 +10503,7 @@ 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) { + } else if (special.body.len != 0 and analyze_body and !special.is_inline) { _ = sema.analyzeBodyInner(&case_block, special.body) catch |err| switch (err) { error.ComptimeBreak => { const zir_datas = sema.code.instructions.items(.data); diff --git a/test/behavior/inline_switch.zig b/test/behavior/inline_switch.zig index 11157f20fd..353157fbfa 100644 --- a/test/behavior/inline_switch.zig +++ b/test/behavior/inline_switch.zig @@ -65,3 +65,36 @@ test "inline switch unions" { }, } } + +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"), + } +} diff --git a/test/cases/compile_errors/invalid_inline_else_type.zig b/test/cases/compile_errors/invalid_inline_else_type.zig new file mode 100644 index 0000000000..2d52fca43e --- /dev/null +++ b/test/cases/compile_errors/invalid_inline_else_type.zig @@ -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' From 83fa216c8d2375476ee02ccf53bf6b5a9ed7480e Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 27 Sep 2022 15:32:34 +0300 Subject: [PATCH 07/10] Sema: implement `inline else` for ints --- src/Sema.zig | 85 ++++++++++++++++++++++++++++++++- test/behavior/inline_switch.zig | 31 ++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/Sema.zig b/src/Sema.zig index 08deb31714..3a3dc5f6ec 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -10414,7 +10414,41 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } }, .Int => { - return sema.fail(block, special_prong_src, "TODO 'inline else' Int", .{}); + var it = try RangeSetUnhandledIterator.init(sema, block, special_prong_src, operand_ty, range_set); + var emit_bb = false; + 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; + + _ = 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, + }; + + // try wip_captures.finalize(); + + 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(item_ref)); + cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); + } }, .Bool => { var emit_bb = false; @@ -10561,6 +10595,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, diff --git a/test/behavior/inline_switch.zig b/test/behavior/inline_switch.zig index 353157fbfa..ecc7bba280 100644 --- a/test/behavior/inline_switch.zig +++ b/test/behavior/inline_switch.zig @@ -98,3 +98,34 @@ test "inline else enum" { 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"); + }, + } +} From 509bb82b20e9417d8b0ff604cd87f70fd1fcf4e9 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 27 Sep 2022 15:51:58 +0300 Subject: [PATCH 08/10] Sema: refactor common code to its own function --- src/Sema.zig | 222 +++++++++------------------------------------------ 1 file changed, 39 insertions(+), 183 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 3a3dc5f6ec..344ae2b153 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -606,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, @@ -10005,18 +10020,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); } @@ -10069,16 +10073,16 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError extra_index += 1; const item_first_ref = try sema.resolveInst(first_ref); - var item_first = sema.resolveConstValue(block, .unneeded, item_first_ref, undefined) catch unreachable; + 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_first.compare(.lte, item_last, operand_ty, sema.mod)) : ({ - item_first = try sema.intAddScalar(block, case_src, item_first, Value.one); + while (item.compare(.lte, item_last, operand_ty, sema.mod)) : ({ + item = try sema.intAddScalar(block, case_src, item, Value.one); }) { cases_len += 1; - const item_ref = try sema.addConstant(operand_ty, item_first); + const item_ref = try sema.addConstant(operand_ty, item); case_block.inline_case_capture = item_ref; case_block.instructions.shrinkRetainingCapacity(0); @@ -10087,25 +10091,12 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (emit_bb) try sema.emitBackwardBranch(block, case_src); emit_bb = true; - _ = 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 wip_captures.finalize(); + 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(item_ref)); + cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); } } @@ -10129,28 +10120,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError emit_bb = true; 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); } - // try wip_captures.finalize(); - 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(item)); + cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); } @@ -10181,18 +10159,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); } @@ -10273,18 +10240,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(); @@ -10315,8 +10271,7 @@ 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 wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, child_block.wip_capture_scope); - defer wip_captures.deinit(); + var emit_bb = false; if (special.is_inline) switch (operand_ty.zigTypeTag()) { .Enum => { if (operand_ty.isNonexhaustiveEnum() and !union_originally) { @@ -10324,7 +10279,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError operand_ty.fmt(sema.mod), }); } - var emit_bb = false; for (seen_enum_fields) |f, i| { if (f != null) continue; cases_len += 1; @@ -10345,24 +10299,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError emit_bb = true; if (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, - }; + try sema.analyzeBodyRuntimeBreak(&case_block, special.body); } else { _ = try case_block.addNoOp(.unreach); } - // try wip_captures.finalize(); - 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)); @@ -10376,7 +10317,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError operand_ty.fmt(sema.mod), }); } - var emit_bb = false; for (operand_ty.errorSetNames()) |error_name| { if (seen_errors.contains(error_name)) continue; cases_len += 1; @@ -10391,20 +10331,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); emit_bb = true; - _ = 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, - }; - - // try wip_captures.finalize(); + try sema.analyzeBodyRuntimeBreak(&case_block, special.body); try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); cases_extra.appendAssumeCapacity(1); // items_len @@ -10415,7 +10342,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError }, .Int => { var it = try RangeSetUnhandledIterator.init(sema, block, special_prong_src, operand_ty, range_set); - var emit_bb = false; while (try it.next()) |cur| { cases_len += 1; @@ -10428,30 +10354,16 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); emit_bb = true; - _ = 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, - }; - - // try wip_captures.finalize(); + 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(item_ref)); + cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); } }, .Bool => { - var emit_bb = false; if (true_count == 0) { cases_len += 1; case_block.inline_case_capture = Air.Inst.Ref.bool_true; @@ -10462,20 +10374,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); emit_bb = true; - _ = 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, - }; - - // try wip_captures.finalize(); + try sema.analyzeBodyRuntimeBreak(&case_block, special.body); try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); cases_extra.appendAssumeCapacity(1); // items_len @@ -10493,20 +10392,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); emit_bb = true; - _ = 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, - }; - - // try wip_captures.finalize(); + try sema.analyzeBodyRuntimeBreak(&case_block, special.body); try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); cases_extra.appendAssumeCapacity(1); // items_len @@ -10520,6 +10406,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError }), }; + 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; @@ -10538,18 +10427,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError { // nothing to do here } else if (special.body.len != 0 and analyze_body and !special.is_inline) { - _ = 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, - }; + try sema.analyzeBodyRuntimeBreak(&case_block, special.body); } else { // We still need a terminator in this block, but we have proven // that it is unreachable. @@ -15716,18 +15594,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); @@ -15746,18 +15613,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); From d4917957ef5aad5a0f381d21040a81c5afe12718 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 27 Sep 2022 16:05:17 +0300 Subject: [PATCH 09/10] Sema: add better source location for inline prong backwards branch limit --- src/Sema.zig | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 344ae2b153..f1c544ca1a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -10042,7 +10042,7 @@ 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; @@ -10062,10 +10062,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (is_inline) { const body_start = extra_index + 2 * ranges_len; const body = sema.code.extra[body_start..][0..body_len]; - const case_src = src; // TODO better source location var emit_bb = false; - var range_i: usize = 0; + 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; @@ -10078,7 +10077,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const item_last = sema.resolveConstValue(block, .unneeded, item_last_ref, undefined) catch unreachable; while (item.compare(.lte, item_last, operand_ty, sema.mod)) : ({ - item = try sema.intAddScalar(block, case_src, item, Value.one); + // Previous validation has resolved any possible lazy values. + item = try sema.intAddScalar(block, .unneeded, item, Value.one); }) { cases_len += 1; @@ -10088,7 +10088,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = child_block.wip_capture_scope; - if (emit_bb) try sema.emitBackwardBranch(block, case_src); + 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); @@ -10101,7 +10109,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } } - for (items) |item_ref| { + for (items) |item_ref, item_i| { cases_len += 1; const item = try sema.resolveInst(item_ref); @@ -10116,7 +10124,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError break :blk field_ty.zigTypeTag() != .NoReturn; } else true; - if (emit_bb) try sema.emitBackwardBranch(block, case_src); + 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) { From 17eea918aee98ca29c3762a7ecd568d2f14f66ef Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 27 Sep 2022 17:38:01 +0300 Subject: [PATCH 10/10] langref: document inline switch --- doc/langref.html.in | 128 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/doc/langref.html.in b/doc/langref.html.in index 816d1fe32a..e5559ff33e 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -4255,6 +4255,134 @@ test "enum literals with switch" { } {#code_end#} {#header_close#} + + {#header_open|Inline switch#} +

+ Switch prongs can be marked as {#syntax#}inline{#endsyntax#} to generate + the prong's body for each possible value it could have: +

+ {#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#} +

+ {#syntax#}inline else{#endsyntax#} prongs can be used as a type safe + alternative to {#syntax#}inline for{#endsyntax#} loops: +

+ {#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#} +

+ When using an inline prong switching on an union an additional + capture can be used to obtain the union's enum tag value. +

+ {#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#}