From e323cf1264f390911dcc2efea71d46be1d631d92 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 27 Aug 2022 18:48:01 +0300 Subject: [PATCH] stage2: change how defers are stored in Zir Storing defers this way has the benefits that the defer doesn't get analyzed multiple times in AstGen, it takes up less space, and it makes Sema aware of defers allowing for 'unreachable else prong' error on error sets in generic code. The disadvantage is that it is a bit more complex and errdefers with payloads now emit a placeholder instruction (but those are rare). Sema.zig before: Total ZIR bytes: 3.7794370651245117MiB Instructions: 238996 (2.051319122314453MiB) String Table Bytes: 89.2802734375KiB Extra Data Items: 430144 (1.640869140625MiB) Sema.zig after: Total ZIR bytes: 3.3344192504882812MiB Instructions: 211829 (1.8181428909301758MiB) String Table Bytes: 89.2802734375KiB Extra Data Items: 374611 (1.4290275573730469MiB) --- doc/langref.html.in | 5 - src/AstGen.zig | 272 +++++++++--------- src/Sema.zig | 34 ++- src/Zir.zig | 30 ++ src/print_zir.zig | 23 ++ test/behavior/defer.zig | 17 ++ .../return_from_defer_expression.zig | 1 + .../uncreachable_else_prong_err_set.zig | 25 ++ test/cases/returns_in_try.zig | 2 + 9 files changed, 261 insertions(+), 148 deletions(-) create mode 100644 test/cases/compile_errors/uncreachable_else_prong_err_set.zig diff --git a/doc/langref.html.in b/doc/langref.html.in index 162ba447003b1f284864fffd66445cfff72af793..cbd841e790bcff67b981f7bec9994eae5af39d6d 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5700,7 +5700,6 @@ fn bar() !void { try quux(); } else |err| switch (err) { error.FileNotFound => try hello(), - else => try another(), } } @@ -5716,10 +5715,6 @@ fn hello() !void { try bang2(); } -fn another() !void { - try bang1(); -} - fn bang1() !void { return error.FileNotFound; } diff --git a/src/AstGen.zig b/src/AstGen.zig index 1502b970178a9c6813605f7843fd4ad48c2b1d04..591c48e4d6961ffa543643749dffcae6a4399c4e 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -135,7 +135,6 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir { var gz_instructions: std.ArrayListUnmanaged(Zir.Inst.Index) = .{}; var gen_scope: GenZir = .{ .force_comptime = true, - .in_defer = false, .parent = &top_scope.base, .anon_name_strategy = .parent, .decl_node_index = 0, @@ -1856,6 +1855,16 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn .gen_zir => { const block_gz = scope.cast(GenZir).?; + if (block_gz.cur_defer_node != 0) { + return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{ + try astgen.errNoteNode( + block_gz.cur_defer_node, + "defer expression here", + .{}, + ), + }); + } + const block_inst = blk: { if (break_label != 0) { if (block_gz.label) |*label| { @@ -1919,17 +1928,6 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, .namespace => break, .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent, - .defer_gen => { - const defer_gen = scope.cast(Scope.DeferGen).?; - - return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{ - try astgen.errNoteNode( - defer_gen.defer_node, - "defer expression here", - .{}, - ), - }); - }, .top => unreachable, } } @@ -1953,6 +1951,16 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) switch (scope.tag) { .gen_zir => { const gen_zir = scope.cast(GenZir).?; + + if (gen_zir.cur_defer_node != 0) { + return astgen.failNodeNotes(node, "cannot continue out of defer expression", .{}, &.{ + try astgen.errNoteNode( + gen_zir.cur_defer_node, + "defer expression here", + .{}, + ), + }); + } const continue_block = gen_zir.continue_block; if (continue_block == 0) { scope = gen_zir.parent; @@ -1985,21 +1993,9 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) .defer_normal => { const defer_scope = scope.cast(Scope.Defer).?; scope = defer_scope.parent; - const expr_node = node_datas[defer_scope.defer_node].rhs; - try unusedResultDeferExpr(parent_gz, defer_scope, defer_scope.parent, expr_node); + try parent_gz.addDefer(defer_scope.index, defer_scope.len); }, .defer_error => scope = scope.cast(Scope.Defer).?.parent, - .defer_gen => { - const defer_gen = scope.cast(Scope.DeferGen).?; - - return astgen.failNodeNotes(node, "cannot continue out of defer expression", .{}, &.{ - try astgen.errNoteNode( - defer_gen.defer_node, - "defer expression here", - .{}, - ), - }); - }, .namespace => break, .top => unreachable, } @@ -2064,7 +2060,6 @@ fn checkLabelRedefinition(astgen: *AstGen, parent_scope: *Scope, label: Ast.Toke .local_val => scope = scope.cast(Scope.LocalVal).?.parent, .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent, - .defer_gen => scope = scope.cast(Scope.DeferGen).?.parent, .namespace => break, .top => unreachable, } @@ -2208,8 +2203,8 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod .simple_var_decl => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.simpleVarDecl(statement)), .aligned_var_decl => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.alignedVarDecl(statement)), - .@"defer" => scope = try makeDeferScope(gz.astgen, scope, statement, block_arena_allocator, .defer_normal), - .@"errdefer" => scope = try makeDeferScope(gz.astgen, scope, statement, block_arena_allocator, .defer_error), + .@"defer" => scope = try deferStmt(gz, scope, statement, block_arena_allocator, .defer_normal), + .@"errdefer" => scope = try deferStmt(gz, scope, statement, block_arena_allocator, .defer_error), .assign => try assign(gz, scope, statement), @@ -2253,28 +2248,6 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod try checkUsed(gz, parent_scope, scope); } -fn unusedResultDeferExpr(gz: *GenZir, defer_scope: *Scope.Defer, expr_scope: *Scope, expr_node: Ast.Node.Index) InnerError!void { - const astgen = gz.astgen; - const prev_offset = astgen.source_offset; - const prev_line = astgen.source_line; - const prev_column = astgen.source_column; - defer { - astgen.source_offset = prev_offset; - astgen.source_line = prev_line; - astgen.source_column = prev_column; - } - astgen.source_offset = defer_scope.source_offset; - astgen.source_line = defer_scope.source_line; - astgen.source_column = defer_scope.source_column; - - var defer_gen: Scope.DeferGen = .{ - .parent = expr_scope, - .defer_node = defer_scope.defer_node, - }; - - _ = try unusedResultExpr(gz, &defer_gen.base, expr_node); -} - /// Returns AST source node of the thing that is noreturn if the statement is /// definitely `noreturn`. Otherwise returns 0. fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) InnerError!Ast.Node.Index { @@ -2584,6 +2557,9 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .validate_struct_init_ty, .validate_deref, => break :b true, + + .@"defer" => unreachable, + .defer_err_code => unreachable, } } else switch (maybe_unused_result) { .none => unreachable, @@ -2603,15 +2579,12 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As return noreturn_src_node; } -fn countDefers(astgen: *AstGen, outer_scope: *Scope, inner_scope: *Scope) struct { +fn countDefers(outer_scope: *Scope, inner_scope: *Scope) struct { have_any: bool, have_normal: bool, have_err: bool, need_err_code: bool, } { - const tree = astgen.tree; - const node_datas = tree.nodes.items(.data); - var have_normal = false; var have_err = false; var need_err_code = false; @@ -2621,7 +2594,6 @@ fn countDefers(astgen: *AstGen, outer_scope: *Scope, inner_scope: *Scope) struct .gen_zir => scope = scope.cast(GenZir).?.parent, .local_val => scope = scope.cast(Scope.LocalVal).?.parent, .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, - .defer_gen => scope = scope.cast(Scope.DeferGen).?.parent, .defer_normal => { const defer_scope = scope.cast(Scope.Defer).?; scope = defer_scope.parent; @@ -2634,7 +2606,7 @@ fn countDefers(astgen: *AstGen, outer_scope: *Scope, inner_scope: *Scope) struct have_err = true; - const have_err_payload = node_datas[defer_scope.defer_node].lhs != 0; + const have_err_payload = defer_scope.remapped_err_code != 0; need_err_code = need_err_code or have_err_payload; }, .namespace => unreachable, @@ -2661,9 +2633,7 @@ fn genDefers( inner_scope: *Scope, which_ones: DefersToEmit, ) InnerError!void { - const astgen = gz.astgen; - const tree = astgen.tree; - const node_datas = tree.nodes.items(.data); + const gpa = gz.astgen.gpa; var scope = inner_scope; while (scope != outer_scope) { @@ -2671,51 +2641,40 @@ fn genDefers( .gen_zir => scope = scope.cast(GenZir).?.parent, .local_val => scope = scope.cast(Scope.LocalVal).?.parent, .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, - .defer_gen => scope = scope.cast(Scope.DeferGen).?.parent, .defer_normal => { const defer_scope = scope.cast(Scope.Defer).?; scope = defer_scope.parent; - const expr_node = node_datas[defer_scope.defer_node].rhs; - const prev_in_defer = gz.in_defer; - gz.in_defer = true; - defer gz.in_defer = prev_in_defer; - try unusedResultDeferExpr(gz, defer_scope, defer_scope.parent, expr_node); + try gz.addDefer(defer_scope.index, defer_scope.len); }, .defer_error => { const defer_scope = scope.cast(Scope.Defer).?; scope = defer_scope.parent; switch (which_ones) { .both_sans_err => { - const expr_node = node_datas[defer_scope.defer_node].rhs; - const prev_in_defer = gz.in_defer; - gz.in_defer = true; - defer gz.in_defer = prev_in_defer; - try unusedResultDeferExpr(gz, defer_scope, defer_scope.parent, expr_node); + try gz.addDefer(defer_scope.index, defer_scope.len); }, .both => |err_code| { - const expr_node = node_datas[defer_scope.defer_node].rhs; - const payload_token = node_datas[defer_scope.defer_node].lhs; - const prev_in_defer = gz.in_defer; - gz.in_defer = true; - defer gz.in_defer = prev_in_defer; - var local_val_scope: Scope.LocalVal = undefined; - try gz.addDbgBlockBegin(); - const sub_scope = if (payload_token == 0) defer_scope.parent else blk: { - const ident_name = try astgen.identAsString(payload_token); - local_val_scope = .{ - .parent = defer_scope.parent, - .gen_zir = gz, - .name = ident_name, - .inst = err_code, - .token_src = payload_token, - .id_cat = .@"capture", - }; - try gz.addDbgVar(.dbg_var_val, ident_name, err_code); - break :blk &local_val_scope.base; - }; - try unusedResultDeferExpr(gz, defer_scope, sub_scope, expr_node); - try checkUsed(gz, scope, sub_scope); - try gz.addDbgBlockEnd(); + if (defer_scope.remapped_err_code == 0) { + try gz.addDefer(defer_scope.index, defer_scope.len); + } else { + try gz.instructions.ensureUnusedCapacity(gpa, 1); + try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1); + + const payload_index = try gz.astgen.addExtra(Zir.Inst.DeferErrCode{ + .remapped_err_code = defer_scope.remapped_err_code, + .index = defer_scope.index, + .len = defer_scope.len, + }); + const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len); + gz.astgen.instructions.appendAssumeCapacity(.{ + .tag = .defer_err_code, + .data = .{ .defer_err_code = .{ + .err_code = err_code, + .payload_index = payload_index, + } }, + }); + gz.instructions.appendAssumeCapacity(new_index); + } }, .normal_only => continue, } @@ -2752,35 +2711,68 @@ fn checkUsed( scope = s.parent; }, .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent, - .defer_gen => scope = scope.cast(Scope.DeferGen).?.parent, .namespace => unreachable, .top => unreachable, } } } -fn makeDeferScope( - astgen: *AstGen, +fn deferStmt( + gz: *GenZir, scope: *Scope, node: Ast.Node.Index, block_arena: Allocator, scope_tag: Scope.Tag, ) InnerError!*Scope { - const tree = astgen.tree; + var defer_gen = gz.makeSubBlock(scope); + defer_gen.cur_defer_node = node; + defer_gen.any_defer_node = node; + defer defer_gen.unstack(); + + const tree = gz.astgen.tree; const node_datas = tree.nodes.items(.data); const expr_node = node_datas[node].rhs; - const token_starts = tree.tokens.items(.start); - const node_start = token_starts[tree.firstToken(expr_node)]; + + const payload_token = node_datas[node].lhs; + var local_val_scope: Scope.LocalVal = undefined; + var remapped_err_code: Zir.Inst.Index = 0; + const have_err_code = scope_tag == .defer_error and payload_token != 0; + const sub_scope = if (!have_err_code) &defer_gen.base else blk: { + try gz.addDbgBlockBegin(); + const ident_name = try gz.astgen.identAsString(payload_token); + remapped_err_code = @intCast(u32, try gz.astgen.instructions.addOne(gz.astgen.gpa)); + const remapped_err_code_ref = Zir.indexToRef(remapped_err_code); + local_val_scope = .{ + .parent = &defer_gen.base, + .gen_zir = gz, + .name = ident_name, + .inst = remapped_err_code_ref, + .token_src = payload_token, + .id_cat = .@"capture", + }; + try gz.addDbgVar(.dbg_var_val, ident_name, remapped_err_code_ref); + break :blk &local_val_scope.base; + }; + _ = try unusedResultExpr(&defer_gen, sub_scope, expr_node); + try checkUsed(gz, scope, sub_scope); + if (have_err_code) try gz.addDbgBlockEnd(); + _ = try defer_gen.addBreak(.break_inline, 0, .void_value); + + const body = defer_gen.instructionsSlice(); + const body_len = gz.astgen.countBodyLenAfterFixups(body); + + const index = @intCast(u32, gz.astgen.extra.items.len); + try gz.astgen.extra.ensureUnusedCapacity(gz.astgen.gpa, body_len); + gz.astgen.appendBodyWithFixups(body); + const defer_scope = try block_arena.create(Scope.Defer); - astgen.advanceSourceCursor(node_start); defer_scope.* = .{ .base = .{ .tag = scope_tag }, .parent = scope, - .defer_node = node, - .source_offset = astgen.source_offset, - .source_line = astgen.source_line, - .source_column = astgen.source_column, + .index = index, + .len = body_len, + .remapped_err_code = remapped_err_code, }; return &defer_scope.base; } @@ -3461,7 +3453,6 @@ fn fnDecl( var decl_gz: GenZir = .{ .force_comptime = true, - .in_defer = false, .decl_node_index = fn_proto.ast.proto_node, .decl_line = astgen.source_line, .parent = scope, @@ -3473,7 +3464,6 @@ fn fnDecl( var fn_gz: GenZir = .{ .force_comptime = false, - .in_defer = false, .decl_node_index = fn_proto.ast.proto_node, .decl_line = decl_gz.decl_line, .parent = &decl_gz.base, @@ -3812,7 +3802,6 @@ fn globalVarDecl( .decl_line = astgen.source_line, .astgen = astgen, .force_comptime = true, - .in_defer = false, .anon_name_strategy = .parent, .instructions = gz.instructions, .instructions_top = gz.instructions.items.len, @@ -3964,7 +3953,6 @@ fn comptimeDecl( var decl_block: GenZir = .{ .force_comptime = true, - .in_defer = false, .decl_node_index = node, .decl_line = astgen.source_line, .parent = scope, @@ -4019,7 +4007,6 @@ fn usingnamespaceDecl( var decl_block: GenZir = .{ .force_comptime = true, - .in_defer = false, .decl_node_index = node, .decl_line = astgen.source_line, .parent = scope, @@ -4067,7 +4054,6 @@ fn testDecl( var decl_block: GenZir = .{ .force_comptime = true, - .in_defer = false, .decl_node_index = node, .decl_line = astgen.source_line, .parent = scope, @@ -4132,7 +4118,6 @@ fn testDecl( .local_val, .local_ptr => unreachable, // a test cannot be in a local scope .gen_zir => s = s.cast(GenZir).?.parent, .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent, - .defer_gen => s = s.cast(Scope.DeferGen).?.parent, .namespace => { const ns = s.cast(Scope.Namespace).?; if (ns.decls.get(name_str_index)) |i| { @@ -4164,7 +4149,6 @@ fn testDecl( var fn_block: GenZir = .{ .force_comptime = false, - .in_defer = false, .decl_node_index = node, .decl_line = decl_block.decl_line, .parent = &decl_block.base, @@ -4288,7 +4272,6 @@ fn structDeclInner( .decl_line = decl_line, .astgen = astgen, .force_comptime = true, - .in_defer = false, .instructions = gz.instructions, .instructions_top = gz.instructions.items.len, }; @@ -4489,7 +4472,6 @@ fn unionDeclInner( .decl_line = astgen.source_line, .astgen = astgen, .force_comptime = true, - .in_defer = false, .instructions = gz.instructions, .instructions_top = gz.instructions.items.len, }; @@ -4773,7 +4755,6 @@ fn containerDecl( .decl_line = astgen.source_line, .astgen = astgen, .force_comptime = true, - .in_defer = false, .instructions = gz.instructions, .instructions_top = gz.instructions.items.len, }; @@ -4879,7 +4860,6 @@ fn containerDecl( .decl_line = astgen.source_line, .astgen = astgen, .force_comptime = true, - .in_defer = false, .instructions = gz.instructions, .instructions_top = gz.instructions.items.len, }; @@ -5110,7 +5090,15 @@ fn tryExpr( return astgen.failNode(node, "'try' outside function scope", .{}); }; - if (parent_gz.in_defer) return astgen.failNode(node, "'try' not allowed inside defer expression", .{}); + if (parent_gz.any_defer_node != 0) { + return astgen.failNodeNotes(node, "'try' not allowed inside defer expression", .{}, &.{ + try astgen.errNoteNode( + parent_gz.any_defer_node, + "defer expression here", + .{}, + ), + }); + } // Ensure debug line/column information is emitted for this try expression. // Then we will save the line/column so that we can emit another one that goes @@ -6656,7 +6644,15 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref return astgen.failNode(node, "'return' outside function scope", .{}); } - if (gz.in_defer) return astgen.failNode(node, "cannot return from defer expression", .{}); + if (gz.any_defer_node != 0) { + return astgen.failNodeNotes(node, "cannot return from defer expression", .{}, &.{ + try astgen.errNoteNode( + gz.any_defer_node, + "defer expression here", + .{}, + ), + }); + } // Ensure debug line/column information is emitted for this return expression. // Then we will save the line/column so that we can emit another one that goes @@ -6683,7 +6679,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref // for detecting whether to add something to the function's inferred error set. const ident_token = node_datas[operand_node].rhs; const err_name_str_index = try astgen.identAsString(ident_token); - const defer_counts = countDefers(astgen, defer_outer, scope); + const defer_counts = countDefers(defer_outer, scope); if (!defer_counts.need_err_code) { try genDefers(gz, defer_outer, scope, .both_sans_err); try emitDbgStmt(gz, ret_line, ret_column); @@ -6724,7 +6720,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref return Zir.Inst.Ref.unreachable_value; }, .maybe => { - const defer_counts = countDefers(astgen, defer_outer, scope); + const defer_counts = countDefers(defer_outer, scope); if (!defer_counts.have_err) { // Only regular defers; no branch needed. try genDefers(gz, defer_outer, scope, .normal_only); @@ -6921,7 +6917,6 @@ fn localVarRef( }, .gen_zir => s = s.cast(GenZir).?.parent, .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent, - .defer_gen => s = s.cast(Scope.DeferGen).?.parent, .namespace => { const ns = s.cast(Scope.Namespace).?; if (ns.decls.get(name_str_index)) |i| { @@ -7550,7 +7545,6 @@ fn builtinCall( }, .gen_zir => s = s.cast(GenZir).?.parent, .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent, - .defer_gen => s = s.cast(Scope.DeferGen).?.parent, .namespace => { const ns = s.cast(Scope.Namespace).?; if (ns.decls.get(decl_name)) |i| { @@ -10046,7 +10040,6 @@ const Scope = struct { local_ptr, defer_normal, defer_error, - defer_gen, namespace, top, }; @@ -10105,10 +10098,9 @@ const Scope = struct { base: Scope, /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`, `Namespace`. parent: *Scope, - defer_node: Ast.Node.Index, - source_offset: u32, - source_line: u32, - source_column: u32, + index: u32, + len: u32, + remapped_err_code: Zir.Inst.Index = 0, }; /// Represents a global scope that has any number of declarations in it. @@ -10144,13 +10136,6 @@ const Scope = struct { const base_tag: Scope.Tag = .top; base: Scope = Scope{ .tag = base_tag }, }; - - const DeferGen = struct { - const base_tag: Scope.Tag = .defer_gen; - base: Scope = Scope{ .tag = base_tag }, - parent: *Scope, - defer_node: Ast.Node.Index, - }; }; /// This is a temporary structure; references to it are valid only @@ -10161,7 +10146,6 @@ const GenZir = struct { force_comptime: bool, /// This is set to true for inline loops; false otherwise. is_inline: bool = false, - in_defer: bool, c_import: bool = false, /// How decls created in this scope should be named. anon_name_strategy: Zir.Inst.NameStrategy = .anon, @@ -10204,6 +10188,10 @@ const GenZir = struct { suspend_node: Ast.Node.Index = 0, nosuspend_node: Ast.Node.Index = 0, + /// Set if this GenZir is a defer. + cur_defer_node: Ast.Node.Index = 0, + // Set if this GenZir is a defer or it is inside a defer. + any_defer_node: Ast.Node.Index = 0, /// Namespace members are lazy. When executing a decl within a namespace, /// any references to external instructions need to be treated specially. @@ -10244,7 +10232,6 @@ const GenZir = struct { fn makeSubBlock(gz: *GenZir, scope: *Scope) GenZir { return .{ .force_comptime = gz.force_comptime, - .in_defer = gz.in_defer, .c_import = gz.c_import, .decl_node_index = gz.decl_node_index, .decl_line = gz.decl_line, @@ -10253,6 +10240,7 @@ const GenZir = struct { .astgen = gz.astgen, .suspend_node = gz.suspend_node, .nosuspend_node = gz.nosuspend_node, + .any_defer_node = gz.any_defer_node, .instructions = gz.instructions, .instructions_top = gz.instructions.items.len, }; @@ -11107,6 +11095,16 @@ const GenZir = struct { }); } + fn addDefer(gz: *GenZir, index: u32, len: u32) !void { + _ = try gz.add(.{ + .tag = .@"defer", + .data = .{ .@"defer" = .{ + .index = index, + .len = len, + } }, + }); + } + fn addDecl( gz: *GenZir, tag: Zir.Inst.Tag, @@ -11670,7 +11668,6 @@ fn detectLocalShadowing( }, .gen_zir => s = s.cast(GenZir).?.parent, .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent, - .defer_gen => s = s.cast(Scope.DeferGen).?.parent, .top => break, }; } @@ -11809,7 +11806,6 @@ fn scanDecls(astgen: *AstGen, namespace: *Scope.Namespace, members: []const Ast. .namespace => s = s.cast(Scope.Namespace).?.parent, .gen_zir => s = s.cast(GenZir).?.parent, .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent, - .defer_gen => s = s.cast(Scope.DeferGen).?.parent, .top => break, }; gop.value_ptr.* = member_node; diff --git a/src/Sema.zig b/src/Sema.zig index 15e891ef870338ff331273fe7087b0340301ff0c..f5e4402ea853366fe5f145a7cf0042c7a2f91082 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1461,6 +1461,29 @@ fn analyzeBodyInner( // break break_data.inst; // } //}, + .@"defer" => blk: { + const inst_data = sema.code.instructions.items(.data)[inst].@"defer"; + const defer_body = sema.code.extra[inst_data.index..][0..inst_data.len]; + const break_inst = sema.analyzeBodyInner(block, defer_body) catch |err| switch (err) { + error.ComptimeBreak => sema.comptime_break_inst, + else => |e| return e, + }; + if (break_inst != defer_body[defer_body.len - 1]) break always_noreturn; + break :blk Air.Inst.Ref.void_value; + }, + .defer_err_code => blk: { + const inst_data = sema.code.instructions.items(.data)[inst].defer_err_code; + const extra = sema.code.extraData(Zir.Inst.DeferErrCode, inst_data.payload_index).data; + const defer_body = sema.code.extra[extra.index..][0..extra.len]; + const err_code = try sema.resolveInst(inst_data.err_code); + try sema.inst_map.put(sema.gpa, extra.remapped_err_code, err_code); + const break_inst = sema.analyzeBodyInner(block, defer_body) catch |err| switch (err) { + error.ComptimeBreak => sema.comptime_break_inst, + else => |e| return e, + }; + if (break_inst != defer_body[defer_body.len - 1]) break always_noreturn; + break :blk Air.Inst.Ref.void_value; + }, }; if (sema.typeOf(air_inst).isNoReturn()) break always_noreturn; @@ -9394,11 +9417,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } if (special_prong == .@"else" and seen_errors.count() == operand_ty.errorSetNames().len) { - - // TODO re-enable if defer implementation is improved - // https://github.com/ziglang/zig/issues/11798 - if (true) break :else_validation; - // In order to enable common patterns for generic code allow simple else bodies // else => unreachable, // else => return, @@ -9415,6 +9433,12 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError .as_node, .ret_node, .@"unreachable", + .@"defer", + .defer_err_code, + .err_union_code, + .ret_err_value_code, + .is_non_err, + .condbr, => {}, else => break, } else break :else_validation; diff --git a/src/Zir.zig b/src/Zir.zig index 3ce086e10b3a567ad1d3aab610d4362739f8f677..9621aefcf68843c1f43a3013c7084bc81a75a44c 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -995,6 +995,13 @@ pub const Inst = struct { /// closure_capture instruction ref. closure_get, + /// A defer statement. + /// Uses the `defer` union field. + @"defer", + /// An errdefer statement with a code. + /// Uses the `err_defer_code` union field. + defer_err_code, + /// The ZIR instruction tag is one of the `Extended` ones. /// Uses the `extended` union field. extended, @@ -1244,6 +1251,8 @@ pub const Inst = struct { .try_ptr, //.try_inline, //.try_ptr_inline, + .@"defer", + .defer_err_code, => false, .@"break", @@ -1311,6 +1320,8 @@ pub const Inst = struct { .memcpy, .memset, .check_comptime_control_flow, + .@"defer", + .defer_err_code, => true, .param, @@ -1819,6 +1830,9 @@ pub const Inst = struct { .closure_capture = .un_tok, .closure_get = .inst_node, + .@"defer" = .@"defer", + .defer_err_code = .defer_err_code, + .extended = .extended, }); }; @@ -2575,6 +2589,14 @@ pub const Inst = struct { return zir.nullTerminatedString(self.str); } }, + @"defer": struct { + index: u32, + len: u32, + }, + defer_err_code: struct { + err_code: Ref, + payload_index: u32, + }, // Make sure we don't accidentally add a field to make this union // bigger than expected. Note that in Debug builds, Zig is allowed @@ -2611,6 +2633,8 @@ pub const Inst = struct { dbg_stmt, inst_node, str_op, + @"defer", + defer_err_code, }; }; @@ -3550,6 +3574,12 @@ pub const Inst = struct { line: u32, column: u32, }; + + pub const DeferErrCode = struct { + remapped_err_code: Index, + index: u32, + len: u32, + }; }; pub const SpecialProng = enum { none, @"else", under }; diff --git a/src/print_zir.zig b/src/print_zir.zig index 976d0121388f3d20b4aec7077b392a62280dec42..fb1d2960c4b42a1b9bafe9b6a11915fd9f564075 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -446,6 +446,9 @@ const Writer = struct { .closure_get => try self.writeInstNode(stream, inst), + .@"defer" => try self.writeDefer(stream, inst), + .defer_err_code => try self.writeDeferErrCode(stream, inst), + .extended => try self.writeExtended(stream, inst), } } @@ -2364,6 +2367,26 @@ const Writer = struct { try stream.print("{d}, {d})", .{ inst_data.line + 1, inst_data.column + 1 }); } + fn writeDefer(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].@"defer"; + const body = self.code.extra[inst_data.index..][0..inst_data.len]; + try self.writeBracedBody(stream, body); + try stream.writeByte(')'); + } + + fn writeDeferErrCode(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].defer_err_code; + const extra = self.code.extraData(Zir.Inst.DeferErrCode, inst_data.payload_index).data; + + try self.writeInstRef(stream, Zir.indexToRef(extra.remapped_err_code)); + try stream.writeAll(" = "); + try self.writeInstRef(stream, inst_data.err_code); + try stream.writeAll(", "); + const body = self.code.extra[extra.index..][0..extra.len]; + try self.writeBracedBody(stream, body); + try stream.writeByte(')'); + } + fn writeInstRef(self: *Writer, stream: anytype, ref: Zir.Inst.Ref) !void { var i: usize = @enumToInt(ref); diff --git a/test/behavior/defer.zig b/test/behavior/defer.zig index 70053ba01f9dc643d9cb249c18bc8822d1b643ed..7bf66496a4646875282a530d0240a6ab1b3e2c27 100644 --- a/test/behavior/defer.zig +++ b/test/behavior/defer.zig @@ -127,3 +127,20 @@ test "errdefer with payload" { try S.doTheTest(); comptime try S.doTheTest(); } + +test "simple else prong doesn't emit an error for unreachable else prong" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + + const S = struct { + fn foo() error{Foo}!void { + return error.Foo; + } + }; + var a: u32 = 0; + defer a += 1; + S.foo() catch |err| switch (err) { + error.Foo => a += 1, + else => |e| return e, + }; + try expect(a == 1); +} diff --git a/test/cases/compile_errors/return_from_defer_expression.zig b/test/cases/compile_errors/return_from_defer_expression.zig index e60b8ae848bb0141d652a58b6f00929131962282..12aa08a11cdc8bb553890c6bae5e5f208c710c7f 100644 --- a/test/cases/compile_errors/return_from_defer_expression.zig +++ b/test/cases/compile_errors/return_from_defer_expression.zig @@ -19,3 +19,4 @@ export fn entry() usize { return @sizeOf(@TypeOf(testTrickyDefer)); } // target=native // // :4:11: error: 'try' not allowed inside defer expression +// :4:5: note: defer expression here diff --git a/test/cases/compile_errors/uncreachable_else_prong_err_set.zig b/test/cases/compile_errors/uncreachable_else_prong_err_set.zig new file mode 100644 index 0000000000000000000000000000000000000000..4b0ae462d6041943387f8cbca37367e7670ed5a9 --- /dev/null +++ b/test/cases/compile_errors/uncreachable_else_prong_err_set.zig @@ -0,0 +1,25 @@ +pub export fn complex() void { + var a: error{ Foo, Bar } = error.Foo; + switch (a) { + error.Foo => unreachable, + error.Bar => unreachable, + else => { + @compileError(""); + }, + } +} + +pub export fn simple() void { + var a: error{ Foo, Bar } = error.Foo; + switch (a) { + error.Foo => unreachable, + error.Bar => unreachable, + else => |e| return e, + } +} + +// error +// backend=llvm +// target=native +// +// :6:14: error: unreachable else prong; all cases already handled diff --git a/test/cases/returns_in_try.zig b/test/cases/returns_in_try.zig index de5a9a3258288c1357d96172f1e8458abe45d4d1..e283b93db2642182ad5b21e326fb95daf7b4aa8a 100644 --- a/test/cases/returns_in_try.zig +++ b/test/cases/returns_in_try.zig @@ -13,4 +13,6 @@ pub fn b() !void { // error // // :7:11: error: 'try' not allowed inside defer expression +// :7:5: note: defer expression here // :10:11: error: cannot return from defer expression +// :10:5: note: defer expression here -- 2.54.0