Handles destructure assignments where the LHS may contain const or var decls.
fn assignDestructureMaybeDecls(
gz: *GenZir,
scope: *Scope,
node: Ast.Node.Index,
block_arena: Allocator,
) InnerError!*Scope
fn assignDestructureMaybeDecls(
gz: *GenZir,
scope: *Scope,
node: Ast.Node.Index,
block_arena: Allocator,
) InnerError!*Scope {
try emitDbgNode(gz, node);
const astgen = gz.astgen;
const tree = astgen.tree;
const full = tree.assignDestructure(node);
if (full.comptime_token != null and gz.is_comptime) {
try astgen.appendErrorTok(full.comptime_token.?, "redundant comptime keyword in already comptime scope", .{});
}
const is_comptime = full.comptime_token != null or gz.is_comptime;
const value_is_comptime = tree.nodeTag(full.ast.value_expr) == .@"comptime";
// When declaring consts via a destructure, we always use a result pointer.
// This avoids the need to create tuple types, and is also likely easier to
// optimize, since it's a bit tricky for the optimizer to "split up" the
// value into individual pointer writes down the line.
// We know this rl information won't live past the evaluation of this
// expression, so it may as well go in the block arena.
const rl_components = try block_arena.alloc(ResultInfo.Loc.DestructureComponent, full.ast.variables.len);
var any_non_const_variables = false;
var any_lvalue_expr = false;
for (rl_components, full.ast.variables) |*variable_rl, variable_node| {
switch (tree.nodeTag(variable_node)) {
.identifier => {
// This intentionally does not support `@"_"` syntax.
const ident_name = tree.tokenSlice(tree.nodeMainToken(variable_node));
if (mem.eql(u8, ident_name, "_")) {
any_non_const_variables = true;
variable_rl.* = .discard;
continue;
}
},
.global_var_decl, .local_var_decl, .simple_var_decl, .aligned_var_decl => {
const full_var_decl = tree.fullVarDecl(variable_node).?;
const name_token = full_var_decl.ast.mut_token + 1;
const ident_name_raw = tree.tokenSlice(name_token);
if (mem.eql(u8, ident_name_raw, "_")) {
return astgen.failTok(name_token, "'_' used as an identifier without @\"_\" syntax", .{});
}
// We detect shadowing in the second pass over these, while we're creating scopes.
if (full_var_decl.ast.addrspace_node.unwrap()) |addrspace_node| {
return astgen.failTok(tree.nodeMainToken(addrspace_node), "cannot set address space of local variable '{s}'", .{ident_name_raw});
}
if (full_var_decl.ast.section_node.unwrap()) |section_node| {
return astgen.failTok(tree.nodeMainToken(section_node), "cannot set section of local variable '{s}'", .{ident_name_raw});
}
const is_const = switch (tree.tokenTag(full_var_decl.ast.mut_token)) {
.keyword_var => false,
.keyword_const => true,
else => unreachable,
};
if (!is_const) any_non_const_variables = true;
// We also mark `const`s as comptime if the RHS is definitely comptime-known.
const this_variable_comptime = is_comptime or (is_const and value_is_comptime);
const align_inst: Zir.Inst.Ref = if (full_var_decl.ast.align_node.unwrap()) |align_node|
try comptimeExpr(gz, scope, coerced_align_ri, align_node, .@"align")
else
.none;
if (full_var_decl.ast.type_node.unwrap()) |type_node| {
// Typed alloc
const type_inst = try typeExpr(gz, scope, type_node);
const ptr = if (align_inst == .none) ptr: {
const tag: Zir.Inst.Tag = if (is_const)
.alloc
else if (this_variable_comptime)
.alloc_comptime_mut
else
.alloc_mut;
break :ptr try gz.addUnNode(tag, type_inst, node);
} else try gz.addAllocExtended(.{
.node = node,
.type_inst = type_inst,
.align_inst = align_inst,
.is_const = is_const,
.is_comptime = this_variable_comptime,
});
variable_rl.* = .{ .typed_ptr = .{ .inst = ptr } };
} else {
// Inferred alloc
const ptr = if (align_inst == .none) ptr: {
const tag: Zir.Inst.Tag = if (is_const) tag: {
break :tag if (this_variable_comptime) .alloc_inferred_comptime else .alloc_inferred;
} else tag: {
break :tag if (this_variable_comptime) .alloc_inferred_comptime_mut else .alloc_inferred_mut;
};
break :ptr try gz.addNode(tag, node);
} else try gz.addAllocExtended(.{
.node = node,
.type_inst = .none,
.align_inst = align_inst,
.is_const = is_const,
.is_comptime = this_variable_comptime,
});
variable_rl.* = .{ .inferred_ptr = ptr };
}
continue;
},
else => {},
}
// This variable is just an lvalue expression.
// We will fill in its result pointer later, inside a comptime block.
any_non_const_variables = true;
any_lvalue_expr = true;
variable_rl.* = .{ .typed_ptr = .{
.inst = undefined,
.src_node = variable_node,
} };
}
if (full.comptime_token != null and !any_non_const_variables) {
try astgen.appendErrorTok(full.comptime_token.?, "'comptime const' is redundant; instead wrap the initialization expression with 'comptime'", .{});
// Note that this is non-fatal; we will still evaluate at comptime.
}
// If this expression is marked comptime, we must wrap it in a comptime block.
var gz_buf: GenZir = undefined;
const inner_gz = if (full.comptime_token) |_| bs: {
gz_buf = gz.makeSubBlock(scope);
gz_buf.is_comptime = true;
break :bs &gz_buf;
} else gz;
defer if (full.comptime_token) |_| inner_gz.unstack();
if (any_lvalue_expr) {
// At least one variable was an lvalue expr. Iterate again in order to
// evaluate the lvalues from within the possible block_comptime.
for (rl_components, full.ast.variables) |*variable_rl, variable_node| {
if (variable_rl.* != .typed_ptr) continue;
switch (tree.nodeTag(variable_node)) {
.global_var_decl, .local_var_decl, .simple_var_decl, .aligned_var_decl => continue,
else => {},
}
variable_rl.typed_ptr.inst = try lvalExpr(inner_gz, scope, variable_node);
}
}
// We can't give a reasonable anon name strategy for destructured inits, so
// leave it at its default of `.anon`.
_ = try reachableExpr(inner_gz, scope, .{ .rl = .{ .destructure = .{
.src_node = node,
.components = rl_components,
} } }, full.ast.value_expr, node);
if (full.comptime_token) |_| {
// Finish the block_comptime. Inferred alloc resolution etc will occur
// in the parent block.
const comptime_block_inst = try gz.makeBlockInst(.block_comptime, node);
_ = try inner_gz.addBreak(.break_inline, comptime_block_inst, .void_value);
try inner_gz.setBlockComptimeBody(comptime_block_inst, .comptime_keyword);
try gz.instructions.append(gz.astgen.gpa, comptime_block_inst);
}
// Now, iterate over the variable exprs to construct any new scopes.
// If there were any inferred allocations, resolve them.
// If there were any `const` decls, make the pointer constant.
var cur_scope = scope;
for (rl_components, full.ast.variables) |variable_rl, variable_node| {
switch (tree.nodeTag(variable_node)) {
.local_var_decl, .simple_var_decl, .aligned_var_decl => {},
else => continue, // We were mutating an existing lvalue - nothing to do
}
const full_var_decl = tree.fullVarDecl(variable_node).?;
const raw_ptr, const resolve_inferred = switch (variable_rl) {
.discard => unreachable,
.typed_ptr => |typed_ptr| .{ typed_ptr.inst, false },
.inferred_ptr => |ptr_inst| .{ ptr_inst, true },
};
const is_const = switch (tree.tokenTag(full_var_decl.ast.mut_token)) {
.keyword_var => false,
.keyword_const => true,
else => unreachable,
};
// If the alloc was inferred, resolve it. If the alloc was const, make it const.
const final_ptr = if (resolve_inferred)
try gz.addUnNode(.resolve_inferred_alloc, raw_ptr, variable_node)
else if (is_const)
try gz.addUnNode(.make_ptr_const, raw_ptr, node)
else
raw_ptr;
const name_token = full_var_decl.ast.mut_token + 1;
const ident_name_raw = tree.tokenSlice(name_token);
const ident_name = try astgen.identAsString(name_token);
try astgen.detectLocalShadowing(
cur_scope,
ident_name,
name_token,
ident_name_raw,
if (is_const) .@"local constant" else .@"local variable",
);
try gz.addDbgVar(.dbg_var_ptr, ident_name, final_ptr);
// Finally, create the scope.
const sub_scope = try block_arena.create(Scope.LocalPtr);
sub_scope.* = .{
.parent = cur_scope,
.gen_zir = gz,
.name = ident_name,
.ptr = final_ptr,
.token_src = name_token,
.maybe_comptime = is_const or is_comptime,
.id_cat = if (is_const) .@"local constant" else .@"local variable",
};
cur_scope = &sub_scope.base;
}
return cur_scope;
}