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.

Root

Scope.Root
pub const Root = struct

File

Code

pub const Root = struct {
    base: Scope,
    translator: *Translator,
    sym_table: SymbolTable,
    blank_macros: std.array_hash_map.String(void),
    nodes: std.ArrayList(ast.Node),
    container_member_fns_map: ContainerMemberFnsHashMap,

    pub fn init(t: *Translator) Root {
        return .{
            .base = .{
                .id = .root,
                .parent = null,
            },
            .translator = t,
            .sym_table = .empty,
            .blank_macros = .empty,
            .nodes = .empty,
            .container_member_fns_map = .empty,
        };
    }

    pub fn deinit(root: *Root) void {
        root.sym_table.deinit(root.translator.gpa);
        root.blank_macros.deinit(root.translator.gpa);
        root.nodes.deinit(root.translator.gpa);
        for (root.container_member_fns_map.values()) |*members| {
            members.member_fns.deinit(root.translator.gpa);
        }
        root.container_member_fns_map.deinit(root.translator.gpa);
    }

    /// Check if the global scope contains this name, without looking into the "future", e.g.
    /// ignore the preprocessed decl and macro names.
    pub fn containsNow(root: *Root, name: []const u8) bool {
        return root.sym_table.contains(name);
    }

    /// Check if the global scope contains the name, includes all decls that haven't been translated yet.
    pub fn contains(root: *Root, name: []const u8) bool {
        return root.containsNow(name) or root.translator.global_names.contains(name) or root.translator.weak_global_names.contains(name);
    }

    pub fn addMemberFunction(root: *Root, func_ty: aro.Type.Func, func: *ast.Payload.Func) !void {
        std.debug.assert(func.data.name != null);
        if (func_ty.params.len == 0) return;

        const param1_base = func_ty.params[0].qt.base(root.translator.comp);
        const container_qt = if (param1_base.type == .pointer)
            param1_base.type.pointer.child.base(root.translator.comp).qt
        else
            param1_base.qt;

        if (root.container_member_fns_map.getPtr(container_qt)) |members| {
            try members.member_fns.append(root.translator.gpa, func);
        }
    }

    pub fn processContainerMemberFns(root: *Root) !void {
        const gpa = root.translator.gpa;
        const arena = root.translator.arena;

        var member_names: std.array_hash_map.String(void) = .empty;
        defer member_names.deinit(gpa);
        for (root.container_member_fns_map.keys(), root.container_member_fns_map.values()) |container_qt, members| {
            // Get the container name
            const container_name = root.translator.unnamed_typedefs.get(container_qt) orelse
                container_qt.getRecord(root.translator.comp).?.name.lookup(root.translator.comp);
            std.debug.assert(container_name.len > 0);

            member_names.clearRetainingCapacity();
            const decls_ptr = switch (members.container_decl_ptr.tag()) {
                .@"struct", .@"union" => blk_record: {
                    const payload: *ast.Payload.Container = @alignCast(@fieldParentPtr("base", members.container_decl_ptr.ptr_otherwise));
                    // Avoid duplication with field names
                    for (payload.data.fields) |field| {
                        try member_names.put(gpa, field.name, {});
                    }
                    break :blk_record &payload.data.decls;
                },
                .opaque_literal => blk_opaque: {
                    const container_decl = try ast.Node.Tag.@"opaque".create(arena, .{
                        .layout = .none,
                        .fields = &.{},
                        .decls = &.{},
                    });
                    members.container_decl_ptr.* = container_decl;
                    break :blk_opaque &container_decl.castTag(.@"opaque").?.data.decls;
                },
                else => continue,
            };

            const old_decls = decls_ptr.*;
            const new_decls = try arena.alloc(ast.Node, old_decls.len + members.member_fns.items.len * 2);
            @memcpy(new_decls[0..old_decls.len], old_decls);
            // Assume the allocator of payload.data.decls is arena,
            // so don't add arena.free(old_variables).
            const func_ref_vars = new_decls[old_decls.len..];
            var count: u32 = 0;

            // Add members without mangling them - only fields may cause name conflicts
            for (members.member_fns.items) |func| {
                const func_name = func.data.name.?;
                const member_name_slot = try member_names.getOrPutValue(gpa, func_name, {});
                if (member_name_slot.found_existing) continue;
                func_ref_vars[count] = try ast.Node.Tag.pub_var_simple.create(arena, .{
                    .name = func_name,
                    .init = try ast.Node.Tag.root_ref.create(arena, func_name),
                });
                count += 1;
            }

            for (members.member_fns.items) |func| {
                const func_name = func.data.name.?;
                const func_name_alias = blk: {
                    // Try multiple candidate prefixes to extract the alias
                    // 1. typedef struct { ... } foo; -> foo_get_bar() extracts "get_bar"
                    // 2. typedef struct _foo foo; -> foo_get_bar() extracts "get_bar"
                    const container_name_trimmed = std.mem.trimStart(u8, container_name, "_");
                    const suffix = std.mem.cutPrefix(u8, func_name, container_name_trimmed);
                    // Check suffix starts with '_' to avoid invalid aliases like "1_get_bar" from foo1_get_bar()
                    if (suffix) |alias| if (alias.len > 0 and alias[0] == '_') {
                        const alias_trimmed = std.mem.trimStart(u8, alias, "_");
                        if (alias_trimmed.len > 0) break :blk alias_trimmed;
                    };

                    // Doesn't match any prefix - fallback to trimming trailing underscores and using last segment
                    const func_name_trimmed = std.mem.trimEnd(u8, func_name, "_");
                    const last_idx = std.mem.findLast(u8, func_name_trimmed, "_") orelse continue;
                    break :blk func_name[last_idx + 1 ..];
                };

                // Skip if the alias conflicts with an existing type
                if (root.contains(func_name_alias)) continue;
                const member_name_slot = try member_names.getOrPutValue(gpa, func_name_alias, {});
                if (member_name_slot.found_existing) continue;
                func_ref_vars[count] = try ast.Node.Tag.pub_var_simple.create(arena, .{
                    .name = func_name_alias,
                    .init = try ast.Node.Tag.root_ref.create(arena, func_name),
                });
                count += 1;
            }

            decls_ptr.* = new_decls[0 .. old_decls.len + count];
        }
    }
}