Skip to content

Commit

Permalink
refactor self parameter detection (#1796)
Browse files Browse the repository at this point in the history
  • Loading branch information
Techatrix authored Feb 27, 2024
1 parent 9cd661a commit b04a268
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 81 deletions.
2 changes: 1 addition & 1 deletion src/ComptimeInterpreter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,7 @@ pub fn interpret(
.async_call_one_comma,
=> {
var params: [1]Ast.Node.Index = undefined;
const call_full = tree.fullCall(&params, node_idx) orelse unreachable;
const call_full = tree.fullCall(&params, node_idx).?;

var args = try std.ArrayListUnmanaged(Value).initCapacity(interpreter.allocator, call_full.ast.params.len);
defer args.deinit(interpreter.allocator);
Expand Down
112 changes: 56 additions & 56 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -306,55 +306,55 @@ pub fn isInstanceCall(
analyser: *Analyser,
call_handle: *DocumentStore.Handle,
call: Ast.full.Call,
func_handle: *DocumentStore.Handle,
func: Ast.full.FnProto,
func_ty: Type,
) error{OutOfMemory}!bool {
const tree = call_handle.tree;
return tree.tokens.items(.tag)[call.ast.lparen - 2] == .period and
try analyser.hasSelfParam(func_handle, func);
}

pub fn hasSelfParam(analyser: *Analyser, handle: *DocumentStore.Handle, func: Ast.full.FnProto) error{OutOfMemory}!bool {
const token_starts = handle.tree.tokens.items(.start);
const in_container = try innermostContainer(handle, token_starts[func.ast.fn_token]);
return analyser.firstParamIs(handle, func, in_container);
}
std.debug.assert(!func_ty.is_type_val);
if (call_handle.tree.nodes.items(.tag)[call.ast.fn_expr] != .field_access) return false;

pub fn isSelfFunction(analyser: *Analyser, func_type: Type, container: Type) error{OutOfMemory}!bool {
if (!func_type.isFunc()) return false;
const func_decl = try analyser.resolveFuncProtoOfCallable(func_type) orelse return false;
if (func_decl.is_type_val) return false;
const container_node = NodeWithHandle{ .node = call_handle.tree.nodes.items(.data)[call.ast.fn_expr].lhs, .handle = call_handle };

const fn_node_handle = func_decl.data.other; // this assumes that function types can only be Ast nodes
const fn_node = fn_node_handle.node;
const fn_handle = fn_node_handle.handle;
const container_ty = if (try analyser.resolveTypeOfNodeInternal(container_node)) |container_instance|
container_instance.typeOf(analyser)
else blk: {
const func_node = func_ty.data.other; // this assumes that function types can only be Ast nodes
const fn_token = func_node.handle.tree.nodes.items(.main_token)[func_node.node];
break :blk try innermostContainer(func_node.handle, func_node.handle.tree.tokens.items(.start)[fn_token]);
};

var buf: [1]Ast.Node.Index = undefined;
const func = fn_handle.tree.fullFnProto(&buf, fn_node).?;
std.debug.assert(container_ty.is_type_val);

// Non-decl prototypes cannot have a self parameter.
if (func.name_token == null) return false;
return analyser.firstParamIs(func_ty, container_ty);
}

return analyser.firstParamIs(fn_handle, func, container);
pub fn hasSelfParam(analyser: *Analyser, func_ty: Type) error{OutOfMemory}!bool {
const func_node = func_ty.data.other; // this assumes that function types can only be Ast nodes
const fn_token = func_node.handle.tree.nodes.items(.main_token)[func_node.node];
const in_container = try innermostContainer(func_node.handle, func_node.handle.tree.tokens.items(.start)[fn_token]);
std.debug.assert(in_container.is_type_val);
return analyser.firstParamIs(func_ty, in_container);
}

pub fn firstParamIs(
analyser: *Analyser,
handle: *DocumentStore.Handle,
func: Ast.full.FnProto,
expected: Type,
func_type: Type,
expected_type: Type,
) error{OutOfMemory}!bool {
const tree = handle.tree;
var it = func.iterate(&tree);
std.debug.assert(func_type.isFunc());
const func_handle = func_type.data.other;

var buffer: [1]Ast.Node.Index = undefined;
const func = func_handle.handle.tree.fullFnProto(&buffer, func_handle.node).?;

var it = func.iterate(&func_handle.handle.tree);
const param = ast.nextFnParam(&it) orelse return false;
if (param.anytype_ellipsis3) |token| {
if (tree.tokens.items(.tag)[token] == .keyword_anytype) return true;
if (func_handle.handle.tree.tokens.items(.tag)[token] == .keyword_anytype) return true;
}
if (param.type_expr == 0) return false;

const resolved_type = try analyser.resolveTypeOfNodeInternal(.{
.node = param.type_expr,
.handle = handle,
.handle = func_handle.handle,
}) orelse return false;
if (!resolved_type.is_type_val) return false;

Expand All @@ -366,7 +366,15 @@ pub fn firstParamIs(
else => resolved_type,
};

return deref_type.eql(expected);
const deref_expected_type = switch (expected_type.data) {
.pointer => |info| switch (info.size) {
.One => info.elem_ty.*,
.Many, .Slice, .C => return false,
},
else => expected_type,
};

return deref_type.eql(deref_expected_type);
}

pub fn getVariableSignature(
Expand Down Expand Up @@ -1271,30 +1279,30 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
.async_call_one_comma,
=> {
var buffer: [1]Ast.Node.Index = undefined;
const call = tree.fullCall(&buffer, node) orelse unreachable;
const call = tree.fullCall(&buffer, node).?;

const callee = .{ .node = call.ast.fn_expr, .handle = handle };
const ty = (try analyser.resolveTypeOfNodeInternal(callee)) orelse return null;
const decl = try analyser.resolveFuncProtoOfCallable(ty) orelse return null;
const ty = try analyser.resolveTypeOfNodeInternal(callee) orelse return null;
const func_ty = try analyser.resolveFuncProtoOfCallable(ty) orelse return null;
if (func_ty.is_type_val) return null;

if (decl.is_type_val) return null;
const func_node_handle = decl.data.other; // this assumes that function types can only be Ast nodes
const func_node_handle = func_ty.data.other; // this assumes that function types can only be Ast nodes
const func_node = func_node_handle.node;
const func_handle = func_node_handle.handle;
const func_tree = func_handle.tree;
var buf: [1]Ast.Node.Index = undefined;
const fn_proto = func_tree.fullFnProto(&buf, func_node) orelse return null;
const fn_proto = func_tree.fullFnProto(&buf, func_node).?;

var params = try std.ArrayListUnmanaged(Ast.full.FnProto.Param).initCapacity(analyser.arena.allocator(), fn_proto.ast.params.len);
defer params.deinit(analyser.arena.allocator());

var it = fn_proto.iterate(&func_tree);
var it = fn_proto.iterate(&func_handle.tree);
while (ast.nextFnParam(&it)) |param| {
try params.append(analyser.arena.allocator(), param);
}

const has_self_param = call.ast.params.len + 1 == params.items.len and
try analyser.isInstanceCall(handle, call, func_handle, fn_proto);
try analyser.isInstanceCall(handle, call, func_ty);

const parameters = params.items[@intFromBool(has_self_param)..];
const arguments = call.ast.params;
Expand Down Expand Up @@ -3700,12 +3708,10 @@ pub fn iterateSymbolsContainer(
if (instance_access) {
// allow declarations which evaluate to functions where
// the first parameter has the type of the container:
const alias_type = try analyser.resolveTypeOfNode(
NodeWithHandle{ .node = node, .handle = handle },
) orelse continue;
const alias_type = try analyser.resolveTypeOfNode(.{ .node = node, .handle = handle }) orelse continue;
const func_ty = try analyser.resolveFuncProtoOfCallable(alias_type) orelse continue;

const container_type = Type.typeVal(container_handle);
if (!try analyser.isSelfFunction(alias_type, container_type)) continue;
if (!try analyser.firstParamIs(func_ty, Type.typeVal(container_handle))) continue;
}
},
else => {},
Expand Down Expand Up @@ -4243,26 +4249,20 @@ pub fn resolveExpressionTypeFromAncestors(
const call = tree.fullCall(&buffer, ancestors[0]).?;
const arg_index = std.mem.indexOfScalar(Ast.Node.Index, call.ast.params, node) orelse return null;

const fn_type = (try analyser.resolveTypeOfNode(.{
.node = call.ast.fn_expr,
.handle = handle,
})) orelse return null;

const ty = try analyser.resolveTypeOfNode(.{ .node = call.ast.fn_expr, .handle = handle }) orelse return null;
const fn_type = try analyser.resolveFuncProtoOfCallable(ty) orelse return null;
if (fn_type.is_type_val) return null;

const fn_node_handle = switch (fn_type.data) {
.other => |n| n,
else => return null,
};
const fn_node_handle = fn_type.data.other; // this assumes that function types can only be Ast nodes
const fn_node = fn_node_handle.node;
const fn_handle = fn_node_handle.handle;
const fn_tree = fn_handle.tree;

var fn_buf: [1]Ast.Node.Index = undefined;
const fn_proto = fn_tree.fullFnProto(&fn_buf, fn_node) orelse return null;
const fn_proto = fn_tree.fullFnProto(&fn_buf, fn_node).?;

var param_iter = fn_proto.iterate(&fn_tree);
if (try analyser.isInstanceCall(handle, call, fn_handle, fn_proto)) {
if (try analyser.isInstanceCall(handle, call, fn_type)) {
_ = ast.nextFnParam(&param_iter);
}

Expand Down
9 changes: 7 additions & 2 deletions src/features/completions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,12 @@ fn nodeToCompletion(
const use_placeholders = server.config.enable_argument_placeholders;
const use_label_details = server.client_capabilities.label_details_support;

const skip_self_param = !(parent_is_type_val orelse true) and try analyser.hasSelfParam(handle, func);
const func_ty = Analyser.Type{
.data = .{ .other = .{ .node = node, .handle = handle } }, // this assumes that function types can only be Ast nodes
.is_type_val = true,
};

const skip_self_param = !(parent_is_type_val orelse true) and try analyser.hasSelfParam(func_ty);

const insert_text = blk: {
if (use_snippets and use_placeholders) {
Expand Down Expand Up @@ -1405,7 +1410,7 @@ fn collectFieldAccessContainerNodes(
const symbol_decl = try analyser.lookupSymbolGlobal(handle, first_symbol, loc.start) orelse continue;
const symbol_type = try symbol_decl.resolveType(analyser) orelse continue;
if (!symbol_type.is_type_val) { // then => instance_of_T
if (try analyser.hasSelfParam(fn_proto_handle, full_fn_proto)) break :blk 2;
if (try analyser.hasSelfParam(node_type)) break :blk 2;
}
break :blk 1; // is `T`, no SelfParam
};
Expand Down
20 changes: 8 additions & 12 deletions src/features/inlay_hints.zig
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,19 @@ const Builder = struct {
}
};

/// `call` is the function call
/// `ty` should be a function protototype
/// writes parameter hints into `builder.hints`
fn writeCallHint(builder: *Builder, call: Ast.full.Call, ty: Analyser.Type) !void {
fn writeCallHint(
builder: *Builder,
/// The function call.
call: Ast.full.Call,
) !void {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();

const handle = builder.handle;
const tree = handle.tree;

const ty = try builder.analyser.resolveTypeOfNode(.{ .node = call.ast.fn_expr, .handle = handle }) orelse return;
const fn_ty = try builder.analyser.resolveFuncProtoOfCallable(ty) orelse return;
const fn_node = fn_ty.data.other; // this assumes that function types can only be Ast nodes

Expand All @@ -116,7 +119,7 @@ fn writeCallHint(builder: *Builder, call: Ast.full.Call, ty: Analyser.Type) !voi
}

const has_self_param = call.ast.params.len + 1 == params.items.len and
try builder.analyser.isInstanceCall(handle, call, fn_node.handle, fn_proto);
try builder.analyser.isInstanceCall(handle, call, fn_ty);

const parameters = params.items[@intFromBool(has_self_param)..];
const arguments = call.ast.params;
Expand Down Expand Up @@ -302,14 +305,7 @@ fn writeCallNodeHint(builder: *Builder, call: Ast.full.Call) !void {
const node_tags = tree.nodes.items(.tag);

switch (node_tags[call.ast.fn_expr]) {
.identifier => {
const ty = try builder.analyser.resolveTypeOfNode(.{ .node = call.ast.fn_expr, .handle = handle }) orelse return;
try writeCallHint(builder, call, ty);
},
.field_access => {
const ty = try builder.analyser.resolveTypeOfNode(.{ .node = call.ast.fn_expr, .handle = handle }) orelse return;
try writeCallHint(builder, call, ty);
},
.identifier, .field_access => try writeCallHint(builder, call),
else => {
log.debug("cannot deduce fn expression with tag '{}'", .{node_tags[call.ast.fn_expr]});
},
Expand Down
18 changes: 9 additions & 9 deletions src/features/semantic_tokens.zig
Original file line number Diff line number Diff line change
Expand Up @@ -252,14 +252,9 @@ fn colorIdentifierBasedOnType(
if (type_node.isGenericFunc()) {
new_tok_mod.generic = true;
}
const has_self_param = switch (type_node.data) {
.other => |node_handle| blk: {
var buffer: [1]Ast.Node.Index = undefined;
const fn_proto = node_handle.handle.tree.fullFnProto(&buffer, node_handle.node).?;
break :blk try builder.analyser.hasSelfParam(node_handle.handle, fn_proto);
},
else => false,
};

const has_self_param = try builder.analyser.hasSelfParam(type_node);

try writeTokenMod(builder, target_tok, if (has_self_param) .method else .function, new_tok_mod);
} else {
var new_tok_mod = tok_mod;
Expand Down Expand Up @@ -418,9 +413,14 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
try writeToken(builder, fn_proto.lib_name, .string);
try writeToken(builder, fn_proto.ast.fn_token, .keyword);

const func_ty = Analyser.Type{
.data = .{ .other = .{ .node = node, .handle = handle } }, // this assumes that function types can only be Ast nodes
.is_type_val = true,
};

const func_name_tok_type: TokenType = if (Analyser.isTypeFunction(tree, fn_proto))
.type
else if (try builder.analyser.hasSelfParam(builder.handle, fn_proto))
else if (try builder.analyser.hasSelfParam(func_ty))
.method
else
.function;
Expand Down
2 changes: 1 addition & 1 deletion src/features/signature_help.zig
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn fnProtoToSignatureInfo(
const proto_comments = try Analyser.getDocComments(arena, tree, fn_node);

const arg_idx = if (skip_self_param) blk: {
const has_self_param = try analyser.hasSelfParam(fn_handle, proto);
const has_self_param = try analyser.hasSelfParam(func_type);
break :blk commas + @intFromBool(has_self_param);
} else commas;

Expand Down

0 comments on commit b04a268

Please sign in to comment.