diff --git a/src/analysis.zig b/src/analysis.zig index c083343be..69160d1f0 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -811,28 +811,37 @@ fn findReturnStatement(tree: Ast, body: Ast.Node.Index) ?Ast.Node.Index { return findReturnStatementInternal(tree, body, &already_found); } -pub fn resolveReturnType(analyser: *Analyser, fn_decl: Ast.full.FnProto, handle: *DocumentStore.Handle, fn_body: ?Ast.Node.Index) error{OutOfMemory}!?Type { - const tree = handle.tree; - if (isTypeFunction(tree, fn_decl) and fn_body != null) { +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; + + var buf: [1]Ast.Node.Index = undefined; + const fn_proto = tree.fullFnProto(&buf, func_node).?; + const has_body = tree.nodes.items(.tag)[func_node] == .fn_decl; + + if (isTypeFunction(tree, fn_proto) and has_body) { + const body = tree.nodes.items(.data)[func_node].rhs; // If this is a type function and it only contains a single return statement that returns // a container declaration, we will return that declaration. - const ret = findReturnStatement(tree, fn_body.?) orelse return null; + const ret = findReturnStatement(tree, body) orelse return null; const data = tree.nodes.items(.data)[ret]; if (data.lhs != 0) { - return try analyser.resolveTypeOfNodeInternal(.{ .node = data.lhs, .handle = handle }); + return try analyser.resolveTypeOfNodeInternal(.{ .node = data.lhs, .handle = func_node_handle.handle }); } return null; } - if (fn_decl.ast.return_type == 0) return null; - const return_type = fn_decl.ast.return_type; - const ret: NodeWithHandle = .{ .node = return_type, .handle = handle }; + if (fn_proto.ast.return_type == 0) return null; + const return_type = fn_proto.ast.return_type; + const ret: NodeWithHandle = .{ .node = return_type, .handle = func_node_handle.handle }; const child_type = (try analyser.resolveTypeOfNodeInternal(ret)) orelse return null; if (!child_type.is_type_val) return null; - if (ast.hasInferredError(tree, fn_decl)) { + if (ast.hasInferredError(tree, fn_proto)) { const child_type_ptr = try analyser.arena.allocator().create(Type); child_type_ptr.* = child_type; return Type{ @@ -1542,11 +1551,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e }, argument_type); } - const has_body = func_tree.nodes.items(.tag)[func_node] == .fn_decl; - const body = func_tree.nodes.items(.data)[func_node].rhs; - if (try analyser.resolveReturnType(fn_proto, func_handle, if (has_body) body else null)) |ret| { - return ret; - } + return try analyser.resolveReturnType(func_ty); }, .container_field, .container_field_init, @@ -2646,18 +2651,27 @@ pub const Type = struct { } } - fn isContainerKind(self: Type, container_kind_tok: std.zig.Token.Tag) bool { + pub fn isContainerType(self: Type) bool { + return self.data == .container; + } + + 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 { @@ -2714,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) { @@ -3231,22 +3256,9 @@ pub fn getFieldAccessType( // Can't call a function type, we need a function type instance. if (current_type.?.is_type_val) return null; - // this assumes that function types can only be Ast nodes - const current_type_node_handle = ty.data.other; - const current_type_node = current_type_node_handle.node; - const current_type_handle = current_type_node_handle.handle; - - const cur_tree = current_type_handle.tree; - var buf: [1]Ast.Node.Index = undefined; - const func = cur_tree.fullFnProto(&buf, current_type_node).?; - // Check if the function has a body and if so, pass it - // so the type can be resolved if it's a generic function returning - // an anonymous struct - const has_body = cur_tree.nodes.items(.tag)[current_type_node] == .fn_decl; - const body = cur_tree.nodes.items(.data)[current_type_node].rhs; // TODO Actually bind params here when calling functions instead of just skipping args. - current_type = try analyser.resolveReturnType(func, current_type_handle, if (has_body) body else null) orelse return null; + current_type = try analyser.resolveReturnType(ty) orelse return null; if (do_unwrap_error_payload) { if (try analyser.resolveUnwrapErrorUnionType(current_type.?, .payload)) |unwrapped| current_type = unwrapped; @@ -4470,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; @@ -4480,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, @@ -4518,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; @@ -4682,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; @@ -4689,27 +4734,16 @@ pub fn resolveExpressionTypeFromAncestors( if (fn_type.is_type_val) 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).?; - - var param_iter = fn_proto.iterate(&fn_tree); - if (try analyser.isInstanceCall(handle, call, fn_type)) { - _ = ast.nextFnParam(¶m_iter); - } + const param_decl: Declaration.Param = .{ + .param_index = @truncate(arg_index + @intFromBool(try analyser.hasSelfParam(fn_type))), + .func = fn_node_handle.node, + }; + const param = param_decl.get(fn_node_handle.handle.tree) orelse return null; - var param_index: usize = 0; - while (ast.nextFnParam(¶m_iter)) |param| : (param_index += 1) { - if (param_index == arg_index) { - return try analyser.resolveTypeOfNode(.{ - .node = param.type_expr, - .handle = fn_handle, - }); - } - } + return try analyser.resolveTypeOfNode(.{ + .node = param.type_expr, + .handle = fn_node_handle.handle, + }); }, .assign => { if (node == datas[ancestors[0]].rhs) { @@ -4785,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 } diff --git a/src/features/completions.zig b/src/features/completions.zig index 662a23d9d..2c10c21b6 100644 --- a/src/features/completions.zig +++ b/src/features/completions.zig @@ -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; }, @@ -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); } } @@ -1467,30 +1507,25 @@ fn collectVarAccessContainerNodes( const symbol_decl = try analyser.lookupSymbolGlobal(handle, handle.tree.source[loc.start..loc.end], loc.end) orelse return; const result = try symbol_decl.resolveType(analyser) orelse return; const type_expr = try analyser.resolveDerefType(result) orelse result; - if (type_expr.isFunc()) { - const fn_proto_node_handle = type_expr.data.other; // this assumes that function types can only be Ast nodes - const fn_proto_node = fn_proto_node_handle.node; - const fn_proto_handle = fn_proto_node_handle.handle; - if (dot_context.likely == .enum_comparison or dot_context.need_ret_type) { // => we need f()'s return type - var buf: [1]Ast.Node.Index = undefined; - const full_fn_proto = fn_proto_handle.tree.fullFnProto(&buf, fn_proto_node).?; - const has_body = fn_proto_handle.tree.nodes.items(.tag)[fn_proto_node] == .fn_decl; - const body = fn_proto_handle.tree.nodes.items(.data)[fn_proto_node].rhs; - var node_type = try analyser.resolveReturnType(full_fn_proto, fn_proto_handle, if (has_body) body else null) orelse return; - if (try analyser.resolveUnwrapErrorUnionType(node_type, .payload)) |unwrapped| node_type = unwrapped; - try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles); - return; - } - const fn_param_decl = Analyser.Declaration{ .function_parameter = .{ - .func = fn_proto_node, - .param_index = @intCast(dot_context.fn_arg_index), - } }; - const fn_param_decl_with_handle = Analyser.DeclWithHandle{ .decl = fn_param_decl, .handle = fn_proto_handle }; - const param_type = try fn_param_decl_with_handle.resolveType(analyser) orelse return; - try types_with_handles.append(arena, param_type); + if (!type_expr.isFunc()) { + try type_expr.getAllTypesWithHandlesArrayList(arena, types_with_handles); return; } - try type_expr.getAllTypesWithHandlesArrayList(arena, types_with_handles); + + if (dot_context.likely == .enum_comparison or dot_context.need_ret_type) { // => we need f()'s return type + var node_type = try analyser.resolveReturnType(type_expr) orelse return; + if (try analyser.resolveUnwrapErrorUnionType(node_type, .payload)) |unwrapped| node_type = unwrapped; + try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles); + return; + } + const func_node_handle = type_expr.data.other; // this assumes that function types can only be Ast nodes + const fn_param_decl: Analyser.Declaration = .{ .function_parameter = .{ + .func = func_node_handle.node, + .param_index = @intCast(dot_context.fn_arg_index), + } }; + const fn_param_decl_with_handle = Analyser.DeclWithHandle{ .decl = fn_param_decl, .handle = func_node_handle.handle }; + const param_type = try fn_param_decl_with_handle.resolveType(analyser) orelse return; + try types_with_handles.append(arena, param_type); } fn collectFieldAccessContainerNodes( @@ -1528,50 +1563,47 @@ fn collectFieldAccessContainerNodes( if (dot_context.likely == .enum_assignment or dot_context.likely == .struct_field) { if (try analyser.resolveOptionalUnwrap(node_type)) |unwrapped| node_type = unwrapped; } - if (node_type.isFunc()) { - const fn_proto_node_handle = node_type.data.other; // this assumes that function types can only be Ast nodes - const fn_proto_node = fn_proto_node_handle.node; - const fn_proto_handle = fn_proto_node_handle.handle; - var buf: [1]Ast.Node.Index = undefined; - const full_fn_proto = fn_proto_handle.tree.fullFnProto(&buf, fn_proto_node).?; - if (dot_context.need_ret_type) { // => we need f()'s return type - const has_body = fn_proto_handle.tree.nodes.items(.tag)[fn_proto_node] == .fn_decl; - const body = fn_proto_handle.tree.nodes.items(.data)[fn_proto_node].rhs; - node_type = try analyser.resolveReturnType(full_fn_proto, fn_proto_handle, if (has_body) body else null) orelse continue; - if (try analyser.resolveUnwrapErrorUnionType(node_type, .payload)) |unwrapped| node_type = unwrapped; - try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles); - continue; - } - var maybe_fn_param: ?Ast.full.FnProto.Param = undefined; - var fn_param_iter = full_fn_proto.iterate(&fn_proto_handle.tree); - // don't have the luxury of referencing an `Ast.full.Call` - // check if the first symbol is a `T` or an instance_of_T - const additional_index: usize = blk: { - // `loc` points to offsets within `handle`, not `node_type.decl.handle` - const field_access_slice = handle.tree.source[loc.start..loc.end]; - if (field_access_slice[0] == '@') break :blk 1; // assume `@import("..").some.Other{.}` - var symbol_iter = std.mem.tokenizeScalar(u8, field_access_slice, '.'); - const first_symbol = symbol_iter.next() orelse continue; - 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(node_type)) break :blk 2; - } - break :blk 1; // is `T`, no SelfParam - }; - for (dot_context.fn_arg_index + additional_index) |_| maybe_fn_param = ast.nextFnParam(&fn_param_iter); - const param = maybe_fn_param orelse continue; - if (param.type_expr == 0) continue; - const param_rcts = try collectContainerNodes( - builder, - fn_proto_handle, - offsets.nodeToLoc(fn_proto_handle.tree, param.type_expr).end, - dot_context, - ); - for (param_rcts) |prct| try types_with_handles.append(arena, prct); + if (!node_type.isFunc()) { + try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles); continue; } - try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles); + + if (dot_context.need_ret_type) { // => we need f()'s return type + node_type = try analyser.resolveReturnType(node_type) orelse continue; + if (try analyser.resolveUnwrapErrorUnionType(node_type, .payload)) |unwrapped| node_type = unwrapped; + try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles); + continue; + } + // don't have the luxury of referencing an `Ast.full.Call` + // check if the first symbol is a `T` or an instance_of_T + const additional_index: usize = blk: { + // `loc` points to offsets within `handle`, not `node_type.decl.handle` + const field_access_slice = handle.tree.source[loc.start..loc.end]; + if (field_access_slice[0] == '@') break :blk 0; // assume `@import("..").some.Other{.}` + var symbol_iter = std.mem.tokenizeScalar(u8, field_access_slice, '.'); + const first_symbol = symbol_iter.next() orelse continue; + 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(node_type)) break :blk 1; + } + break :blk 0; // is `T`, no SelfParam + }; + const fn_node_handle = node_type.data.other; // this assumes that function types can only be Ast nodes + const param_decl: Analyser.Declaration.Param = .{ + .param_index = @truncate(dot_context.fn_arg_index + additional_index), + .func = fn_node_handle.node, + }; + const param = param_decl.get(fn_node_handle.handle.tree) orelse continue; + + if (param.type_expr == 0) continue; + const param_rcts = try collectContainerNodes( + builder, + fn_node_handle.handle, + offsets.nodeToLoc(fn_node_handle.handle.tree, param.type_expr).end, + dot_context, + ); + for (param_rcts) |prct| try types_with_handles.append(arena, prct); } } diff --git a/src/features/references.zig b/src/features/references.zig index 0aaf9f413..ac6672e41 100644 --- a/src/features/references.zig +++ b/src/features/references.zig @@ -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); diff --git a/src/features/semantic_tokens.zig b/src/features/semantic_tokens.zig index 382d4871d..78dbc881d 100644 --- a/src/features/semantic_tokens.zig +++ b/src/features/semantic_tokens.zig @@ -608,7 +608,12 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v const call = tree.fullCall(¶ms, 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); }, diff --git a/src/features/signature_help.zig b/src/features/signature_help.zig index e0970c197..8c832fdaa 100644 --- a/src/features/signature_help.zig +++ b/src/features/signature_help.zig @@ -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( diff --git a/tests/lsp_features/completion.zig b/tests/lsp_features/completion.zig index 1c9502871..03cbeab0f 100644 --- a/tests/lsp_features/completion.zig +++ b/tests/lsp_features/completion.zig @@ -1540,6 +1540,53 @@ test "enum" { }); } +test "decl literal" { + try testCompletion( + \\const S = struct { + \\ field: u32, + \\ + \\ pub const foo: error{OutOfMemory}!S = .{}; + \\ var bar: @This() = .{}; + \\ var baz: u32 = .{}; + \\ + \\ fn init() ?S {} + \\ fn func() void {} + \\}; + \\const s: S = .; + , &.{ + .{ .label = "field", .kind = .Field, .detail = "u32" }, + .{ .label = "foo", .kind = .Constant }, + .{ .label = "bar", .kind = .Struct }, + .{ .label = "init", .kind = .Function, .detail = "fn () ?S" }, + }); +} + +test "decl literal function" { + try testCompletion( + \\const Inner = struct { + \\ fn init() Inner {} + \\}; + \\const Outer = struct { + \\ inner: Inner, + \\}; + \\const foo: Outer = .{ + \\ .inner = .init(), + \\}; + , &.{ + .{ .label = "init", .kind = .Function, .detail = "fn () Inner" }, + }); + try testCompletion( + \\fn Empty() type { + \\ return struct { + \\ fn init() @This() {} + \\ }; + \\} + \\const foo: Empty() = .init(); + , &.{ + .{ .label = "init", .kind = .Function, .detail = "fn () @This()" }, + }); +} + test "enum literal" { try testCompletion( \\const literal = .foo; @@ -3515,6 +3562,20 @@ test "insert replace behaviour - function alias" { }); } +test "insert replace behaviour - decl literal function" { + try testCompletionTextEdit(.{ + .source = + \\const S = struct { + \\ fn init() S {} + \\}; + \\const foo: S = .; + , + .label = "init", + .expected_insert_line = "const foo: S = .init;", + .expected_replace_line = "const foo: S = .init;", + }); +} + test "insert replace behaviour - struct literal" { try testCompletionTextEdit(.{ .source = diff --git a/tests/lsp_features/hover.zig b/tests/lsp_features/hover.zig index 8136e367a..bf5836826 100644 --- a/tests/lsp_features/hover.zig +++ b/tests/lsp_features/hover.zig @@ -417,6 +417,78 @@ test "struct" { ); } +test "decl literal" { + try testHover( + \\const S = struct { + \\ const foo: S = .{}; + \\}; + \\const s: S = .foo; + , + \\```zig + \\const foo: S = .{} + \\``` + \\```zig + \\(S) + \\``` + \\ + \\Go to [S](file:///test.zig#L1) + ); + try testHover( + \\const S = struct { + \\ bar: u32, + \\ const foo: S = .{}; + \\}; + \\const s: S = .bar; + , ""); +} + +test "decl literal function" { + try testHover( + \\const S = struct { + \\ fn foo() S {} + \\}; + \\const s: S = .foo; + , + \\```zig + \\fn foo() S + \\``` + \\ + \\Go to [S](file:///test.zig#L1) + ); + + try testHover( + \\const S = struct { + \\ fn foo() !S {} + \\}; + \\test { + \\ const s: S = try .foo(); + \\} + , + \\```zig + \\fn foo() !S + \\``` + \\ + \\Go to [S](file:///test.zig#L1) + ); + try testHover( + \\const Inner = struct { + \\ fn init() Inner {} + \\}; + \\const Outer = struct { + \\ inner: Inner, + \\}; + \\const foo: Outer = .{ + \\ .inner = .init(), + \\}; + , + \\```zig + \\fn init() Inner + \\``` + \\ + \\Go to [Inner](file:///test.zig#L1) + ); +} + test "enum" { try testHover( \\const MyEnum = enum { @@ -1365,6 +1437,7 @@ fn testHoverWithOptions( }; const response: types.Hover = try ctx.server.sendRequestSync(ctx.arena.allocator(), "textDocument/hover", params) orelse { + if (expected.len == 0) return; std.debug.print("Server returned `null` as the result\n", .{}); return error.InvalidResponse; }; diff --git a/tests/lsp_features/semantic_tokens.zig b/tests/lsp_features/semantic_tokens.zig index 4db4e8222..9270d349d 100644 --- a/tests/lsp_features/semantic_tokens.zig +++ b/tests/lsp_features/semantic_tokens.zig @@ -608,6 +608,30 @@ test "enum literal" { }); } +test "decl literal" { + try testSemanticTokens( + \\const S = struct { + \\ fn foo() S {} + \\}; + \\const foo: S = .foo(); + , &.{ + .{ "const", .keyword, .{} }, + .{ "S", .namespace, .{ .declaration = true } }, + .{ "=", .operator, .{} }, + .{ "struct", .keyword, .{} }, + + .{ "fn", .keyword, .{} }, + .{ "foo", .function, .{ .declaration = true } }, + .{ "S", .namespace, .{} }, + + .{ "const", .keyword, .{} }, + .{ "foo", .variable, .{ .declaration = true } }, + .{ "S", .namespace, .{} }, + .{ "=", .operator, .{} }, + .{ "foo", .function, .{} }, + }); +} + test "error literal" { try testSemanticTokens( \\var alpha = error.OutOfMemory; diff --git a/tests/lsp_features/signature_help.zig b/tests/lsp_features/signature_help.zig index f53aabf5c..dc4967c41 100644 --- a/tests/lsp_features/signature_help.zig +++ b/tests/lsp_features/signature_help.zig @@ -235,6 +235,17 @@ test "nested function call" { , "fn bar(c: bool) bool", 0); } +test "decl literal" { + try testSignatureHelp( + \\const S = struct { + \\ fn foo(a: u32, b: u32) S {} + \\}; + \\test { + \\ const s: S = .foo(); + \\} + , "fn foo(a: u32, b: u32) S", 0); +} + test "builtin" { try testSignatureHelp( \\test {