mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 22:04:21 +00:00
This commits adds the following distinct integer types to std.zig.Ast: - OptionalTokenIndex - TokenOffset - OptionalTokenOffset - Node.OptionalIndex - Node.Offset - Node.OptionalOffset The `Node.Index` type has also been converted to a distinct type while `TokenIndex` remains unchanged. `Ast.Node.Data` has also been changed to a (untagged) union to provide safety checks.
267 lines
8.4 KiB
Zig
267 lines
8.4 KiB
Zig
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;
|
|
}
|