const Decl = @This(); const std = @import("std"); const Ast = std.zig.Ast; const Walk = @import("Walk.zig"); const gpa = std.heap.wasm_allocator; const assert = std.debug.assert; const log = std.log; const Oom = error{OutOfMemory}; ast_node: Ast.Node.Index, file: Walk.File.Index, /// The decl whose namespace this is in. parent: Index, pub const ExtraInfo = struct { is_pub: bool, name: []const u8, first_doc_comment: Ast.OptionalTokenIndex, }; pub const Index = enum(u32) { none = std.math.maxInt(u32), _, pub fn get(i: Index) *Decl { return &Walk.decls.items[@intFromEnum(i)]; } }; pub fn is_pub(d: *const Decl) bool { return d.extra_info().is_pub; } pub fn extra_info(d: *const Decl) ExtraInfo { const ast = d.file.get_ast(); switch (ast.nodeTag(d.ast_node)) { .root => return .{ .name = "", .is_pub = true, .first_doc_comment = if (ast.tokenTag(0) == .container_doc_comment) .fromToken(0) else .none, }, .global_var_decl, .local_var_decl, .simple_var_decl, .aligned_var_decl, => { const var_decl = ast.fullVarDecl(d.ast_node).?; const name_token = var_decl.ast.mut_token + 1; assert(ast.tokenTag(name_token) == .identifier); const ident_name = ast.tokenSlice(name_token); return .{ .name = ident_name, .is_pub = var_decl.visib_token != null, .first_doc_comment = findFirstDocComment(ast, var_decl.firstToken()), }; }, .fn_proto, .fn_proto_multi, .fn_proto_one, .fn_proto_simple, .fn_decl, => { var buf: [1]Ast.Node.Index = undefined; const fn_proto = ast.fullFnProto(&buf, d.ast_node).?; const name_token = fn_proto.name_token.?; assert(ast.tokenTag(name_token) == .identifier); const ident_name = ast.tokenSlice(name_token); return .{ .name = ident_name, .is_pub = fn_proto.visib_token != null, .first_doc_comment = findFirstDocComment(ast, fn_proto.firstToken()), }; }, else => |t| { log.debug("hit '{s}'", .{@tagName(t)}); unreachable; }, } } pub fn value_node(d: *const Decl) ?Ast.Node.Index { const ast = d.file.get_ast(); return switch (ast.nodeTag(d.ast_node)) { .fn_proto, .fn_proto_multi, .fn_proto_one, .fn_proto_simple, .fn_decl, .root, => d.ast_node, .global_var_decl, .local_var_decl, .simple_var_decl, .aligned_var_decl, => { const var_decl = ast.fullVarDecl(d.ast_node).?; if (ast.tokenTag(var_decl.ast.mut_token) == .keyword_const) return var_decl.ast.init_node.unwrap(); return null; }, else => null, }; } pub fn categorize(decl: *const Decl) Walk.Category { return decl.file.categorize_decl(decl.ast_node); } /// Looks up a direct child of `decl` by name. pub fn get_child(decl: *const Decl, name: []const u8) ?Decl.Index { switch (decl.categorize()) { .alias => |aliasee| return aliasee.get().get_child(name), .namespace, .container => |node| { const file = decl.file.get(); const scope = file.scopes.get(node) orelse return null; const child_node = scope.get_child(name) orelse return null; return file.node_decls.get(child_node); }, .type_function => { // Find a decl with this function as the parent, with a name matching `name` for (Walk.decls.items, 0..) |*candidate, i| { if (candidate.parent != .none and candidate.parent.get() == decl and std.mem.eql(u8, candidate.extra_info().name, name)) { return @enumFromInt(i); } } return null; }, else => return null, } } /// If the type function returns another type function, return the index of that type function. pub fn get_type_fn_return_type_fn(decl: *const Decl) ?Decl.Index { if (decl.get_type_fn_return_expr()) |return_expr| { const ast = decl.file.get_ast(); var buffer: [1]Ast.Node.Index = undefined; const call = ast.fullCall(&buffer, return_expr) orelse return null; const token = ast.nodeMainToken(call.ast.fn_expr); const name = ast.tokenSlice(token); if (decl.lookup(name)) |function_decl| { return function_decl; } } return null; } /// Gets the expression after the `return` keyword in a type function declaration. pub fn get_type_fn_return_expr(decl: *const Decl) ?Ast.Node.Index { switch (decl.categorize()) { .type_function => { const ast = decl.file.get_ast(); const body_node = ast.nodeData(decl.ast_node).node_and_node[1]; var buf: [2]Ast.Node.Index = undefined; const statements = ast.blockStatements(&buf, body_node) orelse return null; for (statements) |stmt| { if (ast.nodeTag(stmt) == .@"return") { return ast.nodeData(stmt).node; } } return null; }, else => return null, } } /// Looks up a decl by name accessible in `decl`'s namespace. pub fn lookup(decl: *const Decl, name: []const u8) ?Decl.Index { const namespace_node = switch (decl.categorize()) { .namespace, .container => |node| node, else => decl.parent.get().ast_node, }; const file = decl.file.get(); const scope = file.scopes.get(namespace_node) orelse return null; const resolved_node = scope.lookup(&file.ast, name) orelse return null; return file.node_decls.get(resolved_node); } /// Appends the fully qualified name to `out`. pub fn fqn(decl: *const Decl, out: *std.ArrayListUnmanaged(u8)) Oom!void { try decl.append_path(out); if (decl.parent != .none) { try append_parent_ns(out, decl.parent); try out.appendSlice(gpa, decl.extra_info().name); } else { out.items.len -= 1; // remove the trailing '.' } } pub fn reset_with_path(decl: *const Decl, list: *std.ArrayListUnmanaged(u8)) Oom!void { list.clearRetainingCapacity(); try append_path(decl, list); } pub fn append_path(decl: *const Decl, list: *std.ArrayListUnmanaged(u8)) Oom!void { const start = list.items.len; // Prefer the module name alias. for (Walk.modules.keys(), Walk.modules.values()) |pkg_name, pkg_file| { if (pkg_file == decl.file) { try list.ensureUnusedCapacity(gpa, pkg_name.len + 1); list.appendSliceAssumeCapacity(pkg_name); list.appendAssumeCapacity('.'); return; } } const file_path = decl.file.path(); try list.ensureUnusedCapacity(gpa, file_path.len + 1); list.appendSliceAssumeCapacity(file_path); for (list.items[start..]) |*byte| switch (byte.*) { '/' => byte.* = '.', else => continue, }; if (std.mem.endsWith(u8, list.items, ".zig")) { list.items.len -= 3; } else { list.appendAssumeCapacity('.'); } } pub fn append_parent_ns(list: *std.ArrayListUnmanaged(u8), parent: Decl.Index) Oom!void { assert(parent != .none); const decl = parent.get(); if (decl.parent != .none) { try append_parent_ns(list, decl.parent); try list.appendSlice(gpa, decl.extra_info().name); try list.append(gpa, '.'); } } pub fn findFirstDocComment(ast: *const Ast, token: Ast.TokenIndex) Ast.OptionalTokenIndex { var it = token; while (it > 0) { it -= 1; if (ast.tokenTag(it) != .doc_comment) { return .fromToken(it + 1); } } return .none; } /// Successively looks up each component. pub fn find(search_string: []const u8) Decl.Index { var path_components = std.mem.splitScalar(u8, search_string, '.'); const file = Walk.modules.get(path_components.first()) orelse return .none; var current_decl_index = file.findRootDecl(); while (path_components.next()) |component| { while (true) switch (current_decl_index.get().categorize()) { .alias => |aliasee| current_decl_index = aliasee, else => break, }; current_decl_index = current_decl_index.get().get_child(component) orelse return .none; } return current_decl_index; }