feature. See also
. The project being documented here (as the example) is the Zig library itself.
Walk.File
pub const File = struct
File
Code
pub const File = struct {
ast: Ast,
ident_decls: std.array_hash_map.Auto(Ast.TokenIndex, Ast.Node.Index) = .empty,
token_parents: std.array_hash_map.Auto(Ast.TokenIndex, Ast.Node.Index) = .empty,
node_decls: std.array_hash_map.Auto(Ast.Node.Index, Decl.Index) = .empty,
doctests: std.array_hash_map.Auto(Ast.Node.Index, Ast.Node.Index) = .empty,
scopes: std.array_hash_map.Auto(Ast.Node.Index, *Scope) = .empty,
pub fn lookup_token(file: *File, token: Ast.TokenIndex) Decl.Index {
const decl_node = file.ident_decls.get(token) orelse return .none;
return file.node_decls.get(decl_node) orelse return .none;
}
pub const Index = enum(u32) {
_,
fn add_decl(i: Index, node: Ast.Node.Index, parent_decl: Decl.Index) Oom!Decl.Index {
try decls.append(gpa, .{
.ast_node = node,
.file = i,
.parent = parent_decl,
});
const decl_index: Decl.Index = @fromBackingInt(@intCast(decls.items.len - 1));
try i.get().node_decls.put(gpa, node, decl_index);
return decl_index;
}
pub fn get(i: File.Index) *File {
return &files.values()[@backingInt(i)];
}
pub fn get_ast(i: File.Index) *Ast {
return &i.get().ast;
}
pub fn path(i: File.Index) []const u8 {
return files.keys()[@backingInt(i)];
}
pub fn findRootDecl(file_index: File.Index) Decl.Index {
return file_index.get().node_decls.values()[0];
}
pub fn categorize_decl(file_index: File.Index, node: Ast.Node.Index) Category {
const ast = file_index.get_ast();
switch (ast.nodeTag(node)) {
.root => {
for (ast.rootDecls()) |member| {
switch (ast.nodeTag(member)) {
.container_field_init,
.container_field_align,
.container_field,
=> return .{ .container = node },
else => {},
}
}
return .{ .namespace = node };
},
.global_var_decl,
.local_var_decl,
.simple_var_decl,
.aligned_var_decl,
=> {
const var_decl = ast.fullVarDecl(node).?;
if (ast.tokenTag(var_decl.ast.mut_token) == .keyword_var)
return .{ .global_variable = node };
const init_node = var_decl.ast.init_node.unwrap() orelse
return .{ .global_const = node };
return categorize_expr(file_index, init_node);
},
.fn_proto,
.fn_proto_multi,
.fn_proto_one,
.fn_proto_simple,
.fn_decl,
=> {
var buf: [1]Ast.Node.Index = undefined;
const full = ast.fullFnProto(&buf, node).?;
return categorize_func(file_index, node, full);
},
else => unreachable,
}
}
pub fn categorize_func(
file_index: File.Index,
node: Ast.Node.Index,
full: Ast.full.FnProto,
) Category {
return switch (categorize_expr(file_index, full.ast.return_type.unwrap().?)) {
.namespace, .container, .error_set, .type_type => .{ .type_function = node },
else => .{ .function = node },
};
}
pub fn categorize_expr_deep(file_index: File.Index, node: Ast.Node.Index) Category {
return switch (categorize_expr(file_index, node)) {
.alias => |aliasee| aliasee.get().categorize(),
else => |result| result,
};
}
pub fn categorize_expr(file_index: File.Index, node: Ast.Node.Index) Category {
const file = file_index.get();
const ast = file_index.get_ast();
return switch (ast.nodeTag(node)) {
.container_decl,
.container_decl_trailing,
.container_decl_arg,
.container_decl_arg_trailing,
.container_decl_two,
.container_decl_two_trailing,
.tagged_union,
.tagged_union_trailing,
.tagged_union_enum_tag,
.tagged_union_enum_tag_trailing,
.tagged_union_two,
.tagged_union_two_trailing,
=> {
var buf: [2]Ast.Node.Index = undefined;
const container_decl = ast.fullContainerDecl(&buf, node).?;
if (ast.tokenTag(container_decl.ast.main_token) != .keyword_struct) {
return .{ .container = node };
}
for (container_decl.ast.members) |member| {
switch (ast.nodeTag(member)) {
.container_field_init,
.container_field_align,
.container_field,
=> return .{ .container = node },
else => {},
}
}
return .{ .namespace = node };
},
.error_set_decl,
.merge_error_sets,
=> .{ .error_set = node },
.identifier => {
const name_token = ast.nodeMainToken(node);
const ident_name = ast.tokenSlice(name_token);
if (std.mem.eql(u8, ident_name, "type"))
return .type_type;
if (isPrimitiveNonType(ident_name))
return .{ .primitive = node };
if (std.zig.primitives.isPrimitive(ident_name))
return .type;
if (file.ident_decls.get(name_token)) |decl_node| {
const decl_index = file.node_decls.get(decl_node) orelse .none;
if (decl_index != .none) return .{ .alias = decl_index };
return categorize_decl(file_index, decl_node);
}
return .{ .global_const = node };
},
.field_access => {
const object_node, const field_ident = ast.nodeData(node).node_and_token;
const field_name = ast.tokenSlice(field_ident);
switch (categorize_expr(file_index, object_node)) {
.alias => |aliasee| if (aliasee.get().get_child(field_name)) |decl_index| {
return .{ .alias = decl_index };
},
else => {},
}
return .{ .global_const = node };
},
.builtin_call_two,
.builtin_call_two_comma,
.builtin_call,
.builtin_call_comma,
=> {
var buf: [2]Ast.Node.Index = undefined;
const params = ast.builtinCallParams(&buf, node).?;
return categorize_builtin_call(file_index, node, params);
},
.call_one,
.call_one_comma,
.call,
.call_comma,
=> {
var buf: [1]Ast.Node.Index = undefined;
return categorize_call(file_index, node, ast.fullCall(&buf, node).?);
},
.if_simple,
.@"if",
=> {
const if_full = ast.fullIf(node).?;
if (if_full.ast.else_expr.unwrap()) |else_expr| {
const then_cat = categorize_expr_deep(file_index, if_full.ast.then_expr);
const else_cat = categorize_expr_deep(file_index, else_expr);
if (then_cat == .type_type and else_cat == .type_type) {
return .type_type;
} else if (then_cat == .error_set and else_cat == .error_set) {
return .{ .error_set = node };
} else if (then_cat == .type or else_cat == .type or
then_cat == .namespace or else_cat == .namespace or
then_cat == .container or else_cat == .container or
then_cat == .error_set or else_cat == .error_set or
then_cat == .type_function or else_cat == .type_function)
{
return .type;
}
}
return .{ .global_const = node };
},
.@"switch", .switch_comma => return categorize_switch(file_index, node),
.optional_type,
.array_type,
.array_type_sentinel,
.ptr_type_aligned,
.ptr_type_sentinel,
.ptr_type,
.ptr_type_bit_range,
.anyframe_type,
=> .type,
else => .{ .global_const = node },
};
}
fn categorize_call(
file_index: File.Index,
node: Ast.Node.Index,
call: Ast.full.Call,
) Category {
return switch (categorize_expr(file_index, call.ast.fn_expr)) {
.type_function => .type,
.alias => |aliasee| categorize_decl_as_callee(aliasee, node),
else => .{ .global_const = node },
};
}
fn categorize_decl_as_callee(decl_index: Decl.Index, call_node: Ast.Node.Index) Category {
return switch (decl_index.get().categorize()) {
.type_function => .type,
.alias => |aliasee| categorize_decl_as_callee(aliasee, call_node),
else => .{ .global_const = call_node },
};
}
fn categorize_builtin_call(
file_index: File.Index,
node: Ast.Node.Index,
params: []const Ast.Node.Index,
) Category {
const ast = file_index.get_ast();
const builtin_token = ast.nodeMainToken(node);
const builtin_name = ast.tokenSlice(builtin_token);
if (std.mem.eql(u8, builtin_name, "@import")) {
const str_lit_token = ast.nodeMainToken(params[0]);
const str_bytes = ast.tokenSlice(str_lit_token);
const file_path = std.zig.string_literal.parseAlloc(gpa, str_bytes) catch @panic("OOM");
defer gpa.free(file_path);
if (modules.get(file_path)) |imported_file_index| {
return .{ .alias = File.Index.findRootDecl(imported_file_index) };
}
const base_path = file_index.path();
const resolved_path = std.fs.path.resolvePosix(gpa, &.{
base_path, "..", file_path,
}) catch @panic("OOM");
defer gpa.free(resolved_path);
log.debug("from '{s}' @import '{s}' resolved='{s}'", .{
base_path, file_path, resolved_path,
});
if (files.getIndex(resolved_path)) |imported_file_index| {
return .{ .alias = File.Index.findRootDecl(@fromBackingInt(@intCast(imported_file_index))) };
} else {
log.warn("import target '{s}' did not resolve to any file", .{resolved_path});
}
} else if (std.mem.eql(u8, builtin_name, "@This")) {
if (file_index.get().node_decls.get(node)) |decl_index| {
return .{ .alias = decl_index };
} else {
log.warn("@This() is missing link to Decl.Index", .{});
}
}
return .{ .global_const = node };
}
fn categorize_switch(file_index: File.Index, node: Ast.Node.Index) Category {
const ast = file_index.get_ast();
const full = ast.fullSwitch(node).?;
var all_type_type = true;
var all_error_set = true;
var any_type = false;
if (full.ast.cases.len == 0) return .{ .global_const = node };
for (full.ast.cases) |case_node| {
const case = ast.fullSwitchCase(case_node).?;
switch (categorize_expr_deep(file_index, case.ast.target_expr)) {
.type_type => {
any_type = true;
all_error_set = false;
},
.error_set => {
any_type = true;
all_type_type = false;
},
.type, .namespace, .container, .type_function => {
any_type = true;
all_error_set = false;
all_type_type = false;
},
else => {
all_error_set = false;
all_type_type = false;
},
}
}
if (all_type_type) return .type_type;
if (all_error_set) return .{ .error_set = node };
if (any_type) return .type;
return .{ .global_const = node };
}
};
}