Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

fnDeclInner

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

lib/std/zig/AstGen.zig:4126

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;

    // Note that the capacity here may not be sufficient, as this does not include `anytype` parameters.
    var param_insts: std.ArrayList(Zir.Inst.Index) = try .initCapacity(astgen.arena, fn_proto.ast.params.len);

    // We use this as `is_used_or_discarded` to figure out if parameters / return types are generic.
    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; // we will check this later
                var param_gz = decl_gz.makeSubBlock(scope);
                defer param_gz.unstack();
                const param_type = try fullBodyExpr(&param_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(&param_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;
    };

    // After creating the function ZIR instruction, it will need to update the break
    // 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; // we will check this later
    const ret_ref: Zir.Inst.Ref = inst: {
        // Parameters are in scope for the return type, so we use `params_scope` here.
        // 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) {
            // In this case we will send a len=0 body which can be encoded more efficiently.
            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;

    // We're jumping back in source, so restore the cursor.
    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) {
                // In this case we will send a len=0 body which can be encoded more efficiently.
                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();

    // The scope stack looks like this:
    //  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: {
        // We're essentially guaranteed to need the return type at some point,
        // 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()) {
        // As our last action before the return, "pop" the error trace if needed
        _ = try body_gz.addRestoreErrRetIndex(.ret, .always, decl_node);

        // Add implicit return at end of function.
        _ = 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);
}