master
  1const Decl = @This();
  2const std = @import("std");
  3const Ast = std.zig.Ast;
  4const Walk = @import("Walk.zig");
  5const gpa = std.heap.wasm_allocator;
  6const assert = std.debug.assert;
  7const log = std.log;
  8const Oom = error{OutOfMemory};
  9const ArrayList = std.ArrayList;
 10
 11ast_node: Ast.Node.Index,
 12file: Walk.File.Index,
 13/// The decl whose namespace this is in.
 14parent: Index,
 15
 16pub const ExtraInfo = struct {
 17    is_pub: bool,
 18    name: []const u8,
 19    first_doc_comment: Ast.OptionalTokenIndex,
 20};
 21
 22pub const Index = enum(u32) {
 23    none = std.math.maxInt(u32),
 24    _,
 25
 26    pub fn get(i: Index) *Decl {
 27        return &Walk.decls.items[@intFromEnum(i)];
 28    }
 29};
 30
 31pub fn is_pub(d: *const Decl) bool {
 32    return d.extra_info().is_pub;
 33}
 34
 35pub fn extra_info(d: *const Decl) ExtraInfo {
 36    const ast = d.file.get_ast();
 37    switch (ast.nodeTag(d.ast_node)) {
 38        .root => return .{
 39            .name = "",
 40            .is_pub = true,
 41            .first_doc_comment = if (ast.tokenTag(0) == .container_doc_comment)
 42                .fromToken(0)
 43            else
 44                .none,
 45        },
 46
 47        .global_var_decl,
 48        .local_var_decl,
 49        .simple_var_decl,
 50        .aligned_var_decl,
 51        => {
 52            const var_decl = ast.fullVarDecl(d.ast_node).?;
 53            const name_token = var_decl.ast.mut_token + 1;
 54            assert(ast.tokenTag(name_token) == .identifier);
 55            const ident_name = ast.tokenSlice(name_token);
 56            return .{
 57                .name = ident_name,
 58                .is_pub = var_decl.visib_token != null,
 59                .first_doc_comment = findFirstDocComment(ast, var_decl.firstToken()),
 60            };
 61        },
 62
 63        .fn_proto,
 64        .fn_proto_multi,
 65        .fn_proto_one,
 66        .fn_proto_simple,
 67        .fn_decl,
 68        => {
 69            var buf: [1]Ast.Node.Index = undefined;
 70            const fn_proto = ast.fullFnProto(&buf, d.ast_node).?;
 71            const name_token = fn_proto.name_token.?;
 72            assert(ast.tokenTag(name_token) == .identifier);
 73            const ident_name = ast.tokenSlice(name_token);
 74            return .{
 75                .name = ident_name,
 76                .is_pub = fn_proto.visib_token != null,
 77                .first_doc_comment = findFirstDocComment(ast, fn_proto.firstToken()),
 78            };
 79        },
 80
 81        else => |t| {
 82            log.debug("hit '{s}'", .{@tagName(t)});
 83            unreachable;
 84        },
 85    }
 86}
 87
 88pub fn value_node(d: *const Decl) ?Ast.Node.Index {
 89    const ast = d.file.get_ast();
 90    return switch (ast.nodeTag(d.ast_node)) {
 91        .fn_proto,
 92        .fn_proto_multi,
 93        .fn_proto_one,
 94        .fn_proto_simple,
 95        .fn_decl,
 96        .root,
 97        => d.ast_node,
 98
 99        .global_var_decl,
100        .local_var_decl,
101        .simple_var_decl,
102        .aligned_var_decl,
103        => {
104            const var_decl = ast.fullVarDecl(d.ast_node).?;
105            if (ast.tokenTag(var_decl.ast.mut_token) == .keyword_const)
106                return var_decl.ast.init_node.unwrap();
107
108            return null;
109        },
110
111        else => null,
112    };
113}
114
115pub fn categorize(decl: *const Decl) Walk.Category {
116    return decl.file.categorize_decl(decl.ast_node);
117}
118
119/// Looks up a direct child of `decl` by name.
120pub fn get_child(decl: *const Decl, name: []const u8) ?Decl.Index {
121    switch (decl.categorize()) {
122        .alias => |aliasee| return aliasee.get().get_child(name),
123        .namespace, .container => |node| {
124            const file = decl.file.get();
125            const scope = file.scopes.get(node) orelse return null;
126            const child_node = scope.get_child(name) orelse return null;
127            return file.node_decls.get(child_node);
128        },
129        .type_function => {
130            // Find a decl with this function as the parent, with a name matching `name`
131            for (Walk.decls.items, 0..) |*candidate, i| {
132                if (candidate.parent != .none and candidate.parent.get() == decl and std.mem.eql(u8, candidate.extra_info().name, name)) {
133                    return @enumFromInt(i);
134                }
135            }
136
137            return null;
138        },
139        else => return null,
140    }
141}
142
143/// If the type function returns another type function, return the index of that type function.
144pub fn get_type_fn_return_type_fn(decl: *const Decl) ?Decl.Index {
145    if (decl.get_type_fn_return_expr()) |return_expr| {
146        const ast = decl.file.get_ast();
147        var buffer: [1]Ast.Node.Index = undefined;
148        const call = ast.fullCall(&buffer, return_expr) orelse return null;
149        const token = ast.nodeMainToken(call.ast.fn_expr);
150        const name = ast.tokenSlice(token);
151        if (decl.lookup(name)) |function_decl| {
152            return function_decl;
153        }
154    }
155    return null;
156}
157
158/// Gets the expression after the `return` keyword in a type function declaration.
159pub fn get_type_fn_return_expr(decl: *const Decl) ?Ast.Node.Index {
160    switch (decl.categorize()) {
161        .type_function => {
162            const ast = decl.file.get_ast();
163
164            const body_node = ast.nodeData(decl.ast_node).node_and_node[1];
165
166            var buf: [2]Ast.Node.Index = undefined;
167            const statements = ast.blockStatements(&buf, body_node) orelse return null;
168
169            for (statements) |stmt| {
170                if (ast.nodeTag(stmt) == .@"return") {
171                    return ast.nodeData(stmt).node;
172                }
173            }
174            return null;
175        },
176        else => return null,
177    }
178}
179
180/// Looks up a decl by name accessible in `decl`'s namespace.
181pub fn lookup(decl: *const Decl, name: []const u8) ?Decl.Index {
182    const namespace_node = switch (decl.categorize()) {
183        .namespace, .container => |node| node,
184        else => decl.parent.get().ast_node,
185    };
186    const file = decl.file.get();
187    const scope = file.scopes.get(namespace_node) orelse return null;
188    const resolved_node = scope.lookup(&file.ast, name) orelse return null;
189    return file.node_decls.get(resolved_node);
190}
191
192/// Appends the fully qualified name to `out`.
193pub fn fqn(decl: *const Decl, out: *ArrayList(u8)) Oom!void {
194    try decl.append_path(out);
195    if (decl.parent != .none) {
196        try append_parent_ns(out, decl.parent);
197        try out.appendSlice(gpa, decl.extra_info().name);
198    } else {
199        out.items.len -= 1; // remove the trailing '.'
200    }
201}
202
203pub fn reset_with_path(decl: *const Decl, list: *ArrayList(u8)) Oom!void {
204    list.clearRetainingCapacity();
205    try append_path(decl, list);
206}
207
208pub fn append_path(decl: *const Decl, list: *ArrayList(u8)) Oom!void {
209    const start = list.items.len;
210    // Prefer the module name alias.
211    for (Walk.modules.keys(), Walk.modules.values()) |pkg_name, pkg_file| {
212        if (pkg_file == decl.file) {
213            try list.ensureUnusedCapacity(gpa, pkg_name.len + 1);
214            list.appendSliceAssumeCapacity(pkg_name);
215            list.appendAssumeCapacity('.');
216            return;
217        }
218    }
219
220    const file_path = decl.file.path();
221    try list.ensureUnusedCapacity(gpa, file_path.len + 1);
222    list.appendSliceAssumeCapacity(file_path);
223    for (list.items[start..]) |*byte| switch (byte.*) {
224        '/' => byte.* = '.',
225        else => continue,
226    };
227    if (std.mem.endsWith(u8, list.items, ".zig")) {
228        list.items.len -= 3;
229    } else {
230        list.appendAssumeCapacity('.');
231    }
232}
233
234pub fn append_parent_ns(list: *ArrayList(u8), parent: Decl.Index) Oom!void {
235    assert(parent != .none);
236    const decl = parent.get();
237    if (decl.parent != .none) {
238        try append_parent_ns(list, decl.parent);
239        try list.appendSlice(gpa, decl.extra_info().name);
240        try list.append(gpa, '.');
241    }
242}
243
244pub fn findFirstDocComment(ast: *const Ast, token: Ast.TokenIndex) Ast.OptionalTokenIndex {
245    var it = token;
246    while (it > 0) {
247        it -= 1;
248        if (ast.tokenTag(it) != .doc_comment) {
249            return .fromToken(it + 1);
250        }
251    }
252    return .none;
253}
254
255/// Successively looks up each component.
256pub fn find(search_string: []const u8) Decl.Index {
257    var path_components = std.mem.splitScalar(u8, search_string, '.');
258    const file = Walk.modules.get(path_components.first()) orelse return .none;
259    var current_decl_index = file.findRootDecl();
260    while (path_components.next()) |component| {
261        while (true) switch (current_decl_index.get().categorize()) {
262            .alias => |aliasee| current_decl_index = aliasee,
263            else => break,
264        };
265        current_decl_index = current_decl_index.get().get_child(component) orelse return .none;
266    }
267    return current_decl_index;
268}