Commit e323cf1264
Changed files (9)
test
behavior
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;
}
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;
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);
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;
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 };
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);
+}
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
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("<something complex here>");
+ },
+ }
+}
+
+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
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