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