Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add anytype resolution based on call references #1067

Merged
merged 6 commits into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 158 additions & 1 deletion src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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 = .{
Expand All @@ -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);
Expand Down Expand Up @@ -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 {
Expand All @@ -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.*) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -2662,19 +2811,25 @@ 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
if (param.name_token) |name_token| {
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: {
Expand Down Expand Up @@ -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);
}
Expand Down
Loading