Skip to content

Commit 6ce8440

Browse files
committed
AstGen: properly generate errdefer expressions when returning
`return` statements use a new function `nodeMayEvalToError` which does some basic checks on the AST node to return never, always, or maybe. Depending on this result, AstGen skips the errdefers, always includes the errdefers, or emits a conditional branch to check whether the return value is an error that Sema will have to evaluate. Closes #8821 Unblocks #9047
1 parent ccb6e16 commit 6ce8440

File tree

1 file changed

+259
-13
lines changed

1 file changed

+259
-13
lines changed

src/AstGen.zig

Lines changed: 259 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6005,22 +6005,55 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref
60056005
if (gz.in_defer) return astgen.failNode(node, "cannot return from defer expression", .{});
60066006

60076007
const operand_node = node_datas[node].lhs;
6008-
if (operand_node != 0) {
6009-
const rl: ResultLoc = if (nodeMayNeedMemoryLocation(tree, operand_node)) .{
6010-
.ptr = try gz.addNodeExtended(.ret_ptr, node),
6011-
} else .{
6012-
.ty = try gz.addNodeExtended(.ret_type, node),
6013-
};
6014-
const operand = try expr(gz, scope, rl, operand_node);
6015-
// TODO check operand to see if we need to generate errdefers
6008+
if (operand_node == 0) {
6009+
// Returning a void value; skip error defers.
60166010
try genDefers(gz, &astgen.fn_block.?.base, scope, .none);
6017-
_ = try gz.addUnNode(.ret_node, operand, node);
6011+
_ = try gz.addUnNode(.ret_node, .void_value, node);
60186012
return Zir.Inst.Ref.unreachable_value;
60196013
}
6020-
// Returning a void value; skip error defers.
6021-
try genDefers(gz, &astgen.fn_block.?.base, scope, .none);
6022-
_ = try gz.addUnNode(.ret_node, .void_value, node);
6023-
return Zir.Inst.Ref.unreachable_value;
6014+
6015+
const rl: ResultLoc = if (nodeMayNeedMemoryLocation(tree, operand_node)) .{
6016+
.ptr = try gz.addNodeExtended(.ret_ptr, node),
6017+
} else .{
6018+
.ty = try gz.addNodeExtended(.ret_type, node),
6019+
};
6020+
const operand = try expr(gz, scope, rl, operand_node);
6021+
6022+
switch (nodeMayEvalToError(tree, operand_node)) {
6023+
.never => {
6024+
// Returning a value that cannot be an error; skip error defers.
6025+
try genDefers(gz, &astgen.fn_block.?.base, scope, .none);
6026+
_ = try gz.addUnNode(.ret_node, operand, node);
6027+
return Zir.Inst.Ref.unreachable_value;
6028+
},
6029+
.always => {
6030+
// Value is always an error. Emit both error defers and regular defers.
6031+
const err_code = try gz.addUnNode(.err_union_code, operand, node);
6032+
try genDefers(gz, &astgen.fn_block.?.base, scope, err_code);
6033+
_ = try gz.addUnNode(.ret_node, operand, node);
6034+
return Zir.Inst.Ref.unreachable_value;
6035+
},
6036+
.maybe => {
6037+
// Emit conditional branch for generating errdefers.
6038+
const is_err = try gz.addUnNode(.is_err, operand, node);
6039+
const condbr = try gz.addCondBr(.condbr, node);
6040+
6041+
var then_scope = gz.makeSubBlock(scope);
6042+
defer then_scope.instructions.deinit(astgen.gpa);
6043+
const err_code = try then_scope.addUnNode(.err_union_code, operand, node);
6044+
try genDefers(&then_scope, &astgen.fn_block.?.base, scope, err_code);
6045+
_ = try then_scope.addUnNode(.ret_node, operand, node);
6046+
6047+
var else_scope = gz.makeSubBlock(scope);
6048+
defer else_scope.instructions.deinit(astgen.gpa);
6049+
try genDefers(&else_scope, &astgen.fn_block.?.base, scope, .none);
6050+
_ = try else_scope.addUnNode(.ret_node, operand, node);
6051+
6052+
try setCondBrPayload(condbr, is_err, &then_scope, &else_scope);
6053+
6054+
return Zir.Inst.Ref.unreachable_value;
6055+
},
6056+
}
60246057
}
60256058

