Add anytype resolution based on call references (#1067)

This commit is contained in:
Auguste Rame
2023-03-31 11:54:46 -04:00
committed by GitHub
parent c217502670
commit 8b5c649805
2 changed files with 342 additions and 38 deletions

View File

@@ -9,6 +9,7 @@ const ast = @import("ast.zig");
const tracy = @import("tracy.zig");
const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
const InternPool = ComptimeInterpreter.InternPool;
const references = @import("features/references.zig");
const Analyser = @This();
@@ -1162,6 +1163,77 @@ pub const TypeWithHandle = struct {
type: Type,
handle: *const DocumentStore.Handle,
const Context = struct {
// Note that we don't hash/equate descriptors to remove
// duplicates
fn hashType(hasher: *std.hash.Wyhash, ty: Type) void {
hasher.update(&.{ @boolToInt(ty.is_type_val), @enumToInt(ty.data) });
switch (ty.data) {
.pointer,
.slice,
.error_union,
.other,
.primitive,
=> |idx| hasher.update(&std.mem.toBytes(idx)),
.either => |entries| {
for (entries) |e| {
hasher.update(e.descriptor);
hasher.update(e.type_with_handle.handle.uri);
hashType(hasher, e.type_with_handle.type);
}
},
.array_index => {},
.@"comptime" => {
// TODO
},
}
}
pub fn hash(self: @This(), item: TypeWithHandle) u64 {
_ = self;
var hasher = std.hash.Wyhash.init(0);
hashType(&hasher, item.type);
hasher.update(item.handle.uri);
return hasher.final();
}
pub fn eql(self: @This(), a: TypeWithHandle, b: TypeWithHandle) bool {
_ = self;
if (!std.mem.eql(u8, a.handle.uri, b.handle.uri)) return false;
if (a.type.is_type_val != b.type.is_type_val) return false;
if (@enumToInt(a.type.data) != @enumToInt(b.type.data)) return false;
switch (a.type.data) {
inline .pointer,
.slice,
.error_union,
.other,
.primitive,
=> |a_idx, name| {
if (a_idx != @field(b.type.data, @tagName(name))) return false;
},
.either => |a_entries| {
const b_entries = b.type.data.either;
if (a_entries.len != b_entries.len) return false;
for (a_entries, b_entries) |ae, be| {
if (!std.mem.eql(u8, ae.descriptor, be.descriptor)) return false;
if (!eql(.{}, ae.type_with_handle, be.type_with_handle)) return false;
}
},
.array_index => {},
.@"comptime" => {
// TODO
},
}
return true;
}
};
pub fn typeVal(node_handle: NodeWithHandle) TypeWithHandle {
return .{
.type = .{
@@ -1172,7 +1244,10 @@ pub const TypeWithHandle = struct {
};
}
pub const Deduplicator = std.HashMapUnmanaged(TypeWithHandle, void, TypeWithHandle.Context, std.hash_map.default_max_load_percentage);
/// Resolves possible types of a type (single for all except array_index and either)
/// Drops duplicates
pub fn getAllTypesWithHandles(ty: TypeWithHandle, arena: std.mem.Allocator) ![]const TypeWithHandle {
var all_types = std.ArrayListUnmanaged(TypeWithHandle){};
try ty.getAllTypesWithHandlesArrayList(arena, &all_types);
@@ -1866,6 +1941,7 @@ pub const Declaration = union(enum) {
/// Function parameter
param_payload: struct {
param: Ast.full.FnProto.Param,
param_idx: u16,
func: Ast.Node.Index,
},
pointer_payload: struct {
@@ -1888,12 +1964,20 @@ pub const Declaration = union(enum) {
},
/// always an identifier
error_token: Ast.Node.Index,
pub fn eql(a: Declaration, b: Declaration) bool {
return std.meta.eql(a, b);
}
};
pub const DeclWithHandle = struct {
decl: *Declaration,
handle: *const DocumentStore.Handle,
pub fn eql(a: DeclWithHandle, b: DeclWithHandle) bool {
return a.decl.eql(b.decl.*) and std.mem.eql(u8, a.handle.uri, b.handle.uri);
}
pub fn nameToken(self: DeclWithHandle) Ast.TokenIndex {
const tree = self.handle.tree;
return switch (self.decl.*) {
@@ -1924,6 +2008,71 @@ pub const DeclWithHandle = struct {
.{ .node = node, .handle = self.handle },
),
.param_payload => |pay| {
// handle anytype
if (pay.param.type_expr == 0) {
var func_decl = Declaration{ .ast_node = pay.func };
var func_buf: [1]Ast.Node.Index = undefined;
const func = tree.fullFnProto(&func_buf, pay.func).?;
var func_params_len: usize = 0;
var it = func.iterate(&tree);
while (ast.nextFnParam(&it)) |_| {
func_params_len += 1;
}
var refs = try references.callsiteReferences(analyser.arena.allocator(), analyser, .{
.decl = &func_decl,
.handle = self.handle,
}, false, false, false);
// TODO: Set `workspace` to true; current problems
// - we gather dependencies, not dependents
// - stack overflow due to cyclically anytype resolution(?)
var possible = std.ArrayListUnmanaged(Type.EitherEntry){};
var deduplicator = TypeWithHandle.Deduplicator{};
defer deduplicator.deinit(analyser.gpa);
for (refs.items) |ref| {
var handle = analyser.store.getOrLoadHandle(ref.uri).?;
var call_buf: [1]Ast.Node.Index = undefined;
var call = handle.tree.fullCall(&call_buf, ref.call_node).?;
const real_param_idx = if (func_params_len != 0 and pay.param_idx != 0 and call.ast.params.len == func_params_len - 1)
pay.param_idx - 1
else
pay.param_idx;
if (real_param_idx >= call.ast.params.len) continue;
if (try analyser.resolveTypeOfNode(.{
// TODO?: this is a """heuristic based approach"""
// perhaps it would be better to use proper self detection
// maybe it'd be a perf issue and this is fine?
// you figure it out future contributor <3
.node = call.ast.params[real_param_idx],
.handle = handle,
})) |ty| {
var gop = try deduplicator.getOrPut(analyser.gpa, ty);
if (gop.found_existing) continue;
var loc = offsets.tokenToPosition(handle.tree, main_tokens[call.ast.params[real_param_idx]], .@"utf-8");
try possible.append(analyser.arena.allocator(), .{ // TODO: Dedup
.type_with_handle = ty,
.descriptor = try std.fmt.allocPrint(analyser.arena.allocator(), "{s}:{d}:{d}", .{ handle.uri, loc.line + 1, loc.character + 1 }),
});
}
}
return TypeWithHandle{
.type = .{ .data = .{ .either = try possible.toOwnedSlice(analyser.arena.allocator()) }, .is_type_val = false },
.handle = self.handle,
};
}
const param_decl = pay.param;
if (isMetaType(self.handle.tree, param_decl.type_expr)) {
var bound_param_it = analyser.bound_type_params.iterator();
@@ -2662,6 +2811,11 @@ fn makeScopeInternal(context: ScopeContext, node_idx: Ast.Node.Index) error{OutO
);
defer context.popScope();
// NOTE: We count the param index ourselves
// as param_i stops counting; TODO: change this
var param_index: usize = 0;
var it = func.iterate(&tree);
while (ast.nextFnParam(&it)) |param| {
// Add parameter decls
@@ -2669,12 +2823,13 @@ fn makeScopeInternal(context: ScopeContext, node_idx: Ast.Node.Index) error{OutO
try scopes.items(.decls)[scope_index].put(
allocator,
tree.tokenSlice(name_token),
.{ .param_payload = .{ .param = param, .func = node_idx } },
.{ .param_payload = .{ .param = param, .param_idx = @intCast(u16, param_index), .func = node_idx } },
);
}
// Visit parameter types to pick up any error sets and enum
// completions
try makeScopeInternal(context, param.type_expr);
param_index += 1;
}
if (fn_tag == .fn_decl) blk: {
@@ -2895,6 +3050,8 @@ fn makeScopeInternal(context: ScopeContext, node_idx: Ast.Node.Index) error{OutO
.items = switch_case.ast.values,
},
});
try makeScopeInternal(context, switch_case.ast.target_expr);
} else {
try makeScopeInternal(context, switch_case.ast.target_expr);
}