compiler: remove capturing errdefer from the language

Resolves: https://github.com/ziglang/zig/issues/23734
This commit is contained in:
Matthew Lugg
2026-04-28 19:18:39 +01:00
parent e67c344fc0
commit d764716cb5
13 changed files with 31 additions and 267 deletions
+1 -1
View File
@@ -7774,7 +7774,7 @@ ContainerField <- doc_comment? (KEYWORD_comptime / !KEYWORD_comptime) !KEYWORD_f
BlockStatement
<- Statement
/ KEYWORD_defer BlockExprStatement
/ KEYWORD_errdefer Payload? BlockExprStatement
/ KEYWORD_errdefer BlockExprStatement
/ !ExprStatement (KEYWORD_comptime !BlockExpr)? VarAssignStatement
Statement
+1 -5
View File
@@ -223,12 +223,8 @@ fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
return walkBlock(w, node, statements);
},
.@"errdefer" => {
const expr = ast.nodeData(node).opt_token_and_node[1];
return walkExpression(w, expr);
},
.@"defer",
.@"errdefer",
.@"comptime",
.@"nosuspend",
.@"suspend",
+1 -2
View File
@@ -1014,8 +1014,7 @@ fn block(
.grouped_expression => try expr(w, scope, parent_decl, ast.nodeData(node).node_and_token[0]),
.@"defer" => try expr(w, scope, parent_decl, ast.nodeData(node).node),
.@"errdefer" => try expr(w, scope, parent_decl, ast.nodeData(node).opt_token_and_node[1]),
.@"defer", .@"errdefer" => try expr(w, scope, parent_decl, ast.nodeData(node).node),
else => try expr(w, scope, parent_decl, node),
}
+4 -7
View File
@@ -952,8 +952,8 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex {
.switch_range,
=> n = tree.nodeData(n).node_and_node[1],
.test_decl, .@"errdefer" => n = tree.nodeData(n).opt_token_and_node[1],
.@"defer" => n = tree.nodeData(n).node,
.test_decl => n = tree.nodeData(n).opt_token_and_node[1],
.@"defer", .@"errdefer" => n = tree.nodeData(n).node,
.anyframe_type => n = tree.nodeData(n).token_and_node[1],
.switch_case_one,
@@ -3059,11 +3059,8 @@ pub const Node = struct {
/// a `assign_destructure` node or a parsing error occured.
aligned_var_decl,
/// `errdefer expr`,
/// `errdefer |payload| expr`.
///
/// The `data` field is a `.opt_token_and_node`:
/// 1. a `OptionalTokenIndex` to the payload identifier, if any.
/// 2. a `Node.Index` to the deferred expression.
/// The `data` field is a `.node` to the deferred expression.
///
/// The `main_token` field is the `errdefer` token.
@"errdefer",
@@ -3071,7 +3068,7 @@ pub const Node = struct {
///
/// The `data` field is a `.node` to the deferred expression.
///
/// The `main_token` field is the `defer`.
/// The `main_token` field is the `defer` token.
@"defer",
/// `lhs catch rhs`,
/// `lhs catch |err| rhs`.
+1 -13
View File
@@ -394,20 +394,8 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
return renderBlock(r, node, statements, space);
},
.@"errdefer" => {
const defer_token = tree.nodeMainToken(node);
const maybe_payload_token, const expr = tree.nodeData(node).opt_token_and_node;
try renderToken(r, defer_token, .maybe_space);
if (maybe_payload_token.unwrap()) |payload_token| {
try renderToken(r, payload_token - 1, .none); // |
try renderIdentifier(r, payload_token, .none, .preserve_when_shadowing); // identifier
try renderToken(r, payload_token + 1, .maybe_space); // |
}
return renderExpression(r, expr, space);
},
.@"defer",
.@"errdefer",
.@"comptime",
.@"nosuspend",
.@"suspend",
+19 -120
View File
@@ -2875,7 +2875,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.max,
.min,
.@"resume",
.ret_err_value_code,
.ret_ptr,
.ret_type,
.for_len,
@@ -2964,7 +2963,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
=> break :b true,
.@"defer" => unreachable,
.defer_err_code => unreachable,
}
} else switch (maybe_unused_result) {
.none => unreachable,
@@ -2984,60 +2982,28 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
return noreturn_src_node;
}
fn countDefers(outer_scope: *Scope, inner_scope: *Scope) struct {
have_any: bool,
have_normal: bool,
have_err: bool,
need_err_code: bool,
} {
var have_normal = false;
var have_err = false;
var need_err_code = false;
fn anyErrdefers(outer_scope: *Scope, inner_scope: *Scope) bool {
var scope = inner_scope;
while (scope != outer_scope) {
switch (scope.unwrap()) {
.gen_zir => |gen_zir| scope = gen_zir.parent,
.local_val => |local_val| scope = local_val.parent,
.local_ptr => |local_ptr| scope = local_ptr.parent,
.defer_normal => |defer_scope| {
scope = defer_scope.parent;
have_normal = true;
},
.defer_error => |defer_scope| {
scope = defer_scope.parent;
have_err = true;
const have_err_payload = defer_scope.remapped_err_code != .none;
need_err_code = need_err_code or have_err_payload;
},
.defer_normal => |defer_normal| scope = defer_normal.parent,
.defer_error => return true,
.namespace => unreachable,
.top => unreachable,
}
}
return .{
.have_any = have_normal or have_err,
.have_normal = have_normal,
.have_err = have_err,
.need_err_code = need_err_code,
};
return false;
}
const DefersToEmit = union(enum) {
both: Zir.Inst.Ref, // err code
both_sans_err,
normal_only,
};
fn genDefers(
gz: *GenZir,
outer_scope: *Scope,
inner_scope: *Scope,
which_ones: DefersToEmit,
which_ones: enum { normal_only, normal_and_error },
) InnerError!void {
const gpa = gz.astgen.gpa;
var scope = inner_scope;
while (scope != outer_scope) {
switch (scope.unwrap()) {
@@ -3051,33 +3017,10 @@ fn genDefers(
.defer_error => |defer_scope| {
scope = defer_scope.parent;
switch (which_ones) {
.both_sans_err => {
.normal_only => continue,
.normal_and_error => {
try gz.addDefer(defer_scope.index, defer_scope.len);
},
.both => |err_code| {
if (defer_scope.remapped_err_code.unwrap()) |remapped_err_code| {
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 = remapped_err_code,
.index = defer_scope.index,
.len = defer_scope.len,
});
const new_index: Zir.Inst.Index = @enumFromInt(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);
} else {
try gz.addDefer(defer_scope.index, defer_scope.len);
}
},
.normal_only => continue,
}
},
.namespace => unreachable,
@@ -3140,46 +3083,17 @@ fn deferStmt(
defer defer_gen.unstack();
const tree = gz.astgen.tree;
var local_val_scope: Scope.LocalVal = undefined;
var opt_remapped_err_code: Zir.Inst.OptionalIndex = .none;
const sub_scope = if (scope_tag != .defer_error) &defer_gen.base else blk: {
const payload_token = tree.nodeData(node).opt_token_and_node[0].unwrap() orelse break :blk &defer_gen.base;
const ident_name = try gz.astgen.identAsString(payload_token);
if (std.mem.eql(u8, tree.tokenSlice(payload_token), "_")) {
try gz.astgen.appendErrorTok(payload_token, "discard of error capture; omit it instead", .{});
break :blk &defer_gen.base;
}
const remapped_err_code: Zir.Inst.Index = @enumFromInt(gz.astgen.instructions.len);
opt_remapped_err_code = remapped_err_code.toOptional();
_ = try gz.astgen.appendPlaceholder();
const remapped_err_code_ref = remapped_err_code.toRef();
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;
};
const expr_node = switch (scope_tag) {
.defer_normal => tree.nodeData(node).node,
.defer_error => tree.nodeData(node).opt_token_and_node[1],
else => unreachable,
};
_ = try unusedResultExpr(&defer_gen, sub_scope, expr_node);
try checkUsed(gz, scope, sub_scope);
const expr_node = tree.nodeData(node).node;
_ = try unusedResultExpr(&defer_gen, &defer_gen.base, expr_node);
try checkUsed(gz, scope, &defer_gen.base);
_ = try defer_gen.addBreak(.break_inline, @enumFromInt(0), .void_value);
const body = defer_gen.instructionsSlice();
const extra_insts: []const Zir.Inst.Index = if (opt_remapped_err_code.unwrap()) |ec| &.{ec} else &.{};
const body_len = gz.astgen.countBodyLenAfterFixupsExtraRefs(body, extra_insts);
const body_len = gz.astgen.countBodyLenAfterFixupsExtraRefs(body, &.{});
const index: u32 = @intCast(gz.astgen.extra.items.len);
try gz.astgen.extra.ensureUnusedCapacity(gz.astgen.gpa, body_len);
gz.astgen.appendBodyWithFixupsExtraRefsArrayList(&gz.astgen.extra, body, extra_insts);
gz.astgen.appendBodyWithFixupsExtraRefsArrayList(&gz.astgen.extra, body, &.{});
const defer_scope = try block_arena.create(Scope.Defer);
@@ -3188,7 +3102,6 @@ fn deferStmt(
.parent = scope,
.index = index,
.len = body_len,
.remapped_err_code = opt_remapped_err_code,
};
return &defer_scope.base;
}
@@ -5882,7 +5795,7 @@ fn tryExpr(
else => Zir.Inst.Tag.err_union_code,
};
const err_code = try else_scope.addUnNode(err_tag, operand, node);
try genDefers(&else_scope, &fn_block.base, scope, .{ .both = err_code });
try genDefers(&else_scope, &fn_block.base, scope, .normal_and_error);
try emitDbgStmt(&else_scope, try_lc);
_ = try else_scope.addUnNode(.ret_node, err_code, node);
@@ -8033,18 +7946,10 @@ 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 = tree.nodeMainToken(operand_node) + 2;
const err_name_str_index = try astgen.identAsString(ident_token);
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_lc);
_ = try gz.addStrTok(.ret_err_value, err_name_str_index, ident_token);
return Zir.Inst.Ref.unreachable_value;
}
const err_code = try gz.addStrTok(.ret_err_value_code, err_name_str_index, ident_token);
try genDefers(gz, defer_outer, scope, .{ .both = err_code });
try genDefers(gz, defer_outer, scope, .normal_and_error);
try emitDbgStmt(gz, ret_lc);
_ = try gz.addUnNode(.ret_node, err_code, node);
return Zir.Inst.Ref.unreachable_value;
_ = try gz.addStrTok(.ret_err_value, err_name_str_index, ident_token);
return .unreachable_value;
}
const ri: ResultInfo = if (astgen.nodes_need_rl.contains(node)) .{
@@ -8071,15 +7976,13 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
},
.always => {
// Value is always an error. Emit both error defers and regular defers.
const err_code = if (ri.rl == .ptr) try gz.addUnNode(.load, ri.rl.ptr.inst, node) else operand;
try genDefers(gz, defer_outer, scope, .{ .both = err_code });
try genDefers(gz, defer_outer, scope, .normal_and_error);
try emitDbgStmt(gz, ret_lc);
try gz.addRet(ri, operand, node);
return Zir.Inst.Ref.unreachable_value;
},
.maybe => {
const defer_counts = countDefers(defer_outer, scope);
if (!defer_counts.have_err) {
if (!anyErrdefers(defer_outer, scope)) {
// Only regular defers; no branch needed.
try genDefers(gz, defer_outer, scope, .normal_only);
try emitDbgStmt(gz, ret_lc);
@@ -8111,10 +8014,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
var else_scope = gz.makeSubBlock(scope);
defer else_scope.unstack();
const which_ones: DefersToEmit = if (!defer_counts.need_err_code) .both_sans_err else .{
.both = try else_scope.addUnNode(.err_union_code, result, node),
};
try genDefers(&else_scope, defer_outer, scope, which_ones);
try genDefers(&else_scope, defer_outer, scope, .normal_and_error);
try emitDbgStmt(&else_scope, ret_lc);
try else_scope.addRet(ri, operand, node);
@@ -11239,7 +11139,6 @@ const Scope = struct {
parent: *Scope,
index: u32,
len: u32,
remapped_err_code: Zir.Inst.OptionalIndex = .none,
};
/// Represents a global scope that has any number of declarations in it.
+1 -5
View File
@@ -141,11 +141,7 @@ fn expr(astrl: *AstRlAnnotate, node: Ast.Node.Index, block: ?*Block, ri: ResultI
.asm_input,
=> unreachable,
.@"errdefer" => {
_ = try astrl.expr(tree.nodeData(node).opt_token_and_node[1], block, ResultInfo.none);
return false;
},
.@"defer" => {
.@"defer", .@"errdefer" => {
_ = try astrl.expr(tree.nodeData(node).node, block, ResultInfo.none);
return false;
},
+1 -1
View File
@@ -486,7 +486,7 @@ fn pegContainerField(a: *AstSmith) SourceError!void {
/// BlockStatement
/// <- Statement
/// / KEYWORD_defer BlockExprStatement
/// / KEYWORD_errdefer Payload? BlockExprStatement
/// / KEYWORD_errdefer BlockExprStatement
/// / !ExprStatement (KEYWORD_comptime !BlockExpr)? VarAssignStatement
fn pegBlockStatement(a: *AstSmith) SourceError!void {
const Kind = enum {
+2 -5
View File
@@ -909,7 +909,7 @@ fn expectContainerField(p: *Parse) !Node.Index {
/// BlockStatement
/// <- Statement
/// / KEYWORD_defer BlockExprStatement
/// / KEYWORD_errdefer Payload? BlockExprStatement
/// / KEYWORD_errdefer BlockExprStatement
/// / !ExprStatement (KEYWORD_comptime !BlockExpr)? VarAssignStatement
///
/// Statement
@@ -975,10 +975,7 @@ fn expectStatement(p: *Parse, is_block_level: bool) Error!Node.Index {
.keyword_errdefer => if (is_block_level) return p.addNode(.{
.tag = .@"errdefer",
.main_token = p.nextToken(),
.data = .{ .opt_token_and_node = .{
try p.parsePayload(),
try p.expectBlockExprStatement(),
} },
.data = .{ .node = try p.expectBlockExprStatement() },
}),
.keyword_if => return p.expectIfStatement(),
.keyword_enum, .keyword_struct, .keyword_union => {
-37
View File
@@ -597,13 +597,6 @@ pub const Inst = struct {
/// name is added to it.
/// Uses the `str_tok` union field.
ret_err_value,
/// A string name is provided which is an anonymous error set value.
/// If the current function has an inferred error set, the error given by the
/// name is added to it.
/// Results in the error code. Note that control flow is not diverted with
/// this instruction; a following 'ret' instruction will do the diversion.
/// Uses the `str_tok` union field.
ret_err_value_code,
/// Obtains a pointer to the return value.
/// Uses the `node` union field.
ret_ptr,
@@ -1066,9 +1059,6 @@ pub const Inst = struct {
/// 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,
/// Requests that Sema update the saved error return trace index for the enclosing
/// block, if the operand is .none or of an error/error-union type.
@@ -1295,14 +1285,12 @@ pub const Inst = struct {
.memmove,
.min,
.@"resume",
.ret_err_value_code,
.extended,
.ret_ptr,
.ret_type,
.@"try",
.try_ptr,
.@"defer",
.defer_err_code,
.save_err_ret_index,
.for_len,
.opt_eu_base_ptr_init,
@@ -1377,7 +1365,6 @@ pub const Inst = struct {
.memmove,
.check_comptime_control_flow,
.@"defer",
.defer_err_code,
.save_err_ret_index,
.restore_err_ret_index_unconditional,
.restore_err_ret_index_fn_entry,
@@ -1574,7 +1561,6 @@ pub const Inst = struct {
.max,
.min,
.@"resume",
.ret_err_value_code,
.@"break",
.break_inline,
.condbr,
@@ -1732,7 +1718,6 @@ pub const Inst = struct {
.ret_load = .un_node,
.ret_implicit = .un_tok,
.ret_err_value = .str_tok,
.ret_err_value_code = .str_tok,
.ret_ptr = .node,
.ret_type = .node,
.ptr_type = .ptr_type,
@@ -1866,7 +1851,6 @@ pub const Inst = struct {
.@"resume" = .un_node,
.@"defer" = .@"defer",
.defer_err_code = .defer_err_code,
.save_err_ret_index = .save_err_ret_index,
.restore_err_ret_index_unconditional = .un_node,
@@ -2484,10 +2468,6 @@ pub const Inst = struct {
index: u32,
len: u32,
},
defer_err_code: struct {
err_code: Ref,
payload_index: u32,
},
save_err_ret_index: struct {
operand: Ref, // If error type (or .none), save new trace index
},
@@ -2538,7 +2518,6 @@ pub const Inst = struct {
inst_node,
str_op,
@"defer",
defer_err_code,
save_err_ret_index,
elem_val_imm,
declaration,
@@ -3974,12 +3953,6 @@ pub const Inst = struct {
column: u32,
};
pub const DeferErrCode = struct {
remapped_err_code: Index,
index: u32,
len: u32,
};
pub const ValidateDestructure = struct {
/// The value being destructured.
operand: Ref,
@@ -4222,7 +4195,6 @@ fn findTrackableInner(
.ret_load,
.ret_implicit,
.ret_err_value,
.ret_err_value_code,
.ret_ptr,
.ret_type,
.ptr_type,
@@ -4621,15 +4593,6 @@ fn findTrackableInner(
try zir.findTrackableBody(gpa, contents, defers, body);
}
},
.defer_err_code => {
const inst_data = datas[@intFromEnum(inst)].defer_err_code;
const extra = zir.extraData(Inst.DeferErrCode, inst_data.payload_index).data;
const gop = try defers.getOrPut(gpa, extra.index);
if (!gop.found_existing) {
const body = zir.bodySlice(extra.index, extra.len);
try zir.findTrackableBody(gpa, contents, defers, body);
}
},
}
}
-13
View File
@@ -298,19 +298,6 @@ test "zig fmt: decl between fields" {
});
}
test "zig fmt: errdefer with payload" {
try testCanonical(
\\pub fn main() anyerror!void {
\\ errdefer |a| x += 1;
\\ errdefer |a| {}
\\ errdefer |a| {
\\ x += 1;
\\ }
\\}
\\
);
}
test "zig fmt: nosuspend block" {
try testCanonical(
\\pub fn main() anyerror!void {
-43
View File
@@ -1245,7 +1245,6 @@ fn analyzeBodyInner(
.optional_type => try sema.zirOptionalType(block, inst),
.ptr_type => try sema.zirPtrType(block, inst),
.ref => try sema.zirRef(block, inst),
.ret_err_value_code => try sema.zirRetErrValueCode(inst),
.shr => try sema.zirShr(block, inst, .shr),
.shr_exact => try sema.zirShr(block, inst, .shr_exact),
.slice_end => try sema.zirSliceEnd(block, inst),
@@ -1969,25 +1968,6 @@ fn analyzeBodyInner(
}
break :blk .void_value;
},
.defer_err_code => blk: {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].defer_err_code;
const extra = sema.code.extraData(Zir.Inst.DeferErrCode, inst_data.payload_index).data;
const defer_body = sema.code.bodySlice(extra.index, extra.len);
const err_code = sema.resolveInst(inst_data.err_code);
try map.ensureSpaceForInstructions(sema.gpa, defer_body);
map.putAssumeCapacity(extra.remapped_err_code, err_code);
if (sema.analyzeBodyInner(block, defer_body)) {
// The defer terminated noreturn - no more analysis needed.
break;
} else |err| switch (err) {
error.ComptimeBreak => {},
else => |e| return e,
}
if (sema.comptime_break_inst != defer_body[defer_body.len - 1]) {
return error.ComptimeBreak;
}
break :blk .void_value;
},
};
const is_inferred_alloc = if (air_ref.toIndex()) |air_inst| switch (sema.air_instructions.items(.tag)[@intFromEnum(air_inst)]) {
@@ -12938,29 +12918,6 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
return Air.internedToRef(result.val);
}
fn zirRetErrValueCode(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const comp = zcu.comp;
const gpa = comp.gpa;
const io = comp.io;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
const name = try zcu.intern_pool.getOrPutString(
gpa,
io,
pt.tid,
inst_data.get(sema.code),
.no_embedded_nulls,
);
_ = try pt.getErrorValue(name);
const error_set_type = try pt.singleErrorSetType(name);
return Air.internedToRef((try pt.intern(.{ .err = .{
.ty = error_set_type.toIntern(),
.name = name,
} })));
}
fn zirShl(
sema: *Sema,
block: *Block,
-15
View File
@@ -477,7 +477,6 @@ const Writer = struct {
.decl_ref,
.decl_val,
.ret_err_value,
.ret_err_value_code,
.param_anytype,
.param_anytype_comptime,
=> try self.writeStrTok(stream, inst),
@@ -497,7 +496,6 @@ const Writer = struct {
.dbg_stmt => try self.writeDbgStmt(stream, inst),
.@"defer" => try self.writeDefer(stream, inst),
.defer_err_code => try self.writeDeferErrCode(stream, inst),
.declaration => try self.writeDeclaration(stream, inst),
@@ -2186,19 +2184,6 @@ const Writer = struct {
try stream.writeByte(')');
}
fn writeDeferErrCode(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].defer_err_code;
const extra = self.code.extraData(Zir.Inst.DeferErrCode, inst_data.payload_index).data;
try self.writeInstRef(stream, extra.remapped_err_code.toRef());
try stream.writeAll(" = ");
try self.writeInstRef(stream, inst_data.err_code);
try stream.writeAll(", ");
const body = self.code.bodySlice(extra.index, extra.len);
try self.writeBracedBody(stream, body);
try stream.writeByte(')');
}
fn writeDeclaration(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {
const decl = self.code.getDeclaration(inst);