mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
language-server: Support simple semantic queries. (#7639)
Add support for "jump to declaration", "find references", type information on hover. This support is strictly single-file for now; only references and declarations within the same file are found. We could go a bit beyond that, but to properly handle cross-file references we'll need to build an index and a compilation database, which is beyond the scope of this change. On hover, we provide the type information for the instruction under the cursor as-is. This is frequently not very useful, as the type of a function F is simply "<type of F>", but is a starting point for richer information. Assisted-by: Claude Code
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
// 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 "toolchain/language_server/position.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "toolchain/language_server/sem_ir_index.h"
|
||||
#include "toolchain/parse/tree.h"
|
||||
#include "toolchain/sem_ir/file.h"
|
||||
#include "toolchain/sem_ir/typed_insts.h"
|
||||
|
||||
namespace Carbon::LanguageServer {
|
||||
|
||||
auto GetTokenRange(const Lex::TokenizedBuffer& tokens, Lex::TokenIndex start,
|
||||
Lex::TokenIndex end) -> clang::clangd::Range {
|
||||
auto start_line = tokens.GetLine(start);
|
||||
auto start_col = tokens.GetColumnNumber(start);
|
||||
auto [end_line, end_col] = tokens.GetEndLoc(end);
|
||||
return clang::clangd::Range{
|
||||
.start = {.line = start_line.index, .character = start_col - 1},
|
||||
.end = {.line = end_line.index, .character = end_col - 1},
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the 0-based (line, column) where a token starts, for comparison
|
||||
// against an LSP position.
|
||||
static auto GetTokenStart(const Lex::TokenizedBuffer& tokens,
|
||||
Lex::TokenIndex token) -> std::pair<int, int> {
|
||||
return {tokens.GetLine(token).index, tokens.GetColumnNumber(token) - 1};
|
||||
}
|
||||
|
||||
auto FindToken(const Lex::TokenizedBuffer& tokens,
|
||||
const clang::clangd::Position& position) -> Lex::TokenIndex {
|
||||
std::pair<int, int> target = {position.line, position.character};
|
||||
|
||||
// Tokens are in source order, so find the first one starting after
|
||||
// `position`; only the token before that can contain it. Searching on
|
||||
// (line, column) avoids converting the position to a byte offset, which would
|
||||
// mean rescanning the text for line boundaries.
|
||||
//
|
||||
// `tokens()` excludes the recovery tokens added after lexing finished, which
|
||||
// matters here: those are appended rather than inserted in source order, so
|
||||
// including them would break the ordering this search relies on.
|
||||
auto range = tokens.tokens();
|
||||
auto after = llvm::partition_point(range, [&](Lex::TokenIndex token) {
|
||||
return GetTokenStart(tokens, token) <= target;
|
||||
});
|
||||
if (after == range.begin()) {
|
||||
return Lex::TokenIndex::None;
|
||||
}
|
||||
|
||||
// The position is in that token only if it's also before the token's end;
|
||||
// otherwise it falls in whitespace or a comment.
|
||||
auto token = *std::prev(after);
|
||||
auto [end_line, end_col] = tokens.GetEndLoc(token);
|
||||
if (target < std::pair<int, int>(end_line.index, end_col - 1)) {
|
||||
return token;
|
||||
}
|
||||
return Lex::TokenIndex::None;
|
||||
}
|
||||
|
||||
auto FindPositionInfo(const Context::File& file,
|
||||
const clang::clangd::Position& position) -> PositionInfo {
|
||||
const auto* sem_ir = file.sem_ir();
|
||||
const auto* index = file.sem_ir_index();
|
||||
if (!sem_ir || !index) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto token = FindToken(file.tokens(), position);
|
||||
if (!token.has_value()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
PositionInfo info = {.file = &file, .token = token};
|
||||
auto insts = index->InstsForToken(token);
|
||||
for (auto inst_id : insts) {
|
||||
// Prefer a name reference: a token such as the name in `fn F()` also has
|
||||
// instructions for the declaration itself, but a request at a name is about
|
||||
// the name.
|
||||
if (sem_ir->insts().Is<SemIR::NameRef>(inst_id)) {
|
||||
info.inst_id = inst_id;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
if (!insts.empty()) {
|
||||
info.inst_id = insts.front();
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
auto GetReferencedInst(const SemIR::File& sem_ir, SemIR::InstId inst_id)
|
||||
-> SemIR::InstId {
|
||||
if (auto name_ref = sem_ir.insts().TryGetAs<SemIR::NameRef>(inst_id)) {
|
||||
return name_ref->value_id;
|
||||
}
|
||||
return inst_id;
|
||||
}
|
||||
|
||||
auto GetInstNameToken(const Context::File& file, SemIR::InstId inst_id)
|
||||
-> Lex::TokenIndex {
|
||||
const auto* sem_ir = file.sem_ir();
|
||||
if (!sem_ir || !inst_id.has_value()) {
|
||||
return Lex::TokenIndex::None;
|
||||
}
|
||||
auto loc_id = sem_ir->insts().GetCanonicalLocId(inst_id);
|
||||
if (loc_id.kind() != SemIR::LocId::Kind::NodeId) {
|
||||
// Imported from another file, which we can't yet name a location in.
|
||||
return Lex::TokenIndex::None;
|
||||
}
|
||||
auto node_id = loc_id.node_id();
|
||||
if (!node_id.has_value()) {
|
||||
return Lex::TokenIndex::None;
|
||||
}
|
||||
return GetNameToken(file.tree_and_subtrees(), node_id);
|
||||
}
|
||||
|
||||
auto GetInstLocation(const Context::File& file, SemIR::InstId inst_id)
|
||||
-> std::optional<clang::clangd::Location> {
|
||||
auto token = GetInstNameToken(file, inst_id);
|
||||
if (!token.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return clang::clangd::Location{.uri = file.uri(),
|
||||
.range = GetTokenRange(file.tokens(), token)};
|
||||
}
|
||||
|
||||
} // namespace Carbon::LanguageServer
|
||||
Reference in New Issue
Block a user