60266059
fn identifier(
@@ -7608,6 +7641,219 @@ fn nodeMayNeedMemoryLocation(tree: *const ast.Tree, start_node: ast.Node.Index)
76087641
}
76097642
}
76107643

7644+
fn nodeMayEvalToError(tree: *const ast.Tree, start_node: ast.Node.Index) enum { never, always, maybe } {
7645+
const node_tags = tree.nodes.items(.tag);
7646+
const node_datas = tree.nodes.items(.data);
7647+
const main_tokens = tree.nodes.items(.main_token);
7648+
const token_tags = tree.tokens.items(.tag);
7649+
7650+
var node = start_node;
7651+
while (true) {
7652+
switch (node_tags[node]) {
7653+
.root,
7654+
.@"usingnamespace",
7655+
.test_decl,
7656+
.switch_case,
7657+
.switch_case_one,
7658+
.container_field_init,
7659+
.container_field_align,
7660+
.container_field,
7661+
.asm_output,
7662+
.asm_input,
7663+
=> unreachable,
7664+
7665+
.error_value => return .always,
7666+
7667+
.@"asm",
7668+
.asm_simple,
7669+
.identifier,
7670+
.field_access,
7671+
.deref,
7672+
.array_access,
7673+
.while_simple,
7674+
.while_cont,
7675+
.for_simple,
7676+
.if_simple,
7677+
.@"while",
7678+
.@"if",
7679+
.@"for",
7680+
.@"switch",
7681+
.switch_comma,
7682+
.call_one,
7683+
.call_one_comma,
7684+
.async_call_one,
7685+
.async_call_one_comma,
7686+
.call,
7687+
.call_comma,
7688+
.async_call,
7689+
.async_call_comma,
7690+
=> return .maybe,
7691+
7692+
.@"return",
7693+
.@"break",
7694+
.@"continue",
7695+
.bit_not,
7696+
.bool_not,
7697+
.global_var_decl,
7698+
.local_var_decl,
7699+
.simple_var_decl,
7700+
.aligned_var_decl,
7701+
.@"defer",
7702+
.@"errdefer",
7703+
.address_of,
7704+
.optional_type,
7705+
.negation,
7706+
.negation_wrap,
7707+
.@"resume",
7708+
.array_type,
7709+
.array_type_sentinel,
7710+
.ptr_type_aligned,
7711+
.ptr_type_sentinel,
7712+
.ptr_type,
7713+
.ptr_type_bit_range,
7714+
.@"suspend",
7715+
.@"anytype",
7716+
.fn_proto_simple,
7717+
.fn_proto_multi,
7718+
.fn_proto_one,
7719+
.fn_proto,
7720+
.fn_decl,
7721+
.anyframe_type,
7722+
.anyframe_literal,
7723+
.integer_literal,
7724+
.float_literal,
7725+
.enum_literal,
7726+
.string_literal,
7727+
.multiline_string_literal,
7728+
.char_literal,
7729+
.true_literal,
7730+
.false_literal,
7731+
.null_literal,
7732+
.undefined_literal,
7733+
.unreachable_literal,
7734+
.error_set_decl,
7735+
.container_decl,
7736+
.container_decl_trailing,
7737+
.container_decl_two,
7738+
.container_decl_two_trailing,
7739+
.container_decl_arg,
7740+
.container_decl_arg_trailing,
7741+
.tagged_union,
7742+
.tagged_union_trailing,
7743+
.tagged_union_two,
7744+
.tagged_union_two_trailing,
7745+
.tagged_union_enum_tag,
7746+
.tagged_union_enum_tag_trailing,
7747+
.add,
7748+
.add_wrap,
7749+
.array_cat,
7750+
.array_mult,
7751+
.assign,
7752+
.assign_bit_and,
7753+
.assign_bit_or,
7754+
.assign_bit_shift_left,
7755+
.assign_bit_shift_right,
7756+
.assign_bit_xor,
7757+
.assign_div,
7758+
.assign_sub,
7759+
.assign_sub_wrap,
7760+
.assign_mod,
7761+
.assign_add,
7762+
.assign_add_wrap,
7763+
.assign_mul,
7764+
.assign_mul_wrap,
7765+
.bang_equal,
7766+
.bit_and,
7767+
.bit_or,
7768+
.bit_shift_left,
7769+
.bit_shift_right,
7770+
.bit_xor,
7771+
.bool_and,
7772+
.bool_or,
7773+
.div,
7774+
.equal_equal,
7775+
.error_union,
7776+
.greater_or_equal,
7777+
.greater_than,
7778+
.less_or_equal,
7779+
.less_than,
7780+
.merge_error_sets,
7781+
.mod,
7782+
.mul,
7783+
.mul_wrap,
7784+
.switch_range,
7785+
.sub,
7786+
.sub_wrap,
7787+
.slice,
7788+
.slice_open,
7789+
.slice_sentinel,
7790+
.array_init_one,
7791+
.array_init_one_comma,
7792+
.array_init_dot_two,
7793+
.array_init_dot_two_comma,
7794+
.array_init_dot,
7795+
.array_init_dot_comma,
7796+
.array_init,
7797+
.array_init_comma,
7798+
.struct_init_one,
7799+
.struct_init_one_comma,
7800+
.struct_init_dot_two,
7801+
.struct_init_dot_two_comma,
7802+
.struct_init_dot,
7803+
.struct_init_dot_comma,
7804+
.struct_init,
7805+
.struct_init_comma,
7806+
=> return .never,
7807+
7808+
// Forward the question to the LHS sub-expression.
7809+
.grouped_expression,
7810+
.@"try",
7811+
.@"await",
7812+
.@"comptime",
7813+
.@"nosuspend",
7814+
.unwrap_optional,
7815+
=> node = node_datas[node].lhs,
7816+
7817+
// Forward the question to the RHS sub-expression.
7818+
.@"catch",
7819+
.@"orelse",
7820+
=> node = node_datas[node].rhs,
7821+
7822+
.block_two,
7823+
.block_two_semicolon,
7824+
.block,
7825+
.block_semicolon,
7826+
=> {
7827+
const lbrace = main_tokens[node];
7828+
if (token_tags[lbrace - 1] == .colon) {
7829+
// Labeled blocks may need a memory location to forward
7830+
// to their break statements.
7831+
return .maybe;
7832+
} else {
7833+
return .never;
7834+
}
7835+
},
7836+
7837+
.builtin_call,
7838+
.builtin_call_comma,
7839+
.builtin_call_two,
7840+
.builtin_call_two_comma,
7841+
=> {
7842+
const builtin_token = main_tokens[node];
7843+
const builtin_name = tree.tokenSlice(builtin_token);
7844+
// If the builtin is an invalid name, we don't cause an error here; instead
7845+
// let it pass, and the error will be "invalid builtin function" later.
7846+
const builtin_info = BuiltinFn.list.get(builtin_name) orelse return .maybe;
7847+
if (builtin_info.tag == .err_set_cast) {
7848+
return .always;
7849+
} else {
7850+
return .never;
7851+
}
7852+
},
7853+
}
7854+
}
7855+
}
7856+
76117857
/// Applies `rl` semantics to `inst`. Expressions which do not do their own handling of
76127858
/// result locations must call this function on their result.
76137859
/// As an example, if the `ResultLoc` is `ptr`, it will write the result to the pointer.

0 commit comments

Comments
 (0)