Skip to content

Commit

Permalink
add support for decl literals
Browse files Browse the repository at this point in the history
fixes #2027
  • Loading branch information
Techatrix committed Dec 22, 2024
1 parent 015f619 commit af3a97e
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 46 deletions.
83 changes: 70 additions & 13 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,8 @@ fn findReturnStatement(tree: Ast, body: Ast.Node.Index) ?Ast.Node.Index {
return findReturnStatementInternal(tree, body, &already_found);
}

pub fn resolveReturnType(analyser: *Analyser, func_type: Type) error{OutOfMemory}!?Type {
pub fn resolveReturnType(analyser: *Analyser, func_type_param: Type) error{OutOfMemory}!?Type {
const func_type = try analyser.resolveFuncProtoOfCallable(func_type_param) orelse return null;
const func_node_handle = func_type.data.other; // this assumes that function types can only be Ast nodes
const tree = func_node_handle.handle.tree;
const func_node = func_node_handle.node;
Expand Down Expand Up @@ -2654,18 +2655,23 @@ pub const Type = struct {
return self.data == .container;
}

fn isContainerKind(self: Type, container_kind_tok: std.zig.Token.Tag) bool {
fn getContainerKind(self: Type) ?std.zig.Token.Tag {
const scope_handle = switch (self.data) {
.container => |s| s,
else => return false,
else => return null,
};
if (scope_handle.scope == .root) return .keyword_struct;

const node = scope_handle.toNode();

const tree = scope_handle.handle.tree;
const main_tokens = tree.nodes.items(.main_token);
const tags = tree.tokens.items(.tag);
return tags[main_tokens[node]] == container_kind_tok;
return tags[main_tokens[node]];
}

fn isContainerKind(self: Type, container_kind_tok: std.zig.Token.Tag) bool {
return self.getContainerKind() == container_kind_tok;
}

pub fn isStructType(self: Type) bool {
Expand Down Expand Up @@ -2722,6 +2728,17 @@ pub const Type = struct {
}
}

pub fn resolveDeclLiteralResultType(ty: Type) Type {
var result_type = ty;
while (true) {
result_type = switch (result_type.data) {
.optional => |child_ty| child_ty.*,
.error_union => |info| info.payload.*,
else => return result_type,
};
}
}

pub fn isTypeFunc(self: Type) bool {
var buf: [1]Ast.Node.Index = undefined;
return switch (self.data) {
Expand Down Expand Up @@ -4465,7 +4482,7 @@ pub fn lookupSymbolFieldInit(
analyser: *Analyser,
handle: *DocumentStore.Handle,
field_name: []const u8,
nodes: []Ast.Node.Index,
nodes: []const Ast.Node.Index,
) error{OutOfMemory}!?DeclWithHandle {
if (nodes.len == 0) return null;

Expand All @@ -4475,29 +4492,53 @@ pub fn lookupSymbolFieldInit(
nodes[1..],
)) orelse return null;

const is_struct_init = switch (handle.tree.nodes.items(.tag)[nodes[0]]) {
.struct_init_one,
.struct_init_one_comma,
.struct_init_dot_two,
.struct_init_dot_two_comma,
.struct_init_dot,
.struct_init_dot_comma,
.struct_init,
.struct_init_comma,
=> true,
else => false,
};

if (try analyser.resolveUnwrapErrorUnionType(container_type, .payload)) |unwrapped|
container_type = unwrapped;

if (try analyser.resolveOptionalUnwrap(container_type)) |unwrapped|
container_type = unwrapped;

const container_scope_handle = switch (container_type.data) {
const container_scope = switch (container_type.data) {
.container => |s| s,
else => return null,
};
if (is_struct_init) {
return try analyser.lookupSymbolContainer(container_scope, field_name, .field);
}

return analyser.lookupSymbolContainer(
container_scope_handle,
field_name,
.field,
);
// Assume we are doing decl literals
switch (container_type.getContainerKind() orelse return null) {
.keyword_struct => {
const decl = try analyser.lookupSymbolContainer(container_scope, field_name, .other) orelse return null;
var resolved_type = try decl.resolveType(analyser) orelse return null;
resolved_type = try analyser.resolveReturnType(resolved_type) orelse resolved_type;
resolved_type = resolved_type.resolveDeclLiteralResultType();
if (resolved_type.eql(container_type) or resolved_type.eql(container_type.typeOf(analyser))) return decl;
return null;
},
.keyword_enum, .keyword_union => return try analyser.lookupSymbolContainer(container_scope, field_name, .field),
else => return null,
}
}

pub fn resolveExpressionType(
analyser: *Analyser,
handle: *DocumentStore.Handle,
node: Ast.Node.Index,
ancestors: []Ast.Node.Index,
ancestors: []const Ast.Node.Index,
) error{OutOfMemory}!?Type {
return (try analyser.resolveExpressionTypeFromAncestors(
handle,
Expand All @@ -4513,7 +4554,7 @@ pub fn resolveExpressionTypeFromAncestors(
analyser: *Analyser,
handle: *DocumentStore.Handle,
node: Ast.Node.Index,
ancestors: []Ast.Node.Index,
ancestors: []const Ast.Node.Index,
) error{OutOfMemory}!?Type {
if (ancestors.len == 0) return null;

Expand Down Expand Up @@ -4677,6 +4718,15 @@ pub fn resolveExpressionTypeFromAncestors(
=> {
var buffer: [1]Ast.Node.Index = undefined;
const call = tree.fullCall(&buffer, ancestors[0]).?;

if (call.ast.fn_expr == node) {
return try analyser.resolveExpressionType(
handle,
ancestors[0],
ancestors[1..],
);
}

const arg_index = std.mem.indexOfScalar(Ast.Node.Index, call.ast.params, node) orelse return null;

const ty = try analyser.resolveTypeOfNode(.{ .node = call.ast.fn_expr, .handle = handle }) orelse return null;
Expand Down Expand Up @@ -4769,6 +4819,13 @@ pub fn resolveExpressionTypeFromAncestors(
ancestors[index + 1 ..],
);
},
.@"try" => {
return try analyser.resolveExpressionType(
handle,
ancestors[0],
ancestors[1..],
);
},

else => {}, // TODO: Implement more expressions; better safe than sorry
}
Expand Down
96 changes: 68 additions & 28 deletions src/features/completions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1073,7 +1073,7 @@ fn getEnumLiteralContext(
switch (token_tags[token_index]) {
.equal => {
token_index -= 1;
if ((token_tags[token_index] == .r_paren)) return null; // `..) = .`, ie lhs is a fn call
dot_context.need_ret_type = token_tags[token_index] == .r_paren;
dot_context.likely = .enum_assignment;
dot_context.identifier_token_index = token_index;
},
Expand Down Expand Up @@ -1270,39 +1270,79 @@ fn collectContainerFields(
container: Analyser.Type,
omit_members: std.BufSet,
) error{OutOfMemory}!void {
const use_snippets = builder.server.config.enable_snippets and builder.server.client_capabilities.supports_snippets;
const scope_handle = switch (container.data) {
.container => |s| s,
else => return,
};
const node = scope_handle.toNode();
const handle = scope_handle.handle;
var buffer: [2]Ast.Node.Index = undefined;
const container_decl = Ast.fullContainerDecl(handle.tree, &buffer, node) orelse return;
for (container_decl.ast.members) |member| {
const field = handle.tree.fullContainerField(member) orelse continue;
const name = handle.tree.tokenSlice(field.ast.main_token);

const document_scope = try scope_handle.handle.getDocumentScope();
const scope_decls = document_scope.getScopeDeclarationsConst(scope_handle.scope);

const use_snippets = builder.server.config.enable_snippets and builder.server.client_capabilities.supports_snippets;
for (scope_decls) |decl_index| {
const decl = document_scope.declarations.get(@intFromEnum(decl_index));
if (decl != .ast_node) continue;
const decl_handle: Analyser.DeclWithHandle = .{ .decl = decl, .handle = scope_handle.handle };
const tree = scope_handle.handle.tree;

const name = offsets.tokenToSlice(tree, decl.nameToken(tree));
if (omit_members.contains(name)) continue;
if (likely != .struct_field and likely != .enum_comparison and likely != .switch_case and !field.ast.tuple_like) {
try builder.completions.append(builder.arena, .{
.label = name,
.kind = if (field.ast.tuple_like) .EnumMember else .Field,
.detail = Analyser.getContainerFieldSignature(handle.tree, field),
.insertText = if (use_snippets)
try std.fmt.allocPrint(builder.arena, "{{ .{s} = $1 }}$0", .{name})

const completion_item: types.CompletionItem = switch (tree.nodes.items(.tag)[decl.ast_node]) {
.container_field_init,
.container_field_align,
.container_field,
=> blk: {
const field = tree.fullContainerField(decl.ast_node).?;

const insert_text = if (likely != .struct_field and likely != .enum_comparison and likely != .switch_case and !field.ast.tuple_like)
if (use_snippets)
try std.fmt.allocPrint(builder.arena, "{{ .{s} = $1 }}$0", .{name})
else
try std.fmt.allocPrint(builder.arena, "{{ .{s} = ", .{name})
else if (!use_snippets or field.ast.tuple_like or likely == .enum_comparison or likely == .switch_case)
name
else
try std.fmt.allocPrint(builder.arena, "{{ .{s} = ", .{name}),
.insertTextFormat = if (use_snippets) .Snippet else .PlainText,
});
} else try builder.completions.append(builder.arena, .{
.label = name,
.kind = if (field.ast.tuple_like) .EnumMember else .Field,
.detail = Analyser.getContainerFieldSignature(handle.tree, field),
.insertText = if (!use_snippets or field.ast.tuple_like or likely == .enum_comparison or likely == .switch_case)
name
else
try std.fmt.allocPrint(builder.arena, "{s} = ", .{name}),
});
try std.fmt.allocPrint(builder.arena, "{s} = ", .{name});

break :blk .{
.label = name,
.kind = if (field.ast.tuple_like) .EnumMember else .Field,
.detail = Analyser.getContainerFieldSignature(tree, field),
.insertTextFormat = if (use_snippets) .Snippet else .PlainText,
.insertText = insert_text,
};
},
.global_var_decl,
.local_var_decl,
.simple_var_decl,
.aligned_var_decl,
=> {
if (likely != .enum_assignment) continue;
// decl literal
var expected_ty = try decl_handle.resolveType(builder.analyser) orelse continue;
expected_ty = expected_ty.typeOf(builder.analyser).resolveDeclLiteralResultType();
if (!expected_ty.eql(container)) continue;
try declToCompletion(builder, decl_handle, .{ .parent_container_ty = container });
continue;
},
.fn_proto,
.fn_proto_multi,
.fn_proto_one,
.fn_proto_simple,
.fn_decl,
=> blk: {
if (likely != .enum_assignment) continue;
// decl literal
const resolved_ty = try decl_handle.resolveType(builder.analyser) orelse continue;
var expected_ty = try builder.analyser.resolveReturnType(resolved_ty) orelse continue;
expected_ty = expected_ty.resolveDeclLiteralResultType();
if (!expected_ty.eql(container) and !expected_ty.typeOf(builder.analyser).eql(container)) continue;
break :blk try functionTypeCompletion(builder, name, container, resolved_ty) orelse continue;
},
else => continue,
};
try builder.completions.append(builder.arena, completion_item);
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/features/references.zig
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,7 @@ const Builder = struct {
const name_loc = offsets.tokenToLoc(tree, name_token);
const name = offsets.locToSlice(tree.source, name_loc);

var nodes = [_]Ast.Node.Index{datas[node].lhs};
const lookup = try builder.analyser.lookupSymbolFieldInit(handle, name, &nodes) orelse continue;
const lookup = try builder.analyser.lookupSymbolFieldInit(handle, name, &.{node}) orelse continue;

if (builder.decl_handle.eql(lookup)) {
try builder.add(handle, name_token);
Expand Down
7 changes: 6 additions & 1 deletion src/features/semantic_tokens.zig
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,12 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
const call = tree.fullCall(&params, node).?;

try writeToken(builder, call.async_token, .keyword);
try writeNodeTokens(builder, call.ast.fn_expr);
if (node_tags[call.ast.fn_expr] == .enum_literal) {
// TODO actually try to resolve the decl literal
try writeToken(builder, main_tokens[call.ast.fn_expr], .function);
} else {
try writeNodeTokens(builder, call.ast.fn_expr);
}

for (call.ast.params) |param| try writeNodeTokens(builder, param);
},
Expand Down
16 changes: 14 additions & 2 deletions src/features/signature_help.zig
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,21 @@ pub fn getSignatureInfo(
continue;
}

const loc = offsets.tokensToLoc(tree, expr_first_token, expr_last_token);
var loc = offsets.tokensToLoc(tree, expr_first_token, expr_last_token);

var ty = try analyser.getFieldAccessType(handle, loc.start, loc) orelse continue;
var ty = switch (tree.tokens.items(.tag)[expr_first_token]) {
.period => blk: { // decl literal
loc.start += 1;
const decl = try analyser.getSymbolEnumLiteral(
arena,
handle,
loc.start,
offsets.locToSlice(tree.source, loc),
) orelse continue;
break :blk try decl.resolveType(analyser) orelse continue;
},
else => try analyser.getFieldAccessType(handle, loc.start, loc) orelse continue,
};

if (try analyser.resolveFuncProtoOfCallable(ty)) |func_type| {
return try fnProtoToSignatureInfo(
Expand Down
Loading

0 comments on commit af3a97e

Please sign in to comment.