Returns true if rl provides a result pointer and the expression consumes it.
fn expr(astrl: *AstRlAnnotate, node: Ast.Node.Index, block: ?*Block, ri: ResultInfo) Allocator.Error!bool
fn expr(astrl: *AstRlAnnotate, node: Ast.Node.Index, block: ?*Block, ri: ResultInfo) Allocator.Error!bool {
const tree = astrl.tree;
switch (tree.nodeTag(node)) {
.root,
.switch_case_one,
.switch_case_inline_one,
.switch_case,
.switch_case_inline,
.switch_range,
.for_range,
.asm_output,
.asm_input,
=> unreachable,
.@"defer", .@"errdefer" => {
_ = try astrl.expr(tree.nodeData(node).node, block, ResultInfo.none);
return false;
},
.container_field_init,
.container_field_align,
.container_field,
=> {
const full = tree.fullContainerField(node).?;
const type_expr = full.ast.type_expr.unwrap().?;
_ = try astrl.expr(type_expr, block, ResultInfo.type_only);
if (full.ast.align_expr.unwrap()) |align_expr| {
_ = try astrl.expr(align_expr, block, ResultInfo.type_only);
}
if (full.ast.value_expr.unwrap()) |value_expr| {
_ = try astrl.expr(value_expr, block, ResultInfo.type_only);
}
return false;
},
.test_decl => {
_ = try astrl.expr(tree.nodeData(node).opt_token_and_node[1], block, ResultInfo.none);
return false;
},
.global_var_decl,
.local_var_decl,
.simple_var_decl,
.aligned_var_decl,
=> {
const full = tree.fullVarDecl(node).?;
const init_ri = if (full.ast.type_node.unwrap()) |type_node| init_ri: {
_ = try astrl.expr(type_node, block, ResultInfo.type_only);
break :init_ri ResultInfo.typed_ptr;
} else ResultInfo.inferred_ptr;
const init_node = full.ast.init_node.unwrap() orelse {
// No init node, so we're done.
return false;
};
switch (tree.tokenTag(full.ast.mut_token)) {
.keyword_const => {
const init_consumes_rl = try astrl.expr(init_node, block, init_ri);
if (init_consumes_rl) {
try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
}
return false;
},
.keyword_var => {
// We'll create an alloc either way, so don't care if the
// result pointer is consumed.
_ = try astrl.expr(init_node, block, init_ri);
return false;
},
else => unreachable,
}
},
.assign_destructure => {
const full = tree.assignDestructure(node);
for (full.ast.variables) |variable_node| {
_ = try astrl.expr(variable_node, block, ResultInfo.none);
}
// We don't need to gather any meaningful data here, because destructures always use RLS
_ = try astrl.expr(full.ast.value_expr, block, ResultInfo.none);
return false;
},
.assign => {
const lhs, const rhs = tree.nodeData(node).node_and_node;
_ = try astrl.expr(lhs, block, ResultInfo.none);
_ = try astrl.expr(rhs, block, ResultInfo.typed_ptr);
return false;
},
.assign_shl,
.assign_shl_sat,
.assign_shr,
.assign_bit_and,
.assign_bit_or,
.assign_bit_xor,
.assign_div,
.assign_sub,
.assign_sub_wrap,
.assign_sub_sat,
.assign_mod,
.assign_add,
.assign_add_wrap,
.assign_add_sat,
.assign_mul,
.assign_mul_wrap,
.assign_mul_sat,
=> {
const lhs, const rhs = tree.nodeData(node).node_and_node;
_ = try astrl.expr(lhs, block, ResultInfo.none);
_ = try astrl.expr(rhs, block, ResultInfo.none);
return false;
},
.shl, .shr => {
const lhs, const rhs = tree.nodeData(node).node_and_node;
_ = try astrl.expr(lhs, block, ResultInfo.none);
_ = try astrl.expr(rhs, block, ResultInfo.type_only);
return false;
},
.add,
.add_wrap,
.add_sat,
.sub,
.sub_wrap,
.sub_sat,
.mul,
.mul_wrap,
.mul_sat,
.div,
.mod,
.shl_sat,
.bit_and,
.bit_or,
.bit_xor,
.bang_equal,
.equal_equal,
.greater_than,
.greater_or_equal,
.less_than,
.less_or_equal,
.array_cat,
=> {
const lhs, const rhs = tree.nodeData(node).node_and_node;
_ = try astrl.expr(lhs, block, ResultInfo.none);
_ = try astrl.expr(rhs, block, ResultInfo.none);
return false;
},
.error_union, .merge_error_sets => {
const lhs, const rhs = tree.nodeData(node).node_and_node;
_ = try astrl.expr(lhs, block, ResultInfo.none);
_ = try astrl.expr(rhs, block, ResultInfo.none);
return false;
},
.bool_and,
.bool_or,
=> {
const lhs, const rhs = tree.nodeData(node).node_and_node;
_ = try astrl.expr(lhs, block, ResultInfo.type_only);
_ = try astrl.expr(rhs, block, ResultInfo.type_only);
return false;
},
.bool_not => {
_ = try astrl.expr(tree.nodeData(node).node, block, ResultInfo.type_only);
return false;
},
.bit_not, .negation, .negation_wrap => {
_ = try astrl.expr(tree.nodeData(node).node, block, ResultInfo.none);
return false;
},
// These nodes are leaves and never consume a result location.
.identifier,
.string_literal,
.multiline_string_literal,
.number_literal,
.unreachable_literal,
.asm_simple,
.@"asm",
.enum_literal,
.error_value,
.anyframe_literal,
.@"continue",
.char_literal,
.error_set_decl,
=> return false,
.builtin_call_two,
.builtin_call_two_comma,
.builtin_call,
.builtin_call_comma,
=> {
var buf: [2]Ast.Node.Index = undefined;
const params = tree.builtinCallParams(&buf, node).?;
return astrl.builtinCall(block, ri, node, params);
},
.call_one,
.call_one_comma,
.call,
.call_comma,
=> {
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullCall(&buf, node).?;
_ = try astrl.expr(full.ast.fn_expr, block, ResultInfo.none);
for (full.ast.params) |param_node| {
_ = try astrl.expr(param_node, block, ResultInfo.type_only);
}
return false; // TODO: once function calls are passed result locations this will change
},
.@"return" => {
if (tree.nodeData(node).opt_node.unwrap()) |lhs| {
const ret_val_consumes_rl = try astrl.expr(lhs, block, ResultInfo.typed_ptr);
if (ret_val_consumes_rl) {
try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
}
}
return false;
},
.field_access => {
const lhs, _ = tree.nodeData(node).node_and_token;
_ = try astrl.expr(lhs, block, ResultInfo.none);
return false;
},
.if_simple, .@"if" => {
const full = tree.fullIf(node).?;
if (full.error_token != null or full.payload_token != null) {
_ = try astrl.expr(full.ast.cond_expr, block, ResultInfo.none);
} else {
_ = try astrl.expr(full.ast.cond_expr, block, ResultInfo.type_only); // bool
}
if (full.ast.else_expr.unwrap()) |else_expr| {
const then_uses_rl = try astrl.expr(full.ast.then_expr, block, ri);
const else_uses_rl = try astrl.expr(else_expr, block, ri);
const uses_rl = then_uses_rl or else_uses_rl;
if (uses_rl) try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
return uses_rl;
} else {
_ = try astrl.expr(full.ast.then_expr, block, ResultInfo.none);
return false;
}
},
.while_simple, .while_cont, .@"while" => {
const full = tree.fullWhile(node).?;
const label: ?[]const u8 = if (full.label_token) |label_token| label: {
break :label try astrl.identString(label_token);
} else null;
if (full.error_token != null or full.payload_token != null) {
_ = try astrl.expr(full.ast.cond_expr, block, ResultInfo.none);
} else {
_ = try astrl.expr(full.ast.cond_expr, block, ResultInfo.type_only); // bool
}
var new_block: Block = .{
.parent = block,
.label = label,
.is_loop = true,
.ri = ri,
.consumes_res_ptr = false,
};
if (full.ast.cont_expr.unwrap()) |cont_expr| {
_ = try astrl.expr(cont_expr, &new_block, ResultInfo.none);
}
_ = try astrl.expr(full.ast.then_expr, &new_block, ResultInfo.none);
const else_consumes_rl = if (full.ast.else_expr.unwrap()) |else_expr| else_rl: {
break :else_rl try astrl.expr(else_expr, block, ri);
} else false;
if (new_block.consumes_res_ptr or else_consumes_rl) {
try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
return true;
} else {
return false;
}
},
.for_simple, .@"for" => {
const full = tree.fullFor(node).?;
const label: ?[]const u8 = if (full.label_token) |label_token| label: {
break :label try astrl.identString(label_token);
} else null;
for (full.ast.inputs) |input| {
if (tree.nodeTag(input) == .for_range) {
const lhs, const opt_rhs = tree.nodeData(input).node_and_opt_node;
_ = try astrl.expr(lhs, block, ResultInfo.type_only);
if (opt_rhs.unwrap()) |rhs| {
_ = try astrl.expr(rhs, block, ResultInfo.type_only);
}
} else {
_ = try astrl.expr(input, block, ResultInfo.none);
}
}
var new_block: Block = .{
.parent = block,
.label = label,
.is_loop = true,
.ri = ri,
.consumes_res_ptr = false,
};
_ = try astrl.expr(full.ast.then_expr, &new_block, ResultInfo.none);
const else_consumes_rl = if (full.ast.else_expr.unwrap()) |else_expr| else_rl: {
break :else_rl try astrl.expr(else_expr, block, ri);
} else false;
if (new_block.consumes_res_ptr or else_consumes_rl) {
try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
return true;
} else {
return false;
}
},
.slice_open => {
const sliced, const start = tree.nodeData(node).node_and_node;
_ = try astrl.expr(sliced, block, ResultInfo.none);
_ = try astrl.expr(start, block, ResultInfo.type_only);
return false;
},
.slice => {
const sliced, const extra_index = tree.nodeData(node).node_and_extra;
const extra = tree.extraData(extra_index, Ast.Node.Slice);
_ = try astrl.expr(sliced, block, ResultInfo.none);
_ = try astrl.expr(extra.start, block, ResultInfo.type_only);
_ = try astrl.expr(extra.end, block, ResultInfo.type_only);
return false;
},
.slice_sentinel => {
const sliced, const extra_index = tree.nodeData(node).node_and_extra;
const extra = tree.extraData(extra_index, Ast.Node.SliceSentinel);
_ = try astrl.expr(sliced, block, ResultInfo.none);
_ = try astrl.expr(extra.start, block, ResultInfo.type_only);
if (extra.end.unwrap()) |end| {
_ = try astrl.expr(end, block, ResultInfo.type_only);
}
_ = try astrl.expr(extra.sentinel, block, ResultInfo.none);
return false;
},
.deref => {
_ = try astrl.expr(tree.nodeData(node).node, block, ResultInfo.none);
return false;
},
.address_of => {
_ = try astrl.expr(tree.nodeData(node).node, block, ResultInfo.none);
return false;
},
.optional_type => {
_ = try astrl.expr(tree.nodeData(node).node, block, ResultInfo.type_only);
return false;
},
.@"try",
.@"nosuspend",
=> return astrl.expr(tree.nodeData(node).node, block, ri),
.grouped_expression,
.unwrap_optional,
=> return astrl.expr(tree.nodeData(node).node_and_token[0], block, ri),
.block_two,
.block_two_semicolon,
.block,
.block_semicolon,
=> {
var buf: [2]Ast.Node.Index = undefined;
const statements = tree.blockStatements(&buf, node).?;
return astrl.blockExpr(block, ri, node, statements);
},
.anyframe_type => {
_, const child_type = tree.nodeData(node).token_and_node;
_ = try astrl.expr(child_type, block, ResultInfo.type_only);
return false;
},
.@"catch", .@"orelse" => {
const lhs, const rhs = tree.nodeData(node).node_and_node;
_ = try astrl.expr(lhs, block, ResultInfo.none);
const rhs_consumes_rl = try astrl.expr(rhs, block, ri);
if (rhs_consumes_rl) {
try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
}
return rhs_consumes_rl;
},
.ptr_type_aligned,
.ptr_type_sentinel,
.ptr_type,
.ptr_type_bit_range,
=> {
const full = tree.fullPtrType(node).?;
_ = try astrl.expr(full.ast.child_type, block, ResultInfo.type_only);
if (full.ast.sentinel.unwrap()) |sentinel| {
_ = try astrl.expr(sentinel, block, ResultInfo.type_only);
}
if (full.ast.addrspace_node.unwrap()) |addrspace_node| {
_ = try astrl.expr(addrspace_node, block, ResultInfo.type_only);
}
if (full.ast.align_node.unwrap()) |align_node| {
_ = try astrl.expr(align_node, block, ResultInfo.type_only);
}
if (full.ast.bit_range_start.unwrap()) |bit_range_start| {
const bit_range_end = full.ast.bit_range_end.unwrap().?;
_ = try astrl.expr(bit_range_start, block, ResultInfo.type_only);
_ = try astrl.expr(bit_range_end, block, ResultInfo.type_only);
}
return false;
},
.container_decl,
.container_decl_trailing,
.container_decl_arg,
.container_decl_arg_trailing,
.container_decl_two,
.container_decl_two_trailing,
.tagged_union,
.tagged_union_trailing,
.tagged_union_enum_tag,
.tagged_union_enum_tag_trailing,
.tagged_union_two,
.tagged_union_two_trailing,
=> {
var buf: [2]Ast.Node.Index = undefined;
try astrl.containerDecl(block, tree.fullContainerDecl(&buf, node).?);
return false;
},
.@"break" => {
const opt_label, const opt_rhs = tree.nodeData(node).opt_token_and_opt_node;
const rhs = opt_rhs.unwrap() orelse {
// Breaks with void are not interesting
return false;
};
var opt_cur_block = block;
if (opt_label.unwrap()) |label_token| {
const break_label = try astrl.identString(label_token);
while (opt_cur_block) |cur_block| : (opt_cur_block = cur_block.parent) {
const block_label = cur_block.label orelse continue;
if (std.mem.eql(u8, block_label, break_label)) break;
}
} else {
// No label - we're breaking from a loop.
while (opt_cur_block) |cur_block| : (opt_cur_block = cur_block.parent) {
if (cur_block.is_loop) break;
}
}
if (opt_cur_block) |target_block| {
const consumes_break_rl = try astrl.expr(rhs, block, target_block.ri);
if (consumes_break_rl) target_block.consumes_res_ptr = true;
} else {
// No corresponding scope to break from - AstGen will emit an error.
_ = try astrl.expr(rhs, block, ResultInfo.none);
}
return false;
},
.array_type => {
const lhs, const rhs = tree.nodeData(node).node_and_node;
_ = try astrl.expr(lhs, block, ResultInfo.type_only);
_ = try astrl.expr(rhs, block, ResultInfo.type_only);
return false;
},
.array_type_sentinel => {
const len_expr, const extra_index = tree.nodeData(node).node_and_extra;
const extra = tree.extraData(extra_index, Ast.Node.ArrayTypeSentinel);
_ = try astrl.expr(len_expr, block, ResultInfo.type_only);
_ = try astrl.expr(extra.elem_type, block, ResultInfo.type_only);
_ = try astrl.expr(extra.sentinel, block, ResultInfo.type_only);
return false;
},
.array_access => {
const lhs, const rhs = tree.nodeData(node).node_and_node;
_ = try astrl.expr(lhs, block, ResultInfo.none);
_ = try astrl.expr(rhs, block, ResultInfo.type_only);
return false;
},
.@"comptime" => {
// AstGen will emit an error if the scope is already comptime, so we can assume it is
// not. This means the result location is not forwarded.
_ = try astrl.expr(tree.nodeData(node).node, block, ResultInfo.none);
return false;
},
.@"switch", .switch_comma => {
const operand_node, const extra_index = tree.nodeData(node).node_and_extra;
const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index);
_ = try astrl.expr(operand_node, block, ResultInfo.none);
var any_prong_consumed_rl = false;
for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?;
for (case.ast.values) |item_node| {
if (tree.nodeTag(item_node) == .switch_range) {
const lhs, const rhs = tree.nodeData(item_node).node_and_node;
_ = try astrl.expr(lhs, block, ResultInfo.none);
_ = try astrl.expr(rhs, block, ResultInfo.none);
} else {
_ = try astrl.expr(item_node, block, ResultInfo.none);
}
}
if (try astrl.expr(case.ast.target_expr, block, ri)) {
any_prong_consumed_rl = true;
}
}
if (any_prong_consumed_rl) {
try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
}
return any_prong_consumed_rl;
},
.@"suspend" => {
_ = try astrl.expr(tree.nodeData(node).node, block, ResultInfo.none);
return false;
},
.@"resume" => {
_ = try astrl.expr(tree.nodeData(node).node, block, ResultInfo.none);
return false;
},
.array_init_one,
.array_init_one_comma,
.array_init_dot_two,
.array_init_dot_two_comma,
.array_init_dot,
.array_init_dot_comma,
.array_init,
.array_init_comma,
=> {
var buf: [2]Ast.Node.Index = undefined;
const full = tree.fullArrayInit(&buf, node).?;
if (full.ast.type_expr.unwrap()) |type_expr| {
// Explicitly typed init does not participate in RLS
_ = try astrl.expr(type_expr, block, ResultInfo.none);
for (full.ast.elements) |elem_init| {
_ = try astrl.expr(elem_init, block, ResultInfo.type_only);
}
return false;
}
if (ri.have_type) {
// Always forward type information
// If we have a result pointer, we use and forward it
for (full.ast.elements) |elem_init| {
_ = try astrl.expr(elem_init, block, ri);
}
return ri.have_ptr;
} else {
// Untyped init does not consume result location
for (full.ast.elements) |elem_init| {
_ = try astrl.expr(elem_init, block, ResultInfo.none);
}
return false;
}
},
.struct_init_one,
.struct_init_one_comma,
.struct_init_dot_two,
.struct_init_dot_two_comma,
.struct_init_dot,
.struct_init_dot_comma,
.struct_init,
.struct_init_comma,
=> {
var buf: [2]Ast.Node.Index = undefined;
const full = tree.fullStructInit(&buf, node).?;
if (full.ast.type_expr.unwrap()) |type_expr| {
// Explicitly typed init does not participate in RLS
_ = try astrl.expr(type_expr, block, ResultInfo.none);
for (full.ast.fields) |field_init| {
_ = try astrl.expr(field_init, block, ResultInfo.type_only);
}
return false;
}
if (ri.have_type) {
// Always forward type information
// If we have a result pointer, we use and forward it
for (full.ast.fields) |field_init| {
_ = try astrl.expr(field_init, block, ri);
}
return ri.have_ptr;
} else {
// Untyped init does not consume result location
for (full.ast.fields) |field_init| {
_ = try astrl.expr(field_init, block, ResultInfo.none);
}
return false;
}
},
.fn_proto_simple,
.fn_proto_multi,
.fn_proto_one,
.fn_proto,
.fn_decl,
=> |tag| {
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, node).?;
const body_node = if (tag == .fn_decl) tree.nodeData(node).node_and_node[1].toOptional() else .none;
{
var it = full.iterate(tree);
while (it.next()) |param| {
if (param.anytype_ellipsis3 == null) {
const type_expr = param.type_expr.?;
_ = try astrl.expr(type_expr, block, ResultInfo.type_only);
}
}
}
if (full.ast.align_expr.unwrap()) |align_expr| {
_ = try astrl.expr(align_expr, block, ResultInfo.type_only);
}
if (full.ast.addrspace_expr.unwrap()) |addrspace_expr| {
_ = try astrl.expr(addrspace_expr, block, ResultInfo.type_only);
}
if (full.ast.section_expr.unwrap()) |section_expr| {
_ = try astrl.expr(section_expr, block, ResultInfo.type_only);
}
if (full.ast.callconv_expr.unwrap()) |callconv_expr| {
_ = try astrl.expr(callconv_expr, block, ResultInfo.type_only);
}
const return_type = full.ast.return_type.unwrap().?;
_ = try astrl.expr(return_type, block, ResultInfo.type_only);
if (body_node.unwrap()) |body| {
_ = try astrl.expr(body, block, ResultInfo.none);
}
return false;
},
}
}