zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

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 }