mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 19:00:11 +01:00
Instead of always printing types as canonical, attempt to find a sugared type where possible, and include that type in the diagnostic. We can only do this when given the instruction whose type is being printed (`TypeOfInstId`) rather than the canonical type ID. Initial support here is intentionally minimal: just looking through calls to the callee's declared return type, and looking through pointer dereferences and corresponding pointer types, to build out the initial infrastructure. More cases can be added later; this degrades gracefully to using the canonical type if a better type can't be found. Assisted-by: Claude Opus 5 via Antigravity
193 lines
7.3 KiB
C++
193 lines
7.3 KiB
C++
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
// Exceptions. See /LICENSE for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "common/raw_string_ostream.h"
|
|
#include "toolchain/language_server/handle.h"
|
|
#include "toolchain/language_server/position.h"
|
|
#include "toolchain/language_server/sem_ir_index.h"
|
|
#include "toolchain/sem_ir/file.h"
|
|
#include "toolchain/sem_ir/stringify.h"
|
|
#include "toolchain/sem_ir/typed_insts.h"
|
|
|
|
namespace Carbon::LanguageServer {
|
|
|
|
// Returns the type of `inst_id` rendered as Carbon source, or an empty string
|
|
// if it has no type. Instructions that aren't values, such as declarations of
|
|
// namespaces, have no type to show.
|
|
//
|
|
// TODO: `StringifyTypeOfInst` renders some types as placeholders such as
|
|
// `<type of F>` for a function and `<pattern for i32>` for a binding pattern,
|
|
// which is unhelpful as hover text. Show the signature for a function, and the
|
|
// bound type rather than the pattern type for a binding.
|
|
static auto StringifyTypeForHover(const SemIR::File& sem_ir,
|
|
SemIR::InstId inst_id) -> std::string {
|
|
if (!sem_ir.insts().Get(inst_id).type_id().has_value()) {
|
|
return "";
|
|
}
|
|
return SemIR::StringifyTypeOfInst(sem_ir, inst_id);
|
|
}
|
|
|
|
// Given a position-based query, returns the corresponding position information.
|
|
// If the request is invalid or there is no instruction at that position,
|
|
// produces a response to the query: an InvalidParams error or a
|
|
// default-constructed reply, as appropriate.
|
|
//
|
|
// TODO: If a default-constructed response is not the correct way to handle an
|
|
// invalid location, we will need to extend this function to accept a fallback
|
|
// value. For now, it's right for all the queries we support.
|
|
template <typename ResponseType>
|
|
static auto FindInstAtPositionOrFail(
|
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
|
llvm::function_ref<auto(llvm::Expected<ResponseType>)->void> on_done)
|
|
-> PositionInfo {
|
|
auto* file = context.LookupFile(params.textDocument.uri.file());
|
|
if (!file) {
|
|
on_done(llvm::make_error<clang::clangd::LSPError>(
|
|
llvm::formatv("Unknown textDocument `{0}`",
|
|
params.textDocument.uri.file()),
|
|
clang::clangd::ErrorCode::InvalidParams));
|
|
return {};
|
|
}
|
|
|
|
auto info = FindPositionInfo(*file, params.position);
|
|
if (!info.has_inst()) {
|
|
on_done(ResponseType());
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
// Implements `textDocument/hover`:
|
|
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover
|
|
auto HandleHover(
|
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
|
llvm::function_ref<auto(llvm::Expected<clang::clangd::Hover>)->void>
|
|
on_done) -> void {
|
|
auto info = FindInstAtPositionOrFail(context, params, on_done);
|
|
if (!info.has_inst()) {
|
|
return;
|
|
}
|
|
|
|
const auto& sem_ir = *info.file->sem_ir();
|
|
RawStringOstream text;
|
|
text << "```carbon\n" << info.file->tokens().GetTokenText(info.token);
|
|
if (auto type = StringifyTypeForHover(sem_ir, info.inst_id); !type.empty()) {
|
|
text << ": " << type;
|
|
}
|
|
text << "\n```";
|
|
|
|
on_done(clang::clangd::Hover{
|
|
.contents = {.kind = clang::clangd::MarkupKind::Markdown,
|
|
.value = text.TakeStr()},
|
|
.range = GetTokenRange(info.file->tokens(), info.token)});
|
|
}
|
|
|
|
// Shared implementation of the goto-style requests, which differ only in which
|
|
// instruction they resolve to.
|
|
static auto HandleGoto(
|
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
|
bool use_type,
|
|
llvm::function_ref<
|
|
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
|
on_done) -> void {
|
|
auto info = FindInstAtPositionOrFail(context, params, on_done);
|
|
if (!info.has_inst()) {
|
|
return;
|
|
}
|
|
|
|
const auto& sem_ir = *info.file->sem_ir();
|
|
auto target_id = GetReferencedInst(sem_ir, info.inst_id);
|
|
if (use_type) {
|
|
auto type_id = sem_ir.insts().Get(target_id).type_id();
|
|
if (!type_id.has_value()) {
|
|
on_done(std::vector<clang::clangd::Location>());
|
|
return;
|
|
}
|
|
target_id = sem_ir.types().GetTypeInstId(type_id);
|
|
}
|
|
|
|
std::vector<clang::clangd::Location> locations;
|
|
if (auto location = GetInstLocation(*info.file, target_id)) {
|
|
locations.push_back(*location);
|
|
}
|
|
on_done(std::move(locations));
|
|
}
|
|
|
|
// Implements `textDocument/definition` and `textDocument/declaration`:
|
|
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition
|
|
//
|
|
// Carbon separates declaration from definition, but SemIR resolves a name to a
|
|
// single entity instruction, so both requests currently answer the same way.
|
|
// TODO: Point `definition` at the definition when an entity is declared in one
|
|
// place and defined in another.
|
|
auto HandleDefinition(
|
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
|
llvm::function_ref<
|
|
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
|
on_done) -> void {
|
|
HandleGoto(context, params, /*use_type=*/false, on_done);
|
|
}
|
|
|
|
// Implements `textDocument/typeDefinition`:
|
|
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_typeDefinition
|
|
auto HandleTypeDefinition(
|
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
|
llvm::function_ref<
|
|
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
|
on_done) -> void {
|
|
HandleGoto(context, params, /*use_type=*/true, on_done);
|
|
}
|
|
|
|
// Implements `textDocument/references`:
|
|
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references
|
|
//
|
|
// Only finds references within the file being edited. Without a project-wide
|
|
// index there's no way to see other files, so results may be incomplete.
|
|
auto HandleReferences(
|
|
Context& context, const clang::clangd::ReferenceParams& params,
|
|
llvm::function_ref<
|
|
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
|
on_done) -> void {
|
|
auto info = FindInstAtPositionOrFail(context, params, on_done);
|
|
if (!info.has_inst()) {
|
|
return;
|
|
}
|
|
|
|
const auto& sem_ir = *info.file->sem_ir();
|
|
auto target_token =
|
|
GetInstNameToken(*info.file, GetReferencedInst(sem_ir, info.inst_id));
|
|
if (!target_token.has_value()) {
|
|
on_done(std::vector<clang::clangd::Location>());
|
|
return;
|
|
}
|
|
|
|
// This is the one request the token index can't serve: it needs every
|
|
// instruction referring to an entity, which is the opposite direction from
|
|
// the index. A scan is inherent, and cheap next to the compile that produced
|
|
// the IR.
|
|
std::vector<clang::clangd::Location> locations;
|
|
if (params.context.includeDeclaration) {
|
|
locations.push_back(
|
|
{.uri = info.file->uri(),
|
|
.range = GetTokenRange(info.file->tokens(), target_token)});
|
|
}
|
|
for (auto [inst_id, inst] : sem_ir.insts().enumerate()) {
|
|
auto name_ref = inst.TryAs<SemIR::NameRef>();
|
|
if (!name_ref ||
|
|
GetInstNameToken(*info.file, name_ref->value_id) != target_token) {
|
|
continue;
|
|
}
|
|
if (auto location = GetInstLocation(*info.file, inst_id)) {
|
|
locations.push_back(*location);
|
|
}
|
|
}
|
|
on_done(std::move(locations));
|
|
}
|
|
|
|
} // namespace Carbon::LanguageServer
|