feature. See also
. The project being documented here (as the example) is the Zig library itself.
AstGen.fnDeclInner
fn fnDeclInner(
astgen: *AstGen,
decl_gz: *GenZir,
scope: *Scope,
saved_cursor: SourceCursor,
decl_inst: Zir.Inst.Index,
decl_node: Ast.Node.Index,
body_node: Ast.Node.Index,
fn_proto: Ast.full.FnProto,
) InnerError!void
File
Code
fn fnDeclInner(
astgen: *AstGen,
decl_gz: *GenZir,
scope: *Scope,
saved_cursor: SourceCursor,
decl_inst: Zir.Inst.Index,
decl_node: Ast.Node.Index,
body_node: Ast.Node.Index,
fn_proto: Ast.full.FnProto,
) InnerError!void {
const tree = astgen.tree;
const is_noinline = blk: {
const maybe_noinline_token = fn_proto.extern_export_inline_token orelse break :blk false;
break :blk tree.tokenTag(maybe_noinline_token) == .keyword_noinline;
};
const has_inline_keyword = blk: {
const maybe_inline_token = fn_proto.extern_export_inline_token orelse break :blk false;
break :blk tree.tokenTag(maybe_inline_token) == .keyword_inline;
};
const return_type = fn_proto.ast.return_type.unwrap().?;
const maybe_bang = tree.firstToken(return_type) - 1;
const is_inferred_error = tree.tokenTag(maybe_bang) == .bang;
var param_insts: std.ArrayList(Zir.Inst.Index) = try .initCapacity(astgen.arena, fn_proto.ast.params.len);
var any_param_used = false;
var noalias_bits: u32 = 0;
var params_scope = scope;
const is_var_args = is_var_args: {
var param_type_i: usize = 0;
var it = fn_proto.iterate(tree);
while (it.next()) |param| : (param_type_i += 1) {
const is_comptime = if (param.comptime_noalias) |token| switch (tree.tokenTag(token)) {
.keyword_noalias => is_comptime: {
noalias_bits |= @as(u32, 1) << (std.math.cast(u5, param_type_i) orelse
return astgen.failTok(token, "this compiler implementation only supports 'noalias' on the first 32 parameters", .{}));
break :is_comptime false;
},
.keyword_comptime => true,
else => false,
} else false;
const is_anytype = if (param.anytype_ellipsis3) |token| blk: {
switch (tree.tokenTag(token)) {
.keyword_anytype => break :blk true,
.ellipsis3 => break :is_var_args true,
else => unreachable,
}
} else false;
const param_name: Zir.NullTerminatedString = if (param.name_token) |name_token| blk: {
const name_bytes = tree.tokenSlice(name_token);
if (mem.eql(u8, "_", name_bytes))
break :blk .empty;
const param_name = try astgen.identAsString(name_token);
try astgen.detectLocalShadowing(params_scope, param_name, name_token, name_bytes, .@"function parameter");
break :blk param_name;
} else {
if (param.anytype_ellipsis3) |tok| {
return astgen.failTok(tok, "missing parameter name", .{});
} else {
const type_expr = param.type_expr.?;
ambiguous: {
if (tree.nodeTag(type_expr) != .identifier) break :ambiguous;
const main_token = tree.nodeMainToken(type_expr);
const identifier_str = tree.tokenSlice(main_token);
if (isPrimitive(identifier_str)) break :ambiguous;
return astgen.failNodeNotes(
type_expr,
"missing parameter name or type",
.{},
&[_]u32{
try astgen.errNoteNode(
type_expr,
"if this is a name, annotate its type: '{s}: T'",
.{identifier_str},
),
try astgen.errNoteNode(
type_expr,
"if this is a type, give it a name: 'name: {s}'",
.{identifier_str},
),
},
);
}
return astgen.failNode(type_expr, "missing parameter name", .{});
}
};
const param_inst = if (is_anytype) param: {
const name_token = param.name_token orelse param.anytype_ellipsis3.?;
const tag: Zir.Inst.Tag = if (is_comptime)
.param_anytype_comptime
else
.param_anytype;
break :param try decl_gz.addStrTok(tag, param_name, name_token);
} else param: {
const param_type_node = param.type_expr.?;
any_param_used = false;
var param_gz = decl_gz.makeSubBlock(scope);
defer param_gz.unstack();
const param_type = try fullBodyExpr(¶m_gz, params_scope, coerced_type_ri, param_type_node, .normal);
const param_inst_expected: Zir.Inst.Index = @fromBackingInt(@intCast(astgen.instructions.len + 1));
_ = try param_gz.addBreakWithSrcNode(.break_inline, param_inst_expected, param_type, param_type_node);
const param_type_is_generic = any_param_used;
const name_token = param.name_token orelse tree.nodeMainToken(param_type_node);
const tag: Zir.Inst.Tag = if (is_comptime) .param_comptime else .param;
const param_inst = try decl_gz.addParam(¶m_gz, param_insts.items, param_type_is_generic, tag, name_token, param_name);
assert(param_inst_expected == param_inst);
break :param param_inst.toRef();
};
if (param_name == .empty) continue;
const sub_scope = try astgen.arena.create(Scope.LocalVal);
sub_scope.* = .{
.parent = params_scope,
.gen_zir = decl_gz,
.name = param_name,
.inst = param_inst,
.token_src = param.name_token.?,
.id_cat = .@"function parameter",
.is_used_or_discarded = &any_param_used,
};
params_scope = &sub_scope.base;
try param_insts.append(astgen.arena, param_inst.toIndex().?);
}
break :is_var_args false;
};
// instructions inside the expression blocks for cc and ret_ty to use the function
// instruction as the body to break from.
var ret_gz = decl_gz.makeSubBlock(params_scope);
defer ret_gz.unstack();
any_param_used = false;
const ret_ref: Zir.Inst.Ref = inst: {
// The calling convention will not have parameters in scope, so we'll just use `scope`.
// See #22263 for a proposal to solve the inconsistency here.
const inst = try fullBodyExpr(&ret_gz, params_scope, coerced_type_ri, fn_proto.ast.return_type.unwrap().?, .normal);
if (ret_gz.instructionsSlice().len == 0) {
break :inst inst;
}
_ = try ret_gz.addBreak(.break_inline, @fromBackingInt(@intCast(0)), inst);
break :inst inst;
};
const ret_body_param_refs = try astgen.fetchRemoveRefEntries(param_insts.items);
const ret_ty_is_generic = any_param_used;
astgen.restoreSourceCursor(saved_cursor);
var cc_gz = decl_gz.makeSubBlock(scope);
defer cc_gz.unstack();
const cc_ref: Zir.Inst.Ref = blk: {
if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
const inst = try expr(
&cc_gz,
scope,
.{ .rl = .{ .coerced_ty = try cc_gz.addStdLangValue(callconv_expr, .calling_convention) } },
callconv_expr,
);
if (cc_gz.instructionsSlice().len == 0) {
break :blk inst;
}
_ = try cc_gz.addBreak(.break_inline, @fromBackingInt(@intCast(0)), inst);
break :blk inst;
} else if (has_inline_keyword) {
const inst = try cc_gz.addStdLangValue(decl_node, .calling_convention_inline);
_ = try cc_gz.addBreak(.break_inline, @fromBackingInt(@intCast(0)), inst);
break :blk inst;
} else {
break :blk .none;
}
};
var body_gz: GenZir = .{
.is_comptime = false,
.decl_node_index = fn_proto.ast.proto_node,
.decl_line = decl_gz.decl_line,
.parent = params_scope,
.astgen = astgen,
.instructions = decl_gz.instructions,
.instructions_top = decl_gz.instructions.items.len,
};
defer body_gz.unstack();
// body_gz (top)
// param2
// param1
// param0
// decl_gz (bottom)
// Construct the prototype hash.
// Leave `astgen.src_hasher` unmodified; this will be used for hashing
// the *whole* function declaration, including its body.
var proto_hasher = astgen.src_hasher;
const proto_node = tree.nodeData(decl_node).node_and_node[0];
proto_hasher.update(tree.getNodeSource(proto_node));
var proto_hash: std.zig.SrcHash = undefined;
proto_hasher.final(&proto_hash);
const prev_fn_block = astgen.fn_block;
const prev_fn_ret_ty = astgen.fn_ret_ty;
defer {
astgen.fn_block = prev_fn_block;
astgen.fn_ret_ty = prev_fn_ret_ty;
}
astgen.fn_block = &body_gz;
astgen.fn_ret_ty = if (is_inferred_error or ret_ref.toIndex() != null) r: {
// since the return type is likely not `void` or `noreturn` so there
// will probably be an explicit return requiring RLS. Fetch this
// return type now so the rest of the function can use it.
break :r try body_gz.addNode(.ret_type, decl_node);
} else ret_ref;
const prev_var_args = astgen.fn_var_args;
astgen.fn_var_args = is_var_args;
defer astgen.fn_var_args = prev_var_args;
astgen.advanceSourceCursorToNode(body_node);
const lbrace_line = astgen.source_line - decl_gz.decl_line;
const lbrace_column = astgen.source_column;
_ = try fullBodyExpr(&body_gz, &body_gz.base, .{ .rl = .none }, body_node, .allow_branch_hint);
try checkUsed(decl_gz, scope, params_scope);
if (!body_gz.endsWithNoReturn()) {
_ = try body_gz.addRestoreErrRetIndex(.ret, .always, decl_node);
_ = try body_gz.addUnTok(.ret_implicit, .void_value, tree.lastToken(body_node));
}
const func_inst = try decl_gz.addFunc(.{
.src_node = decl_node,
.cc_ref = cc_ref,
.cc_gz = &cc_gz,
.ret_ref = ret_ref,
.ret_gz = &ret_gz,
.ret_param_refs = ret_body_param_refs,
.ret_ty_is_generic = ret_ty_is_generic,
.lbrace_line = lbrace_line,
.lbrace_column = lbrace_column,
.param_block = decl_inst,
.param_insts = param_insts.items,
.body_gz = &body_gz,
.is_var_args = is_var_args,
.is_inferred_error = is_inferred_error,
.is_noinline = is_noinline,
.noalias_bits = noalias_bits,
.proto_hash = proto_hash,
});
_ = try decl_gz.addBreakWithSrcNode(.break_inline, decl_inst, func_inst, decl_node);
}