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}