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.

File

Walk.File
pub const File = struct

File

lib/docs/wasm/Walk.zig:42

Code

pub const File = struct {
    ast: Ast,
    /// Maps identifiers to the declarations they point to.
    ident_decls: std.array_hash_map.Auto(Ast.TokenIndex, Ast.Node.Index) = .empty,
    /// Maps field access identifiers to the containing field access node.
    token_parents: std.array_hash_map.Auto(Ast.TokenIndex, Ast.Node.Index) = .empty,
    /// Maps declarations to their global index.
    node_decls: std.array_hash_map.Auto(Ast.Node.Index, Decl.Index) = .empty,
    /// Maps function declarations to doctests.
    doctests: std.array_hash_map.Auto(Ast.Node.Index, Ast.Node.Index) = .empty,
    /// root node => its namespace scope
    /// struct/union/enum/opaque decl node => its namespace scope
    /// local var decl node => its local variable scope
    scopes: std.array_hash_map.Auto(Ast.Node.Index, *Scope) = .empty,

    pub fn lookup_token(file: *File, token: Ast.TokenIndex) Decl.Index {
        const decl_node = file.ident_decls.get(token) orelse return .none;
        return file.node_decls.get(decl_node) orelse return .none;
    }

    pub const Index = enum(u32) {
        _,

        fn add_decl(i: Index, node: Ast.Node.Index, parent_decl: Decl.Index) Oom!Decl.Index {
            try decls.append(gpa, .{
                .ast_node = node,
                .file = i,
                .parent = parent_decl,
            });
            const decl_index: Decl.Index = @fromBackingInt(@intCast(decls.items.len - 1));
            try i.get().node_decls.put(gpa, node, decl_index);
            return decl_index;
        }

        pub fn get(i: File.Index) *File {
            return &files.values()[@backingInt(i)];
        }

        pub fn get_ast(i: File.Index) *Ast {
            return &i.get().ast;
        }

        pub fn path(i: File.Index) []const u8 {
            return files.keys()[@backingInt(i)];
        }

        pub fn findRootDecl(file_index: File.Index) Decl.Index {
            return file_index.get().node_decls.values()[0];
        }

        pub fn categorize_decl(file_index: File.Index, node: Ast.Node.Index) Category {
            const ast = file_index.get_ast();
            switch (ast.nodeTag(node)) {
                .root => {
                    for (ast.rootDecls()) |member| {
                        switch (ast.nodeTag(member)) {
                            .container_field_init,
                            .container_field_align,
                            .container_field,
                            => return .{ .container = node },
                            else => {},
                        }
                    }
                    return .{ .namespace = node };
                },

                .global_var_decl,
                .local_var_decl,
                .simple_var_decl,
                .aligned_var_decl,
                => {
                    const var_decl = ast.fullVarDecl(node).?;
                    if (ast.tokenTag(var_decl.ast.mut_token) == .keyword_var)
                        return .{ .global_variable = node };
                    const init_node = var_decl.ast.init_node.unwrap() orelse
                        return .{ .global_const = node };

                    return categorize_expr(file_index, init_node);
                },

                .fn_proto,
                .fn_proto_multi,
                .fn_proto_one,
                .fn_proto_simple,
                .fn_decl,
                => {
                    var buf: [1]Ast.Node.Index = undefined;
                    const full = ast.fullFnProto(&buf, node).?;
                    return categorize_func(file_index, node, full);
                },

                else => unreachable,
            }
        }

        pub fn categorize_func(
            file_index: File.Index,
            node: Ast.Node.Index,
            full: Ast.full.FnProto,
        ) Category {
            return switch (categorize_expr(file_index, full.ast.return_type.unwrap().?)) {
                .namespace, .container, .error_set, .type_type => .{ .type_function = node },
                else => .{ .function = node },
            };
        }

        pub fn categorize_expr_deep(file_index: File.Index, node: Ast.Node.Index) Category {
            return switch (categorize_expr(file_index, node)) {
                .alias => |aliasee| aliasee.get().categorize(),
                else => |result| result,
            };
        }

        pub fn categorize_expr(file_index: File.Index, node: Ast.Node.Index) Category {
            const file = file_index.get();
            const ast = file_index.get_ast();
            //log.debug("categorize_expr tag {s}", .{@tagName(ast.nodeTag(node))});
            return switch (ast.nodeTag(node)) {
                .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;
                    const container_decl = ast.fullContainerDecl(&buf, node).?;
                    if (ast.tokenTag(container_decl.ast.main_token) != .keyword_struct) {
                        return .{ .container = node };
                    }
                    for (container_decl.ast.members) |member| {
                        switch (ast.nodeTag(member)) {
                            .container_field_init,
                            .container_field_align,
                            .container_field,
                            => return .{ .container = node },
                            else => {},
                        }
                    }
                    return .{ .namespace = node };
                },

                .error_set_decl,
                .merge_error_sets,
                => .{ .error_set = node },

                .identifier => {
                    const name_token = ast.nodeMainToken(node);
                    const ident_name = ast.tokenSlice(name_token);
                    if (std.mem.eql(u8, ident_name, "type"))
                        return .type_type;

                    if (isPrimitiveNonType(ident_name))
                        return .{ .primitive = node };

                    if (std.zig.primitives.isPrimitive(ident_name))
                        return .type;

                    if (file.ident_decls.get(name_token)) |decl_node| {
                        const decl_index = file.node_decls.get(decl_node) orelse .none;
                        if (decl_index != .none) return .{ .alias = decl_index };
                        return categorize_decl(file_index, decl_node);
                    }

                    return .{ .global_const = node };
                },

                .field_access => {
                    const object_node, const field_ident = ast.nodeData(node).node_and_token;
                    const field_name = ast.tokenSlice(field_ident);

                    switch (categorize_expr(file_index, object_node)) {
                        .alias => |aliasee| if (aliasee.get().get_child(field_name)) |decl_index| {
                            return .{ .alias = decl_index };
                        },
                        else => {},
                    }

                    return .{ .global_const = node };
                },

                .builtin_call_two,
                .builtin_call_two_comma,
                .builtin_call,
                .builtin_call_comma,
                => {
                    var buf: [2]Ast.Node.Index = undefined;
                    const params = ast.builtinCallParams(&buf, node).?;
                    return categorize_builtin_call(file_index, node, params);
                },

                .call_one,
                .call_one_comma,
                .call,
                .call_comma,
                => {
                    var buf: [1]Ast.Node.Index = undefined;
                    return categorize_call(file_index, node, ast.fullCall(&buf, node).?);
                },

                .if_simple,
                .@"if",
                => {
                    const if_full = ast.fullIf(node).?;
                    if (if_full.ast.else_expr.unwrap()) |else_expr| {
                        const then_cat = categorize_expr_deep(file_index, if_full.ast.then_expr);
                        const else_cat = categorize_expr_deep(file_index, else_expr);
                        if (then_cat == .type_type and else_cat == .type_type) {
                            return .type_type;
                        } else if (then_cat == .error_set and else_cat == .error_set) {
                            return .{ .error_set = node };
                        } else if (then_cat == .type or else_cat == .type or
                            then_cat == .namespace or else_cat == .namespace or
                            then_cat == .container or else_cat == .container or
                            then_cat == .error_set or else_cat == .error_set or
                            then_cat == .type_function or else_cat == .type_function)
                        {
                            return .type;
                        }
                    }
                    return .{ .global_const = node };
                },

                .@"switch", .switch_comma => return categorize_switch(file_index, node),

                .optional_type,
                .array_type,
                .array_type_sentinel,
                .ptr_type_aligned,
                .ptr_type_sentinel,
                .ptr_type,
                .ptr_type_bit_range,
                .anyframe_type,
                => .type,

                else => .{ .global_const = node },
            };
        }

        fn categorize_call(
            file_index: File.Index,
            node: Ast.Node.Index,
            call: Ast.full.Call,
        ) Category {
            return switch (categorize_expr(file_index, call.ast.fn_expr)) {
                .type_function => .type,
                .alias => |aliasee| categorize_decl_as_callee(aliasee, node),
                else => .{ .global_const = node },
            };
        }

        fn categorize_decl_as_callee(decl_index: Decl.Index, call_node: Ast.Node.Index) Category {
            return switch (decl_index.get().categorize()) {
                .type_function => .type,
                .alias => |aliasee| categorize_decl_as_callee(aliasee, call_node),
                else => .{ .global_const = call_node },
            };
        }

        fn categorize_builtin_call(
            file_index: File.Index,
            node: Ast.Node.Index,
            params: []const Ast.Node.Index,
        ) Category {
            const ast = file_index.get_ast();
            const builtin_token = ast.nodeMainToken(node);
            const builtin_name = ast.tokenSlice(builtin_token);
            if (std.mem.eql(u8, builtin_name, "@import")) {
                const str_lit_token = ast.nodeMainToken(params[0]);
                const str_bytes = ast.tokenSlice(str_lit_token);
                const file_path = std.zig.string_literal.parseAlloc(gpa, str_bytes) catch @panic("OOM");
                defer gpa.free(file_path);
                if (modules.get(file_path)) |imported_file_index| {
                    return .{ .alias = File.Index.findRootDecl(imported_file_index) };
                }
                const base_path = file_index.path();
                const resolved_path = std.fs.path.resolvePosix(gpa, &.{
                    base_path, "..", file_path,
                }) catch @panic("OOM");
                defer gpa.free(resolved_path);
                log.debug("from '{s}' @import '{s}' resolved='{s}'", .{
                    base_path, file_path, resolved_path,
                });
                if (files.getIndex(resolved_path)) |imported_file_index| {
                    return .{ .alias = File.Index.findRootDecl(@fromBackingInt(@intCast(imported_file_index))) };
                } else {
                    log.warn("import target '{s}' did not resolve to any file", .{resolved_path});
                }
            } else if (std.mem.eql(u8, builtin_name, "@This")) {
                if (file_index.get().node_decls.get(node)) |decl_index| {
                    return .{ .alias = decl_index };
                } else {
                    log.warn("@This() is missing link to Decl.Index", .{});
                }
            }

            return .{ .global_const = node };
        }

        fn categorize_switch(file_index: File.Index, node: Ast.Node.Index) Category {
            const ast = file_index.get_ast();
            const full = ast.fullSwitch(node).?;
            var all_type_type = true;
            var all_error_set = true;
            var any_type = false;
            if (full.ast.cases.len == 0) return .{ .global_const = node };
            for (full.ast.cases) |case_node| {
                const case = ast.fullSwitchCase(case_node).?;
                switch (categorize_expr_deep(file_index, case.ast.target_expr)) {
                    .type_type => {
                        any_type = true;
                        all_error_set = false;
                    },
                    .error_set => {
                        any_type = true;
                        all_type_type = false;
                    },
                    .type, .namespace, .container, .type_function => {
                        any_type = true;
                        all_error_set = false;
                        all_type_type = false;
                    },
                    else => {
                        all_error_set = false;
                        all_type_type = false;
                    },
                }
            }
            if (all_type_type) return .type_type;
            if (all_error_set) return .{ .error_set = node };
            if (any_type) return .type;
            return .{ .global_const = node };
        }
    };
}