Add language server support for SemIR in testdata. (#7803)

Add hover cards and jump to declaration / definition / reference for the
formatted SemIR that appears in check tests. This is done by adding a
heuristic "parser" for SemIR to the language server. The
cross-references are strictly best-effort, since this is just a tool for
Carbon developers, not a user-facing facility.

A couple of other changes made along the way:

* file_test tests with an AUTOUPDATE-SPLIT no longer look for CHECK:
lines outside that split. This was motivated by the tests for this new
facility including CHECK: lines as part of the test input.
* An agent skill for working on the language server, tracking some
things that cost Claude time when working on this.

Assisted-by: Claude via Antigravity
This commit is contained in:
Richard Smith
2026-09-23 23:44:32 +00:00
committed by GitHub
parent 652e0ce7d0
commit 03939a9223
49 changed files with 2587 additions and 75 deletions
+197
View File
@@ -0,0 +1,197 @@
---
name: Language server
description:
Instructions for working on Carbon's LSP language server, including its
architecture, its file_test-based tests, and the VS Code extension.
---
# Language server
<!--
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
-->
## Introduction
This skill covers [`toolchain/language_server/`](/toolchain/language_server/),
which implements `carbon language-server`, and
[`utils/vscode/`](/utils/vscode/), the VS Code extension that launches it.
## Architecture
The server is built on clangd's LSP transport (`clang::clangd`), not on a
Carbon-specific one. That means clangd's `Protocol.h` types (`Position`,
`Range`, `Location`, `Hover`, `MarkupContent`) are the interface currency.
- `server.cpp` / `incoming_messages.cpp`: message dispatch. A handler must be
registered in `incoming_messages.cpp` before it can be called.
- `handle_*.cpp`: one file per request family, each declaring its entry point
in `handle.h`.
- `handle_initialize.cpp`: the advertised capabilities. **Adding a capability
changes `Content-Length` in every test that calls `initialize`**, so expect
a large autoupdate diff.
- `context.h` / `context.cpp`: `Context::File` per open document, plus the
compile driver. `Context::File::unit()` has a `CARBON_CHECK` on the compile
driver, so any handler that reaches for the parse tree must first rule out
documents that were never compiled.
- `position.h`, `sem_ir_index.h`: mapping source positions to SemIR
instructions for real Carbon files.
- `sem_ir_text.h`, `handle_sem_ir_text.h`: navigation within the *formatted
SemIR* in a test file's `// CHECK:STDOUT:` lines. This is a heuristic text
index, deliberately independent of the real SemIR data structures. See
[the SemIR text reader](#the-semir-text-reader).
### Document kinds
The server handles two kinds of document, distinguished by the `languageId`
from `textDocument/didOpen`, with a content sniff as a fallback:
- `carbon`: a real Carbon file. Compiled; diagnostics published.
- `carbon-testdata`: a test file. **Not compiled**, because we lack logic to
split it into one file per `// ---` split marker.
> [!IMPORTANT]
> A handler that assumes every file was compiled will crash on a test file.
> When adding one, give the test-file path an explicit early return.
## Tests
There is **no language-server-specific test target**.
`toolchain/language_server/BUILD` only declares
`filegroup(name = "testdata")`, which is pulled into
`//toolchain/testing:all_testdata` and run by `//toolchain/testing:file_test`.
```bash
# Run just the language server tests (or any subset).
bazelisk test //toolchain/testing:file_test \
--test_arg=--file_tests=toolchain/language_server/testdata/position/hover_and_goto.carbon
# See the raw output, which is much easier to read than a test failure.
bazelisk run //toolchain/testing:file_test -- --dump_output \
--file_tests=toolchain/language_server/testdata/position/hover_and_goto.carbon
# Update expectations. Never hand-write CHECK lines.
./toolchain/autoupdate_testdata.py toolchain/language_server/testdata/...
```
These tests run serially, because clangd's logging is a global singleton.
### Test file shape
The request stream is a `// --- STDIN` split written with the `[[@LSP-*]]`
keywords, and the responses land in a trailing `// --- AUTOUPDATE-SPLIT`.
Documents come from other splits by way of `"text": "FROM_FILE_SPLIT"`, which
is substituted with the content of the split whose name matches the `uri`.
```carbon
// --- position.carbon
fn Abs(n: i32) -> i32 { return n; }
// --- STDIN
[[@LSP-CALL:initialize:"capabilities": {}]]
[[@LSP-NOTIFY:textDocument/didOpen:
"textDocument": {
"uri": "file:/position.carbon",
"languageId": "carbon",
"text": "FROM_FILE_SPLIT"
}
]]
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/position.carbon"},
"position": {"line": 0, "character": 3}
]]
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// --- AUTOUPDATE-SPLIT
```
Full keyword documentation is in
[`testing/file_test/README.md`](/testing/file_test/README.md).
### Traps
> [!WARNING]
> **A blank line inside the `STDIN` split breaks the JSON transport.** It
> terminates a header block, so clangd logs a timestamped
> `Warning: Missing Content-Length header, or zero-length message.` The
> timestamp makes the test unreproducible, so it fails on the next run.
> Comment lines between messages are fine; blank lines are not. A single blank
> line immediately before `// --- AUTOUPDATE-SPLIT` is also fine.
Other things worth knowing:
- **`positionEncoding` is UTF-16.** A `character` is a UTF-16 code unit
offset, not a byte offset.
- **Line and character numbers in requests are 0-based**, while the `locN_M`
suffixes in SemIR output are 1-based. Off-by-ones here are silent: the
request succeeds and returns the wrong thing.
- **A split can hold a document that itself contains `// CHECK:STDOUT:`
lines**, because `CHECK` lines only form expectations inside the
`AUTOUPDATE-SPLIT`. Such a document still can't contain a literal `// ---`
line, which would split the enclosing test file; write it as
`[[@0x2f]]/ --- name.carbon`.
## The SemIR text reader
`sem_ir_text.cpp` indexes the formatted SemIR inside a test file's
`// CHECK:STDOUT:` lines so that hover and go-to-definition work on operand
names. It is a heuristic reader, not a parser, and its correctness rests on
facts about `toolchain/sem_ir/formatter.cpp` and
`toolchain/sem_ir/inst_namer.cpp`. Re-check these if the formatter changes:
- There are exactly four scope keywords: `file`, `generated`, `imports`, and
`constants` (`InstNamer::GetScopeName`). Everything else is `@entityname`.
- A reference is `%name` within its own scope and `scope.%name` otherwise
(`InstNamer::GetNameFor`). Names may contain `.` and may _start_ with one,
as in `%.Self.frozen`.
- **Type annotations are printed in the `constants` scope.**
`Formatter::FormatTypeOfInst` does
`llvm::SaveAndRestore file_scope(scope_, InstNamer::ScopeId::Constants)`,
so in `%x: %foo = ...` a bare `%foo` means `constants.%foo`. This does not
apply to ordinary operands or to `[concrete = ...]` annotations.
- **`*_decl` braces hold the declared entity's scope.** The braces of
`%F.decl: ... = fn_decl @F [...] { ... } { ... }` are lexically inside
`file { }`, but their names belong to `@F`.
- **`!with Self:` switches scope without a brace**, until `!members:`
switches it back. Brace counting alone cannot see this.
- A `specific @F(args) { }` block uses `@F`'s scope and defines nothing; each
`%name => value` row references an instruction of the generic.
### Validating a change to the reader
The unit tests only cover a handful of cases. To check a change against the
real corpus, drive the server over a sample of check testdata, hovering on
every `%name`, and compare the resolved fraction before and after. Roughly 99%
of names resolve; the residue are names the formatter references but never
emits a definition line for, such as `%I.WithSelf.F`, which only ever appears
inside a `[symbolic = ...]` annotation.
Two things to get right in such a harness:
- Feed the request stream from a **file**, not a pipe. The server reads stdin
as a file and reports `error: Input/output error` on a pipe.
- Read the output as **bytes**. Python's `text=True` rewrites the `\r\n`
framing, and `Content-Length` counts bytes.
## VS Code extension
[`utils/vscode/`](/utils/vscode/) declares three languages in `package.json`:
| Language id | Applies to |
| ---------------- | --------------------------------- |
| `carbon` | `*.carbon` |
| `carbon-testdata`| `**/testdata/**/*.carbon` |
| `semir` | `*.semir` |
> [!NOTE]
> The TextMate _scope_ for SemIR is `source.carbon-semir`, but the
> _language id_ is `semir`. Markdown code fences in hover text resolve language
> ids, so a fence must say ` ```semir `.
`extension.ts` launches the server over stdio, using the `carbonPath` setting
(default `./bazel-bin/toolchain/carbon`). Its `documentSelector` controls which
files are sent to the server at all; a new document kind has to be added there
as well as in the server.
@@ -35,6 +35,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
([SKILL.md](../builtins/SKILL.md)) for guidelines on registering, mapping,
constant evaluating, and lowering compiler builtin primitives (e.g.
`"int.convert_float"`).
- **Language server**: Refer to the **Language server** skill
([SKILL.md](../language_server/SKILL.md)) before working on
`toolchain/language_server/` or `utils/vscode/`. Neither follows the
patterns described here.
- **Phases**: Lex -> Parse -> Check -> Lower.
- **Definitions**: Many kinds (tokens, parse nodes, SemIR instructions) are
defined in `.def` files and expanded by way of macros.
+5
View File
@@ -23,6 +23,11 @@ Toolchain tests evaluate Carbon source files through Lexing, Parsing, Checking,
and optionally Lowering. Output (for example SemIR dumps, Clang errors) is
captured and validated using inline CHECK records.
Language server tests also use `file_test`, but with quite different
conventions (an LSP message stream, `AUTOUPDATE-SPLIT`, `FROM_FILE_SPLIT`).
Refer to the **Language server** skill
([SKILL.md](../language_server/SKILL.md)) for those.
## Structure and Authoring
### File Layout and Headers
+7
View File
@@ -284,6 +284,13 @@ Supported comment markers are:
Output line matchers may contain `[[@LINE+offset]` and `{{regex}}` syntaxes,
similar to `FileCheck`.
When the file uses an `AUTOUPDATE-SPLIT`, only `CHECK` lines in that split
are matchers; elsewhere they are ordinary content. This is what allows a
split to hold a test file that itself contains `CHECK` lines, as the
language server's SemIR tests do. Note that such a split still can't contain
a `// ---` line, which would split the enclosing file; write the `/`
characters as `[[@0x2f]]` to avoid that.
- ```
// TIP: <tip>
```
+33 -9
View File
@@ -25,6 +25,9 @@ using ::testing::Matcher;
using ::testing::MatchesRegex;
using ::testing::StrEq;
// The name of the trailing split that autoupdate writes `CHECK` lines into.
static constexpr llvm::StringLiteral AutoupdateSplit = "AUTOUPDATE-SPLIT";
// Represents the different kinds of version-control conflict markers that are
// relevant for the autoupdater. One key concern here is the distinction between
// "snapshot" and "diff" conflict regions. Snapshot regions are the more
@@ -735,6 +738,22 @@ static auto TryConsumeSetFlag(llvm::StringRef line_trimmed,
return true;
}
// Returns whether the file uses a `// --- AUTOUPDATE-SPLIT` split.
//
// Autoupdate writes every `CHECK` into that split, so when one is present, a
// `CHECK` line anywhere else is part of the test input rather than an
// expectation. That's what allows a split to hold a test file that itself
// contains `CHECK` lines.
static auto UsesAutoupdateSplit(llvm::StringRef content) -> bool {
for (llvm::StringRef line : llvm::split(content, '\n')) {
llvm::StringRef trimmed = line.trim();
if (trimmed.consume_front("// ---") && trimmed.trim() == AutoupdateSplit) {
return true;
}
}
return false;
}
// Process content for either the main file (with `test_file` and
// `found_autoupdate` provided) or an included file (with those arguments null).
//
@@ -761,6 +780,8 @@ static auto ProcessFileContent(llvm::StringRef filename,
// Otherwise conflict markers are errors.
auto previous_conflict_marker = MarkerKind::None;
const bool uses_autoupdate_split = UsesAutoupdateSplit(content_cursor);
SplitState split_state;
while (!content_cursor.empty()) {
@@ -802,13 +823,18 @@ static auto ProcessFileContent(llvm::StringRef filename,
continue;
}
CARBON_ASSIGN_OR_RETURN(
is_consumed,
TryConsumeCheck(running_autoupdate, line_index, line, line_trimmed,
test_file ? &test_file->expected_stdout : nullptr,
test_file ? &test_file->expected_stderr : nullptr));
if (is_consumed) {
continue;
// `CHECK` lines are only expectations where autoupdate would write them.
// Everywhere else they're input, which is how a split can hold a test file
// that itself contains `CHECK` lines.
if (!uses_autoupdate_split || split_state.filename == AutoupdateSplit) {
CARBON_ASSIGN_OR_RETURN(
is_consumed,
TryConsumeCheck(running_autoupdate, line_index, line, line_trimmed,
test_file ? &test_file->expected_stdout : nullptr,
test_file ? &test_file->expected_stderr : nullptr));
if (is_consumed) {
continue;
}
}
if (test_file) {
@@ -895,8 +921,6 @@ auto ProcessTestFile(llvm::StringRef test_name, bool running_autoupdate)
return ErrorBuilder() << "Missing AUTOUPDATE/NOAUTOUPDATE setting";
}
constexpr llvm::StringLiteral AutoupdateSplit = "AUTOUPDATE-SPLIT";
// Validate AUTOUPDATE-SPLIT use, and remove it from test files if present.
if (test_file.has_splits) {
for (const auto& test_file :
@@ -0,0 +1,30 @@
// 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
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //testing/file_test:file_test_base_test --test_arg=--file_tests=testing/file_test/testdata/check_outside_autoupdate_split.carbon
// TIP: To dump output, run:
// TIP: bazel run //testing/file_test:file_test_base_test -- --dump_output --file_tests=testing/file_test/testdata/check_outside_autoupdate_split.carbon
// When the file uses an `AUTOUPDATE-SPLIT`, `CHECK` lines elsewhere are
// content rather than expectations, so a split can hold a test file of its
// own. Such a split still can't contain a literal `// ---` line, so the `/`
// characters are written as `[[@0x2f]]`.
//
// The echoed line numbers below show that both lines are part of `a.carbon`:
// if the `CHECK` had been consumed it would be an unmatched expectation, and
// if the `// ---` had split, there would be a second file in the arguments.
// --- a.carbon
// CHECK:STDOUT: not an expectation
this line follows a CHECK line
[[@0x2f]][[@0x2f]] --- not a split
this line follows a split marker
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: 2 args: `default_args`, `a.carbon`
// CHECK:STDOUT: a.carbon:2: this line follows a CHECK line
// CHECK:STDOUT: a.carbon:4: this line follows a split marker
+18 -2
View File
@@ -44,12 +44,24 @@ cc_library(
],
)
cc_library(
name = "sem_ir_text",
srcs = ["sem_ir_text.cpp"],
hdrs = ["sem_ir_text.h"],
deps = [
"//common:raw_string_ostream",
"@llvm-project//clang-tools-extra/clangd:ClangDaemon",
"@llvm-project//llvm:Support",
],
)
cc_library(
name = "context",
srcs = ["context.cpp"],
hdrs = ["context.h"],
deps = [
":sem_ir_index",
":sem_ir_text",
"//common:check",
"//common:map",
"//common:raw_string_ostream",
@@ -93,12 +105,16 @@ cc_library(
cc_library(
name = "handle",
srcs = glob(["handle_*"]),
hdrs = ["handle.h"],
srcs = glob(["handle_*.cpp"]),
hdrs = [
"handle.h",
"handle_sem_ir_text.h",
],
deps = [
":context",
":position",
":sem_ir_index",
":sem_ir_text",
"//common:check",
"//common:raw_string_ostream",
"//toolchain/base:shared_value_stores",
+25
View File
@@ -10,6 +10,7 @@
#include "common/check.h"
#include "common/raw_string_ostream.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/TargetParser/Host.h"
#include "toolchain/base/clang_invocation.h"
@@ -216,17 +217,41 @@ auto Context::File::sem_ir_index() const -> const SemIRIndex* {
return &*sem_ir_index_;
}
auto Context::File::sem_ir_text() const -> const SemIRText* {
if (!is_test_file_) {
return nullptr;
}
if (!sem_ir_text_) {
sem_ir_text_.emplace(text_);
}
return sem_ir_text_->empty() ? nullptr : &*sem_ir_text_;
}
auto Context::File::SetText(Context& context, std::optional<int64_t> version,
llvm::StringRef text) -> void {
// Clear state dependent on the source text.
compile_driver_.reset();
sem_ir_index_.reset();
sem_ir_text_.reset();
text_ = text.str();
is_test_file_ = language_id_ == "carbon-testdata";
// A consumer to gather diagnostics for the file.
DiagnosticConsumer consumer(&context, uri_, version);
if (is_test_file_) {
// A test file isn't a Carbon source file: it holds any number of input
// files, plus the output expected from compiling them. Compiling it as one
// source file would report errors on almost every line, so don't. Publish
// the empty diagnostic list anyway, to clear anything left over from
// before the file became a test file.
// TODO: Compile each of the file's splits separately, so that the Carbon
// source in a test file gets the same support as any other Carbon source.
context.PublishDiagnostics(consumer.params());
return;
}
// TODO: Make the processing asynchronous, to better handle rapid text
// updates.
+28 -3
View File
@@ -19,6 +19,7 @@
#include "toolchain/driver/compile_driver.h"
#include "toolchain/driver/compile_options.h"
#include "toolchain/language_server/sem_ir_index.h"
#include "toolchain/language_server/sem_ir_text.h"
#include "toolchain/lex/tokenized_buffer.h"
#include "toolchain/parse/tree_and_subtrees.h"
#include "toolchain/sem_ir/file.h"
@@ -31,9 +32,12 @@ class Context {
// Cached information for an open file.
class File {
public:
explicit File(clang::clangd::URIForFile uri)
// `language_id` is the client's `TextDocumentItem::languageId`, which may
// be empty for a file we were never told about in `didOpen`.
explicit File(clang::clangd::URIForFile uri, llvm::StringRef language_id)
: uri_(std::move(uri)),
filename_(uri_.file().str()),
language_id_(language_id.str()),
options_(&codegen_options_) {}
// Changes the file's text, updating dependent state.
@@ -44,6 +48,13 @@ class Context {
auto filename() const -> llvm::StringRef { return filename_; }
auto text() const -> llvm::StringRef { return text_; }
// Returns whether this is a toolchain test file rather than a Carbon
// source file. Test files aren't compiled: their text is a script for the
// test runner, holding any number of input files plus the output expected
// from compiling them, so compiling it as a single source file would
// produce nothing but noise.
auto is_test_file() const -> bool { return is_test_file_; }
auto tree_and_subtrees() const -> const Parse::TreeAndSubtrees& {
return unit().parse_tree_and_subtrees();
}
@@ -53,8 +64,11 @@ class Context {
}
// Returns the checked IR, or null if checking didn't get far enough to
// produce one.
// produce one, including because this is a test file and wasn't compiled.
auto sem_ir() const -> const SemIR::File* {
if (!compile_driver_) {
return nullptr;
}
const auto& compilation_unit = unit();
return compilation_unit.has_sem_ir() ? &compilation_unit.sem_ir()
: nullptr;
@@ -70,6 +84,11 @@ class Context {
// users actually notice.
auto sem_ir_index() const -> const SemIRIndex*;
// Returns an index of the formatted SemIR in this file's expected output,
// building it on first use as `sem_ir_index` does. Returns null unless
// this is a test file that has some.
auto sem_ir_text() const -> const SemIRText*;
private:
auto unit() const -> const CompilationUnit& {
CARBON_CHECK(compile_driver_);
@@ -80,15 +99,21 @@ class Context {
clang::clangd::URIForFile uri_;
std::string filename_;
// The language the client says this file is written in, which is how we
// recognize a test file when the client knows it's looking at one.
std::string language_id_;
// Current file content, and derived values.
std::string text_;
bool is_test_file_ = false;
CodegenOptions codegen_options_;
CompileOptions options_;
std::unique_ptr<CompileDriver> compile_driver_;
// Built on demand by `sem_ir_index()`, and discarded by `SetText`.
// Built on demand by their accessors, and discarded by `SetText`.
mutable std::optional<SemIRIndex> sem_ir_index_;
mutable std::optional<SemIRText> sem_ir_text_;
};
// `vlog_stream` is optional; other parameters are required.
+14
View File
@@ -10,6 +10,13 @@
namespace Carbon::LanguageServer {
// Locates where the entity named at a position was declared.
auto HandleDeclaration(
Context& context, const clang::clangd::TextDocumentPositionParams& params,
llvm::function_ref<
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
on_done) -> void;
// Locates the entity named at a position.
auto HandleDefinition(
Context& context, const clang::clangd::TextDocumentPositionParams& params,
@@ -57,6 +64,13 @@ auto HandleHover(
llvm::function_ref<auto(llvm::Expected<clang::clangd::Hover>)->void>
on_done) -> void;
// Locates the implementations of the entity named at a position.
auto HandleImplementation(
Context& context, const clang::clangd::TextDocumentPositionParams& params,
llvm::function_ref<
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
on_done) -> void;
// Tells the client what features are supported, and negotiates the position
// encoding.
auto HandleInitialize(
@@ -123,6 +123,13 @@ auto HandleDocumentSymbol(
if (!file) {
return;
}
if (file->is_test_file()) {
// A test file isn't Carbon source, so it has no parse tree to find symbols
// in.
// TODO: Report the file's splits, and the entities within each.
on_done(std::vector<clang::clangd::DocumentSymbol>());
return;
}
const auto& tree_and_subtrees = file->tree_and_subtrees();
const auto& tree = tree_and_subtrees.tree();
@@ -32,6 +32,12 @@ auto HandleFormatting(
if (!file) {
return;
}
if (file->is_test_file()) {
// A test file isn't Carbon source, so it has no parse tree to format from,
// and reformatting one as if it were would destroy it.
on_done(std::vector<clang::clangd::TextEdit>());
return;
}
RawStringOstream out;
if (!Format::Format(file->tokens(), out)) {
@@ -35,6 +35,7 @@ auto HandleInitialize(
{"documentFormattingProvider", true},
{"documentSymbolProvider", true},
{"hoverProvider", true},
{"implementationProvider", true},
{"positionEncoding", encoding},
{"referencesProvider", true},
{"textDocumentSync", /*Incremental=*/2},
+96 -30
View File
@@ -4,10 +4,12 @@
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "common/raw_string_ostream.h"
#include "toolchain/language_server/handle.h"
#include "toolchain/language_server/handle_sem_ir_text.h"
#include "toolchain/language_server/position.h"
#include "toolchain/language_server/sem_ir_index.h"
#include "toolchain/sem_ir/file.h"
@@ -32,34 +34,21 @@ static auto StringifyTypeForHover(const SemIR::File& sem_ir,
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.
// Returns the file a position-based query refers to. If the request names a
// file we don't know, produces an InvalidParams error and returns null.
template <typename ResponseType>
static auto FindInstAtPositionOrFail(
static auto FindFileOrFail(
Context& context, const clang::clangd::TextDocumentPositionParams& params,
llvm::function_ref<auto(llvm::Expected<ResponseType>)->void> on_done)
-> PositionInfo {
-> const Context::File* {
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;
return file;
}
// Implements `textDocument/hover`:
@@ -68,8 +57,19 @@ 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);
const auto* file =
FindFileOrFail<clang::clangd::Hover>(context, params, on_done);
if (!file) {
return;
}
if (auto hover = GetSemIRTextHover(*file, params.position)) {
on_done(std::move(*hover));
return;
}
auto info = FindPositionInfo(*file, params.position);
if (!info.has_inst()) {
on_done(clang::clangd::Hover());
return;
}
@@ -91,12 +91,24 @@ auto HandleHover(
// instruction they resolve to.
static auto HandleGoto(
Context& context, const clang::clangd::TextDocumentPositionParams& params,
bool use_type,
SemIRTextGoto sem_ir_text_goto, 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);
const auto* file = FindFileOrFail<std::vector<clang::clangd::Location>>(
context, params, on_done);
if (!file) {
return;
}
if (auto locations =
GetSemIRTextLocations(*file, params.position, sem_ir_text_goto)) {
on_done(std::move(*locations));
return;
}
auto info = FindPositionInfo(*file, params.position);
if (!info.has_inst()) {
on_done(std::vector<clang::clangd::Location>());
return;
}
@@ -118,19 +130,58 @@ static auto HandleGoto(
on_done(std::move(locations));
}
// Implements `textDocument/definition` and `textDocument/declaration`:
// Implements `textDocument/definition`:
// 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);
HandleGoto(context, params, SemIRTextGoto::Definition, /*use_type=*/false,
on_done);
}
// Implements `textDocument/declaration`:
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_declaration
//
// In formatted SemIR this finds the source text the name was checked from,
// which is the closest thing an instruction has to a declaration, and is the
// more useful answer given `definition` already finds the instruction.
//
// Carbon separates declaration from definition, but SemIR resolves a name to a
// single entity instruction, so for Carbon source both requests answer alike.
// TODO: Point `definition` at the definition when an entity is declared in one
// place and defined in another.
auto HandleDeclaration(
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, SemIRTextGoto::Source, /*use_type=*/false,
on_done);
}
// Implements `textDocument/implementation`:
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_implementation
//
// In formatted SemIR this finds the value the name takes in each `specific` of
// the generic that defines it.
//
// TODO: For Carbon source, find the `impl`s of an interface, and the functions
// implementing an interface's associated functions.
auto HandleImplementation(
Context& context, const clang::clangd::TextDocumentPositionParams& params,
llvm::function_ref<
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
on_done) -> void {
const auto* file = FindFileOrFail<std::vector<clang::clangd::Location>>(
context, params, on_done);
if (!file) {
return;
}
auto locations =
GetSemIRTextLocations(*file, params.position, SemIRTextGoto::Specifics);
on_done(locations.value_or(std::vector<clang::clangd::Location>()));
}
// Implements `textDocument/typeDefinition`:
@@ -140,7 +191,10 @@ auto HandleTypeDefinition(
llvm::function_ref<
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
on_done) -> void {
HandleGoto(context, params, /*use_type=*/true, on_done);
// A SemIR name's type is written on its defining line, which `definition`
// already finds, so there's nothing extra to offer for formatted SemIR.
HandleGoto(context, params, SemIRTextGoto::Definition, /*use_type=*/true,
on_done);
}
// Implements `textDocument/references`:
@@ -153,8 +207,20 @@ auto HandleReferences(
llvm::function_ref<
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
on_done) -> void {
auto info = FindInstAtPositionOrFail(context, params, on_done);
const auto* file = FindFileOrFail<std::vector<clang::clangd::Location>>(
context, params, on_done);
if (!file) {
return;
}
if (auto locations = GetSemIRTextLocations(*file, params.position,
SemIRTextGoto::References)) {
on_done(std::move(*locations));
return;
}
auto info = FindPositionInfo(*file, params.position);
if (!info.has_inst()) {
on_done(std::vector<clang::clangd::Location>());
return;
}
@@ -0,0 +1,167 @@
// 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/handle_sem_ir_text.h"
#include <optional>
#include <utility>
#include <vector>
#include "common/raw_string_ostream.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringExtras.h"
#include "toolchain/language_server/sem_ir_text.h"
namespace Carbon::LanguageServer {
// The language id the VS Code extension uses for formatted SemIR, so that
// hover text is highlighted the same way the output it was read from is. Note
// this is the language id from `package.json`, not the TextMate scope name
// `source.carbon-semir`.
static constexpr llvm::StringLiteral SemIRLanguage = "semir";
// Writes `text` as a fenced code block of formatted SemIR.
static auto WriteCodeBlock(llvm::raw_ostream& out, llvm::StringRef text)
-> void {
out << "```" << SemIRLanguage << "\n" << text << "\n```\n";
}
// Returns the 0-based line `line` of `text`, without its terminator, or an
// empty string if the text has no such line.
static auto GetLine(llvm::StringRef text, int line) -> llvm::StringRef {
for (llvm::StringRef candidate : llvm::split(text, '\n')) {
if (line-- == 0) {
return candidate.rtrim('\r');
}
}
return llvm::StringRef();
}
// Returns a definition written the way a reference to it from outside its
// scope would be, so that the scope it came from is visible. Only the first
// line is qualified: the rest is the body of the definition's brace group.
static auto Qualified(const SemIRTextDefinition& definition) -> std::string {
RawStringOstream out;
out << definition.scope << "." << definition.text;
return out.TakeStr();
}
// Returns `text` with every line indented, so that it reads as subordinate to
// the line before it.
static auto Indent(llvm::StringRef text) -> std::string {
RawStringOstream out;
llvm::ListSeparator newline("\n");
for (llvm::StringRef line : llvm::split(text, '\n')) {
out << newline << " " << line;
}
return out.TakeStr();
}
auto GetSemIRTextHover(const Context::File& file,
const clang::clangd::Position& position)
-> std::optional<clang::clangd::Hover> {
const auto* sem_ir_text = file.sem_ir_text();
if (!sem_ir_text) {
return std::nullopt;
}
auto name = sem_ir_text->Lookup(position);
if (!name) {
return std::nullopt;
}
if (name->definitions.empty() && !name->source_position) {
// We found a name but have nothing to say about it, so say nothing rather
// than showing an empty popup.
return std::nullopt;
}
RawStringOstream text;
// The definitions, written the way a reference to them from outside their
// scope would be, so that the scope each one came from is visible.
for (const auto& definition : name->definitions) {
WriteCodeBlock(text, Qualified(definition));
}
// What the name becomes in each specific of the generic that defines it.
if (!name->specific_values.empty()) {
text << "\nSpecific values:\n\n";
RawStringOstream values;
llvm::ListSeparator newline("\n");
for (const auto& value : name->specific_values) {
values << newline << value.specific << " =>";
if (value.value_definition) {
// The value is nearly always a reference to a constant, and the name
// of the constant says little, so show what it's defined as. It goes
// on its own line because a definition can be long, or several lines.
values << "\n" << Indent(Qualified(*value.value_definition));
} else {
values << " " << value.value;
}
}
WriteCodeBlock(text, values.TakeStr());
}
// The source text the name was checked from.
if (name->source_position) {
auto source = GetLine(file.text(), name->source_position->line).trim();
if (!source.empty()) {
text << "\nSource:\n\n```carbon\n" << source << "\n```\n";
}
}
return clang::clangd::Hover{
.contents = {.kind = clang::clangd::MarkupKind::Markdown,
.value = text.TakeStr()},
.range = name->range};
}
auto GetSemIRTextLocations(const Context::File& file,
const clang::clangd::Position& position,
SemIRTextGoto goto_kind)
-> std::optional<std::vector<clang::clangd::Location>> {
const auto* sem_ir_text = file.sem_ir_text();
if (!sem_ir_text) {
return std::nullopt;
}
auto name = sem_ir_text->Lookup(position);
if (!name) {
return std::nullopt;
}
std::vector<clang::clangd::Location> locations;
auto add = [&](clang::clangd::Range range) {
locations.push_back({.uri = file.uri(), .range = range});
};
switch (goto_kind) {
case SemIRTextGoto::Definition:
for (const auto& definition : name->definitions) {
add(definition.range);
}
break;
case SemIRTextGoto::Source:
if (name->source_position) {
// We know where the source text starts but not how far it runs, so
// point at it rather than selecting it.
add({.start = *name->source_position, .end = *name->source_position});
}
break;
case SemIRTextGoto::Specifics:
for (const auto& value : name->specific_values) {
add(value.range);
}
break;
case SemIRTextGoto::References:
for (const auto& occurrence : name->occurrences) {
add(occurrence);
}
break;
}
return locations;
}
} // namespace Carbon::LanguageServer
@@ -0,0 +1,55 @@
// 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
#ifndef CARBON_TOOLCHAIN_LANGUAGE_SERVER_HANDLE_SEM_IR_TEXT_H_
#define CARBON_TOOLCHAIN_LANGUAGE_SERVER_HANDLE_SEM_IR_TEXT_H_
#include <optional>
#include <vector>
#include "clang-tools-extra/clangd/Protocol.h"
#include "toolchain/language_server/context.h"
namespace Carbon::LanguageServer {
// Answers `textDocument/hover` from the formatted SemIR in a test file's
// expected output. Returns `nullopt` if `position` isn't within a SemIR name,
// leaving the request to be answered from the file's Carbon source.
auto GetSemIRTextHover(const Context::File& file,
const clang::clangd::Position& position)
-> std::optional<clang::clangd::Hover>;
// What a goto-style request should find for a name in formatted SemIR.
enum class SemIRTextGoto : int8_t {
// The lines that define the name, for `textDocument/definition`.
Definition,
// The source text that the name's `.loc<line>_<column>` suffix points at,
// for `textDocument/declaration`. A SemIR name is derived from the source it
// was checked from, so this is where the name came from, in the input file
// the enclosing block of output was compiled from.
Source,
// The rows of `specific` blocks that give the name its value, for
// `textDocument/implementation`. A name defined in a generic is symbolic,
// and each specific of that generic is one way of making it concrete, which
// is as close to an implementation as SemIR has.
Specifics,
// Everywhere the name is written in the same block of output, for
// `textDocument/references`.
References,
};
// Answers a goto-style request from the formatted SemIR in a test file's
// expected output. Returns `nullopt` if `position` isn't within a SemIR name,
// leaving the request to be answered from the file's Carbon source.
auto GetSemIRTextLocations(const Context::File& file,
const clang::clangd::Position& position,
SemIRTextGoto goto_kind)
-> std::optional<std::vector<clang::clangd::Location>>;
} // namespace Carbon::LanguageServer
#endif // CARBON_TOOLCHAIN_LANGUAGE_SERVER_HANDLE_SEM_IR_TEXT_H_
@@ -21,8 +21,10 @@ auto HandleDidOpenTextDocument(
return;
}
auto insert_result = context.files().Insert(
filename, [&] { return Context::File(params.textDocument.uri); });
auto insert_result = context.files().Insert(filename, [&] {
return Context::File(params.textDocument.uri,
params.textDocument.languageId);
});
insert_result.value().SetText(context, params.textDocument.version,
params.textDocument.text);
if (!insert_result.is_inserted()) {
@@ -73,11 +73,12 @@ auto IncomingMessages::AddNotificationHandler(
IncomingMessages::IncomingMessages(clang::clangd::Transport* transport,
Context* context)
: transport_(transport), context_(context) {
AddCallHandler("textDocument/declaration", &HandleDefinition);
AddCallHandler("textDocument/declaration", &HandleDeclaration);
AddCallHandler("textDocument/definition", &HandleDefinition);
AddCallHandler("textDocument/documentSymbol", &HandleDocumentSymbol);
AddCallHandler("textDocument/formatting", &HandleFormatting);
AddCallHandler("textDocument/hover", &HandleHover);
AddCallHandler("textDocument/implementation", &HandleImplementation);
AddCallHandler("textDocument/references", &HandleReferences);
AddCallHandler("textDocument/typeDefinition", &HandleTypeDefinition);
AddCallHandler("initialize", &HandleInitialize);
+756
View File
@@ -0,0 +1,756 @@
// 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/sem_ir_text.h"
#include <cstdint>
#include <iterator>
#include <optional>
#include <tuple>
#include <utility>
#include "common/raw_string_ostream.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/Twine.h"
namespace Carbon::LanguageServer {
namespace {
// A line of formatted SemIR recovered from a `// CHECK:STDOUT:` line, and
// where it was written in the document.
struct ContentLine {
// 0-based document line.
int line;
// 0-based document column where `text` begins.
int column;
// The SemIR, with the check prefix removed.
llvm::StringRef text;
};
// The source location encoded in a name's `.loc<line>[_<column>]` suffix.
struct SourceLoc {
// 1-based line number within the input file the SemIR was compiled from.
int line;
// 0-based column number.
int column;
};
} // namespace
// The prefix on a line of expected standard output in a test file.
static constexpr llvm::StringLiteral CheckPrefix = "// CHECK:STDOUT:";
// The prefix on a line that starts a new input file within a test file.
static constexpr llvm::StringLiteral SplitPrefix = "// ---";
// The prefix on the line that starts a new file's output within formatted
// SemIR. Note this is the same marker as `SplitPrefix` without the comment,
// because the output names the input files it came from.
static constexpr llvm::StringLiteral BlockPrefix = "--- ";
// The scope that type annotations are written in, whatever scope encloses them.
static constexpr llvm::StringLiteral ConstantsScope = "constants";
// Stands in for the scope of a top-level construct we didn't recognize, so
// that its names are still findable by the whole-block fallback.
static constexpr llvm::StringLiteral UnknownScope = "<unknown scope>";
// The text introducing the braces that hold a declared entity's names, as in
// `%F.decl: %F.type = fn_decl @F [concrete = constants.%F] { ... }`.
static constexpr llvm::StringLiteral DeclMarker = "_decl @";
// Separates the name from the value in a row of a `specific` block.
static constexpr llvm::StringLiteral SpecificSeparator = " => ";
// Starts the part of an interface or named constraint body that is written in
// the entity's `WithSelf` scope.
static constexpr llvm::StringLiteral WithSelfLabel = "!with Self:";
// Ends a `WithSelfLabel` region, returning to the entity's own scope.
static constexpr llvm::StringLiteral MembersLabel = "!members:";
// Appended to an entity's scope name to get the scope of its `!with Self:`
// region.
static constexpr llvm::StringLiteral WithSelfSuffix = ".WithSelf";
// Returns whether `c` can appear in a SemIR name or scope name. Note `.` is
// included: names are built from dot-separated segments, and a name can start
// with one, as in `%.loc12_34.1` and `%.Self`.
static auto IsNameChar(char c) -> bool {
return llvm::isAlnum(c) || c == '_' || c == '.';
}
// Returns the end of the `%name` whose `%` is at `text[percent]`, or
// `percent + 1` if no name follows it.
static auto FindNameEnd(llvm::StringRef text, int percent) -> int {
int end = percent + 1;
while (end < static_cast<int>(text.size()) && IsNameChar(text[end])) {
++end;
}
// A trailing `.` belongs to the surrounding text rather than to the name.
while (end > percent + 1 && text[end - 1] == '.') {
--end;
}
return end;
}
// Returns the source location a name's `.loc<line>[_<column>]` suffix refers
// to, or `nullopt` if it has no such suffix.
static auto ParseLocSuffix(llvm::StringRef name) -> std::optional<SourceLoc> {
auto [rest, last] = name.rsplit('.');
// An all-digits final segment is a disambiguating counter, such as the `.1`
// of `%T.loc5_16.1`, so the location is the segment before it.
if (!last.empty() && llvm::all_of(last, llvm::isDigit)) {
std::tie(rest, last) = rest.rsplit('.');
}
if (!last.consume_front("loc")) {
return std::nullopt;
}
auto [line_text, column_text] = last.split('_');
int line = 0;
if (line_text.getAsInteger(10, line) || line <= 0) {
return std::nullopt;
}
// A location with no column, such as `%i32.loc5`, refers to the whole line.
int column = 1;
if (!column_text.empty() &&
(column_text.getAsInteger(10, column) || column <= 0)) {
return std::nullopt;
}
return SourceLoc{.line = line, .column = column - 1};
}
// Returns the `@name` starting at `text[at]`, or an empty string if there
// isn't one there.
static auto ParseEntityName(llvm::StringRef text, size_t at)
-> llvm::StringRef {
size_t end = at + 1;
while (end < text.size() && IsNameChar(text[end])) {
++end;
}
if (end == at + 1) {
return llvm::StringRef();
}
return text.substr(at, end - at);
}
// Returns the scope that a top-level line introduces: a scope keyword for
// `constants {` and friends, and otherwise the entity name.
//
// The entity name is the first `@name` on the line, which works because every
// construct names itself before it mentions anything else:
// `generic fn @F(%T: type) {` introduces `@F` however its parameters are
// spelled, and `specific @F(constants.%i32) {` is written in `@F` too, because
// the names it maps are `@F`'s.
static auto ScopeNameFor(llvm::StringRef line) -> llvm::StringRef {
for (llvm::StringRef keyword :
{"constants", "imports", "generated", "file"}) {
if (line.starts_with(keyword) &&
line.drop_front(keyword.size()).ltrim(' ').starts_with("{")) {
return keyword;
}
}
size_t at = line.find('@');
if (at == llvm::StringRef::npos) {
return UnknownScope;
}
auto name = ParseEntityName(line, at);
return name.empty() ? llvm::StringRef(UnknownScope) : name;
}
// Returns the entity scope that the braces on `line` hold, or an empty string
// if they hold the same scope as the line itself.
//
// The only lines that change scope without saying so are the declarations:
// the braces of `%F.decl: %F.type = fn_decl @F [...] { ... } { ... }` are
// lexically inside `file`, but the names they define belong to `@F`.
static auto DeclScopeFor(llvm::StringRef line) -> llvm::StringRef {
size_t decl = line.find(DeclMarker);
if (decl == llvm::StringRef::npos) {
return llvm::StringRef();
}
return ParseEntityName(line, decl + DeclMarker.size() - 1);
}
// Returns whether a block's output looks like formatted SemIR, rather than
// some other part of the toolchain's output such as a lowering test's LLVM IR
// or a parse test's YAML.
static auto IsSemIR(llvm::ArrayRef<ContentLine> lines) -> bool {
for (const auto& line : lines) {
llvm::StringRef text = line.text;
if (text == "constants {" || text == "imports {" || text == "generated {" ||
text == "file {") {
return true;
}
// A file whose top-level scopes are all empty still prints its entities.
for (llvm::StringRef keyword :
{"generic ", "specific ", "fn @", "class @", "interface @",
"constraint @", "impl @", "final impl @", "vtable @"}) {
if (text.starts_with(keyword)) {
return true;
}
}
}
return false;
}
// Reads the formatted SemIR of one `--- file.carbon` block, and adds the
// definitions and references it finds to the index.
class SemIRText::BlockReader {
public:
explicit BlockReader(SemIRText& index, int32_t block)
: index_(&index), block_(block) {}
auto Read(llvm::ArrayRef<ContentLine> lines) -> void {
lines_ = lines;
for (int index = 0, size = lines.size(); index < size; ++index) {
ReadLine(index);
}
// The output can be truncated mid-definition, so close whatever is still
// open at the end of the block rather than dropping it.
while (!open_definitions_.empty()) {
FinishDefinition(open_definitions_.pop_back_val(), lines.size() - 1);
}
// References are resolved only once the whole block has been read, because
// a name can be used before the line that defines it.
Resolve();
}
private:
// Where the parts of a line that need separate treatment are, as offsets
// into the line's body.
struct LineParts {
// The `<type>` of a `%name: <type> = ...`, which is written in the
// `constants` scope rather than in the line's own scope. `-1` for both if
// the line has no type annotation.
int type_begin = -1;
int type_end = -1;
// The `specific` row this line is, as an index into `specific_values_`, or
// -1 if it isn't one.
int32_t specific_value = -1;
// Where the value of a `specific` row starts, or -1.
int value_begin = -1;
};
// A definition whose brace group is still open.
struct OpenDefinition {
// Index into `definitions_`.
int32_t definition;
// Index into `lines_` of the line the definition starts on.
int start_line;
// The brace depth just before that line, which the depth returns to when
// the definition's last brace group closes.
int depth;
};
auto ReadLine(int index) -> void;
auto ReadNames(const ContentLine& line, int body_offset, llvm::StringRef body,
llvm::StringRef scope, const LineParts& parts) -> void;
auto AddDefinition(llvm::StringRef name, llvm::StringRef scope,
clang::clangd::Range range, llvm::StringRef text)
-> int32_t;
auto FinishDefinition(const OpenDefinition& open, int end_line) -> void;
auto Resolve() -> void;
auto LookupIn(llvm::StringRef scope, llvm::StringRef name) const
-> std::optional<int32_t>;
// Updates `scope_stack_` for the braces on `text`, pushing `nested_scope`
// for each `{`. Text inside a string literal is skipped, so that a brace in
// a name or path doesn't unbalance the stack.
auto UpdateScopeStack(llvm::StringRef text, llvm::StringRef nested_scope)
-> void {
bool in_string = false;
for (size_t i = 0; i < text.size(); ++i) {
char c = text[i];
if (in_string) {
if (c == '\\') {
++i;
} else if (c == '"') {
in_string = false;
}
} else if (c == '"') {
in_string = true;
} else if (c == '{') {
scope_stack_.push_back({.scope = nested_scope});
} else if (c == '}' && !scope_stack_.empty()) {
scope_stack_.pop_back();
}
}
}
// Handles the labels that change the scope of the rest of the brace group
// they're in. An interface or named constraint body switches to the
// `@Entity.WithSelf` scope at `!with Self:`, and back at `!members:`.
auto UpdateScopeForLabel(llvm::StringRef body) -> void {
if (scope_stack_.empty()) {
return;
}
OpenScope& open = scope_stack_.back();
if (body == WithSelfLabel) {
open.saved_scope = open.scope;
open.scope =
index_->strings_.save(llvm::Twine(open.scope) + WithSelfSuffix);
} else if (body == MembersLabel && !open.saved_scope.empty()) {
open.scope = open.saved_scope;
open.saved_scope = llvm::StringRef();
}
}
SemIRText* index_;
int32_t block_;
// The content lines of the block being read.
llvm::ArrayRef<ContentLine> lines_;
// An open brace group and the scope its contents are written in.
struct OpenScope {
llvm::StringRef scope;
// The scope to return to at `!members:`, or empty if we haven't passed a
// `!with Self:` in this brace group.
llvm::StringRef saved_scope;
};
// The scope of each open brace. Empty at the top level of the block, where
// the next line introduces a scope of its own.
llvm::SmallVector<OpenScope> scope_stack_;
// Definitions whose brace groups haven't closed yet, innermost last.
llvm::SmallVector<OpenDefinition> open_definitions_;
// The header of the `specific` block being read, such as
// `specific @F(constants.%i32)`, or empty if we aren't in one.
llvm::StringRef specific_;
llvm::SmallVector<PendingRef> pending_;
};
auto SemIRText::BlockReader::ReadLine(int index) -> void {
const ContentLine& line = lines_[index];
llvm::StringRef text = line.text;
llvm::StringRef body = text.ltrim(' ');
int body_offset = text.size() - body.size();
// A label can change the scope of the rest of the brace group it's in, and
// has no names or braces of its own, so handle it before anything else.
if (body.starts_with("!")) {
UpdateScopeForLabel(body);
return;
}
// The scope the names on this line are written in, and the scope that any
// braces on it open.
llvm::StringRef scope;
llvm::StringRef nested_scope;
if (scope_stack_.empty()) {
if (body.empty()) {
return;
}
// A construct at the top level of the block introduces its own scope.
scope = ScopeNameFor(body);
nested_scope = scope;
specific_ =
body.starts_with("specific ") ? body.rtrim(" {") : llvm::StringRef();
} else {
scope = scope_stack_.back().scope;
nested_scope = DeclScopeFor(body);
if (nested_scope.empty()) {
nested_scope = scope;
}
}
// A line that starts with a name either defines it or, in a `specific`
// block, gives it a value.
LineParts parts;
int32_t definition = -1;
if (body.starts_with("%")) {
int name_end = FindNameEnd(body, 0);
llvm::StringRef name = body.substr(1, name_end - 1);
llvm::StringRef rest = body.substr(name_end);
clang::clangd::Range range = {
.start = {.line = line.line, .character = line.column + body_offset},
.end = {.line = line.line,
.character = line.column + body_offset + name_end}};
if (!name.empty() && !specific_.empty() &&
rest.starts_with(SpecificSeparator)) {
// A `specific` block doesn't define names; it maps its generic's names
// to the values they take, so this is a reference into the generic.
parts.specific_value = index_->specific_values_.size();
parts.value_begin = name_end + SpecificSeparator.size();
index_->specific_values_.push_back(
{.info = {.range = range,
.specific = specific_,
.value = body.substr(parts.value_begin)}});
} else if (!name.empty()) {
definition = AddDefinition(name, scope, range, body);
// In `%name: <type> = ...`, the type is written in the `constants`
// scope: the formatter switches scope to print it, because a type is
// nearly always a constant.
if (rest.starts_with(":")) {
size_t equals = body.find(" = ", name_end);
if (equals != llvm::StringRef::npos) {
parts.type_begin = name_end + 1;
parts.type_end = equals;
}
}
}
}
ReadNames(line, body_offset, body, scope, parts);
int depth = scope_stack_.size();
UpdateScopeStack(text, nested_scope);
// A definition that leaves a brace group open continues onto later lines,
// and isn't complete until the depth comes back to where it started. Note a
// `fn_decl` has two brace groups, and the line between them dips to the
// starting depth and back; because the depth is only checked once per line,
// that doesn't end the definition.
if (definition >= 0 && static_cast<int>(scope_stack_.size()) > depth) {
open_definitions_.push_back(
{.definition = definition, .start_line = index, .depth = depth});
}
while (!open_definitions_.empty() && static_cast<int>(scope_stack_.size()) <=
open_definitions_.back().depth) {
FinishDefinition(open_definitions_.pop_back_val(), index);
}
}
auto SemIRText::BlockReader::FinishDefinition(const OpenDefinition& open,
int end_line) -> void {
llvm::StringRef first = lines_[open.start_line].text;
size_t indent = first.size() - first.ltrim(' ').size();
// Each line is a separate `// CHECK:STDOUT:` line in the document, so the
// text has to be rebuilt rather than pointed at.
RawStringOstream text;
llvm::ListSeparator newline("\n");
for (int i = open.start_line; i <= end_line; ++i) {
llvm::StringRef line = lines_[i].text;
text << newline
<< (line.size() >= indent ? line.drop_front(indent) : line.ltrim(' '));
}
index_->definitions_[open.definition].info.text =
index_->strings_.save(text.TakeStr());
}
auto SemIRText::BlockReader::ReadNames(const ContentLine& line, int body_offset,
llvm::StringRef body,
llvm::StringRef scope,
const LineParts& parts) -> void {
int size = body.size();
for (int i = 0; i < size; ++i) {
if (body[i] != '%') {
continue;
}
int name_end = FindNameEnd(body, i);
if (name_end == i + 1) {
continue;
}
// A name from another scope is written `scope.%name`, where the scope is
// either a keyword or an `@`-prefixed entity name.
int begin = i;
llvm::StringRef explicit_scope;
if (i > 0 && body[i - 1] == '.') {
int scope_begin = i - 1;
while (scope_begin > 0 && IsNameChar(body[scope_begin - 1])) {
--scope_begin;
}
if (scope_begin > 0 && body[scope_begin - 1] == '@') {
--scope_begin;
}
if (scope_begin < i - 1) {
explicit_scope = body.substr(scope_begin, i - 1 - scope_begin);
begin = scope_begin;
}
}
int column = line.column + body_offset;
// A `specific` row's value gets linked to the row only when this name
// makes up the whole of it, so that a compound value such as
// `%A.type (%A)` isn't reported as the definition of the row.
bool is_whole_value =
begin == parts.value_begin && name_end == static_cast<int>(body.size());
pending_.push_back(
{.ref = {.line = line.line,
.column_begin = column + begin,
.column_end = column + name_end,
.block = block_,
.name = body.substr(i + 1, name_end - i - 1),
.definitions_begin = 0,
.definitions_size = 0},
.explicit_scope = explicit_scope,
.context_scope = scope,
.in_type = i >= parts.type_begin && name_end <= parts.type_end,
.specific_value = i == 0 ? parts.specific_value : -1,
.value_of_specific = is_whole_value ? parts.specific_value : -1});
i = name_end - 1;
}
}
auto SemIRText::BlockReader::AddDefinition(llvm::StringRef name,
llvm::StringRef scope,
clang::clangd::Range range,
llvm::StringRef text) -> int32_t {
int32_t index = index_->definitions_.size();
index_->definitions_.push_back(
{.info = {.range = range, .scope = scope, .text = text}});
// Names are unique within a scope, but the output can be truncated or
// regex-substituted, so keep the first of any duplicates.
index_->blocks_[block_].scopes[scope].insert({name, index});
return index;
}
auto SemIRText::BlockReader::LookupIn(llvm::StringRef scope,
llvm::StringRef name) const
-> std::optional<int32_t> {
const auto& scopes = index_->blocks_[block_].scopes;
auto scope_it = scopes.find(scope);
if (scope_it == scopes.end()) {
return std::nullopt;
}
auto name_it = scope_it->second.find(name);
if (name_it == scope_it->second.end()) {
return std::nullopt;
}
return name_it->second;
}
auto SemIRText::BlockReader::Resolve() -> void {
for (auto& pending : pending_) {
llvm::StringRef name = pending.ref.name;
llvm::SmallVector<int32_t, 1> definitions;
if (!pending.explicit_scope.empty()) {
if (auto index = LookupIn(pending.explicit_scope, name)) {
definitions.push_back(*index);
}
} else {
// A bare name usually comes from the scope of the line it's written on,
// except in a type annotation, where it comes from `constants`. Try the
// likely scope first, then the other one.
llvm::StringRef scopes[] = {pending.context_scope, ConstantsScope};
if (pending.in_type) {
std::swap(scopes[0], scopes[1]);
}
for (llvm::StringRef scope : scopes) {
if (auto index = LookupIn(scope, name)) {
definitions.push_back(*index);
break;
}
}
}
if (definitions.empty()) {
// The formatter has a few more places where it switches scope without
// saying so, such as the constant value of a symbolic binding, which is
// written in the generic that owns it, and it doesn't always print the
// contents of a scope it names. Rather than model each of them, search
// the whole block and report everything that matches.
for (const auto& scope : index_->blocks_[block_].scopes) {
auto it = scope.second.find(name);
if (it != scope.second.end()) {
definitions.push_back(it->second);
}
}
// `StringMap` iteration order is unspecified; sorting puts the
// definitions back into the order they were written in.
llvm::sort(definitions);
}
pending.ref.definitions_begin = index_->ref_definitions_.size();
pending.ref.definitions_size = definitions.size();
llvm::append_range(index_->ref_definitions_, definitions);
index_->refs_.push_back(pending.ref);
if (pending.specific_value >= 0) {
for (int32_t index : definitions) {
index_->definitions_[index].specific_values.push_back(
pending.specific_value);
}
}
// An ambiguous value would be misleading to show as *the* definition, so
// only record one we resolved to exactly one place.
if (pending.value_of_specific >= 0 && definitions.size() == 1) {
index_->specific_values_[pending.value_of_specific].value_definition =
definitions.front();
}
}
}
// Recovers the formatted SemIR from the `// CHECK:STDOUT:` lines of `text`,
// and records the line each `// --- file.carbon` split was written on.
static auto ExtractContent(llvm::StringRef text,
llvm::SmallVectorImpl<ContentLine>& content,
llvm::StringMap<int>& split_lines) -> void {
llvm::SmallVector<llvm::StringRef> lines;
text.split(lines, '\n');
for (auto [index, line] : llvm::enumerate(lines)) {
llvm::StringRef body = line.rtrim('\r');
int indent = body.size();
body = body.ltrim(' ');
indent -= body.size();
if (body.consume_front(CheckPrefix)) {
int column = indent + CheckPrefix.size();
// An empty line of output has no space after the prefix.
if (body.consume_front(" ")) {
++column;
}
content.push_back(
{.line = static_cast<int>(index), .column = column, .text = body});
} else if (body.consume_front(SplitPrefix)) {
split_lines.insert({body.trim(), static_cast<int>(index)});
}
}
}
// Returns what to add to a SemIR line number to get the 0-based document line
// of the corresponding source text.
static auto FindSourceLineBase(llvm::StringRef name,
const llvm::StringMap<int>& split_lines)
-> std::optional<int> {
// Line 1 of a split is the line after its `// --- file.carbon` marker.
if (auto it = split_lines.find(name); it != split_lines.end()) {
return it->second;
}
if (split_lines.empty()) {
// The test file isn't split, so it is itself the input file, and SemIR
// line numbers are 1-based document line numbers.
return -1;
}
// The input was pulled in by `// INCLUDE-FILE`, so its text is in a
// different document, which we have no way to refer to from here.
return std::nullopt;
}
SemIRText::SemIRText(llvm::StringRef text) {
llvm::SmallVector<ContentLine> content;
llvm::StringMap<int> split_lines;
ExtractContent(text, content, split_lines);
// Each `--- file.carbon` block is a separate compilation, so names in one
// never refer to names in another. Read them independently.
for (size_t begin = 0; begin < content.size();) {
llvm::StringRef name;
if (content[begin].text.starts_with(BlockPrefix)) {
name = content[begin].text.drop_front(BlockPrefix.size()).trim();
++begin;
}
size_t end = begin;
while (end < content.size() &&
!content[end].text.starts_with(BlockPrefix)) {
++end;
}
auto lines = llvm::ArrayRef(content).slice(begin, end - begin);
if (IsSemIR(lines)) {
blocks_.push_back(
{.source_line_base = FindSourceLineBase(name, split_lines)});
BlockReader(*this, blocks_.size() - 1).Read(lines);
}
begin = end;
}
// Blocks and lines are read in order, so this is nearly always a no-op, but
// `Lookup` depends on it.
llvm::stable_sort(refs_, [](const Ref& lhs, const Ref& rhs) {
return std::pair(lhs.line, lhs.column_begin) <
std::pair(rhs.line, rhs.column_begin);
});
}
auto SemIRText::FindRef(clang::clangd::Position position) const -> const Ref* {
// References are sorted by position, so only the last one starting at or
// before `position` can contain it.
auto after = llvm::partition_point(refs_, [&](const Ref& ref) {
return std::pair(ref.line, ref.column_begin) <=
std::pair(position.line, position.character);
});
if (after == refs_.begin()) {
return nullptr;
}
const Ref& ref = *std::prev(after);
if (ref.line != position.line || position.character >= ref.column_end) {
return nullptr;
}
return &ref;
}
auto SemIRText::Lookup(clang::clangd::Position position) const
-> std::optional<SemIRTextNameRef> {
const Ref* ref = FindRef(position);
if (!ref) {
return std::nullopt;
}
auto range_of = [](const Ref& ref) -> clang::clangd::Range {
return {.start = {.line = ref.line, .character = ref.column_begin},
.end = {.line = ref.line, .character = ref.column_end}};
};
auto definitions_of = [&](const Ref& ref) -> llvm::ArrayRef<int32_t> {
return llvm::ArrayRef(ref_definitions_)
.slice(ref.definitions_begin, ref.definitions_size);
};
SemIRTextNameRef result = {.range = range_of(*ref)};
for (int32_t index : definitions_of(*ref)) {
const Definition& definition = definitions_[index];
result.definitions.push_back(definition.info);
for (int32_t value : definition.specific_values) {
const SpecificValue& specific_value = specific_values_[value];
result.specific_values.push_back(specific_value.info);
if (specific_value.value_definition >= 0) {
result.specific_values.back().value_definition =
definitions_[specific_value.value_definition].info;
}
}
}
// A name's `.loc<line>_<column>` suffix names the source text the
// instruction was checked from, in the input file this block was compiled
// from.
if (auto loc = ParseLocSuffix(ref->name)) {
if (auto base = blocks_[ref->block].source_line_base) {
result.source_position = {.line = *base + loc->line,
.character = loc->column};
}
}
for (const Ref& other : refs_) {
// Two names denote the same thing when they resolve to the same
// definitions. When neither resolves, fall back to matching the text,
// which is still better than reporting nothing.
bool same = definitions_of(*ref).empty() && definitions_of(other).empty()
? ref->name == other.name
: definitions_of(*ref) == definitions_of(other);
if (other.block == ref->block && same) {
result.occurrences.push_back(range_of(other));
}
}
return result;
}
} // namespace Carbon::LanguageServer
+220
View File
@@ -0,0 +1,220 @@
// 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
#ifndef CARBON_TOOLCHAIN_LANGUAGE_SERVER_SEM_IR_TEXT_H_
#define CARBON_TOOLCHAIN_LANGUAGE_SERVER_SEM_IR_TEXT_H_
#include <optional>
#include <vector>
#include "clang-tools-extra/clangd/Protocol.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/StringSaver.h"
namespace Carbon::LanguageServer {
// The formatted SemIR that introduces a name, such as
// `%i32: type = class_type @Int, @Int(%int_32) [concrete]`.
struct SemIRTextDefinition {
// The range of the `%name` the definition introduces.
clang::clangd::Range range;
// The scope the name belongs to: a keyword such as `constants`, or an entity
// name such as `@F`.
llvm::StringRef scope;
// The defining text, with the indentation of its first line removed. This is
// more than one line when the definition opens a brace group, as
// `%foo: <namespace> = namespace [concrete] { ... }` does, in which case it
// runs to the matching closing brace.
llvm::StringRef text;
};
// A `%name => value` row of a `specific` block, giving the value that one name
// from a generic takes in one particular specific.
struct SemIRTextSpecificValue {
// The range of the row's `%name`.
clang::clangd::Range range;
// The header of the enclosing `specific` block, such as
// `specific @F(constants.%i32)`.
llvm::StringRef specific;
// The text to the right of the `=>`.
llvm::StringRef value;
// Where `value` is defined, when it is a single name reference that resolves
// unambiguously. A specific's value is nearly always `constants.%something`,
// and the name on its own says little, so this is what a reader wants to see.
std::optional<SemIRTextDefinition> value_definition;
};
// What a `%name` written at some position in formatted SemIR refers to.
struct SemIRTextNameRef {
// The range of the reference, including any `scope.` prefix.
clang::clangd::Range range;
// The definitions the name resolves to. Normally one; none if we couldn't
// resolve it, and more than one if the name is ambiguous.
llvm::SmallVector<SemIRTextDefinition, 1> definitions;
// The value this name takes in each `specific` of the generic that defines
// it. Empty unless the name is defined in a generic that has specifics.
llvm::SmallVector<SemIRTextSpecificValue, 0> specific_values;
// Where in this document the source text named by the name's
// `.loc<line>_<column>` suffix lives. `nullopt` if the name has no such
// suffix, or if we couldn't work out where the input file it refers to was
// written.
std::optional<clang::clangd::Position> source_position;
// Every place this name is written within the same output block, including
// its definition and this reference itself.
llvm::SmallVector<clang::clangd::Range, 1> occurrences;
};
// An index of the formatted SemIR that a toolchain test file carries in its
// `// CHECK:STDOUT:` lines.
//
// This is a heuristic reader, not a parser: there is no grammar for formatted
// SemIR, and the format is free to change. It recognizes just enough structure
// to answer position-based requests -- which names exist, which scope each one
// belongs to, and where each is written -- and silently ignores anything it
// doesn't understand. A test file whose expected output isn't SemIR at all,
// such as a lowering test's LLVM IR, is skipped by `--- file` block rather
// than producing nonsense.
//
// The interesting structure, and the reason a plain text search isn't enough,
// is scoping. A name is written `scope.%name` when it comes from another scope
// and bare `%name` when it comes from the current one, so resolving a name
// means knowing which scope the line it's written on belongs to. That isn't
// simply the innermost `{`: the braces of a `fn_decl @F` sit inside `file` but
// hold `@F`'s names, and a type annotation is written in the `constants` scope
// whatever scope encloses it. Where the scope is still ambiguous, we fall back
// to searching the whole output block, and report every match.
class SemIRText {
public:
// Indexes the formatted SemIR in `text`, which must outlive this object.
explicit SemIRText(llvm::StringRef text);
// Returns whether any formatted SemIR was found.
auto empty() const -> bool { return refs_.empty(); }
// Returns what the name written at `position` refers to, or `nullopt` if
// `position` isn't within a `%name` in formatted SemIR.
auto Lookup(clang::clangd::Position position) const
-> std::optional<SemIRTextNameRef>;
private:
// A name defined within one scope of one output block.
struct Definition {
SemIRTextDefinition info;
// Indices into `specific_values_` of the rows giving this name's value in
// each `specific` of the generic that defines it.
llvm::SmallVector<int32_t, 0> specific_values;
};
// A `%name => value` row of a `specific` block.
struct SpecificValue {
SemIRTextSpecificValue info;
// Where `info.value` is defined, as an index into `definitions_`, or -1 if
// the value isn't a single name that resolved unambiguously.
int32_t value_definition = -1;
};
// One `--- file.carbon` section of the output. Sections are independent:
// each is a separate compilation with its own names, so a name in one never
// refers to a name in another.
struct Block {
// Definitions by scope name, then by name. Indices into `definitions_`.
llvm::StringMap<llvm::StringMap<int32_t>> scopes;
// Added to a 1-based SemIR line number to get the 0-based document line of
// the corresponding source text. `nullopt` if we couldn't find where this
// block's input file was written, which happens when it came from an
// `// INCLUDE-FILE` rather than from a `// --- file.carbon` split.
std::optional<int> source_line_base;
};
// A `%name`, or `scope.%name`, written somewhere in the output.
struct Ref {
// Position in the document. `refs_` is sorted by these.
int32_t line;
int32_t column_begin;
int32_t column_end;
// The enclosing `--- file.carbon` section, as an index into `blocks_`.
int32_t block;
// The name, without the `%`.
llvm::StringRef name;
// The definitions this resolves to, as the range
// `ref_definitions_[definitions_begin ..][0 .. definitions_size)` of
// indices into `definitions_`.
int32_t definitions_begin;
int32_t definitions_size;
};
// A reference found while reading, kept until the whole block has been read
// so that it can be resolved against names defined later in the block.
struct PendingRef {
Ref ref;
// The scope written before the `%`, or empty if the name was unqualified.
llvm::StringRef explicit_scope;
// The scope of the line the name is written on, used to resolve a name
// that has no `explicit_scope`.
llvm::StringRef context_scope;
// Whether the name sits in a type annotation, which is written in the
// `constants` scope rather than in `context_scope`.
bool in_type;
// The specific-value row this name labels, as an index into
// `specific_values_`, or -1. Set for the `%name` of a `%name => value` row
// of a `specific` block, which names an instruction of the generic.
int32_t specific_value = -1;
// The specific-value row this name is the value of, as an index into
// `specific_values_`, or -1. Set when a name makes up the whole of the
// right side of a `%name => value` row, so that resolving the name also
// tells us where that row's value is defined.
int32_t value_of_specific = -1;
};
// Reads the SemIR of one `--- file.carbon` section, and appends what it
// finds to the index. `lines` is that section's content lines, excluding the
// `--- file.carbon` line itself.
class BlockReader;
auto FindRef(clang::clangd::Position position) const -> const Ref*;
std::vector<Block> blocks_;
std::vector<Definition> definitions_;
std::vector<SpecificValue> specific_values_;
// References, sorted by position, so that a lookup is a binary search.
std::vector<Ref> refs_;
// The definitions each `Ref` resolves to; see `Ref::definitions_begin`.
std::vector<int32_t> ref_definitions_;
// Holds text that we build rather than point at in the document: the
// `@Entity.WithSelf` scope name of a `!with Self:` region, and the text of a
// definition that spans more than one line, which has to have each line's
// `// CHECK:STDOUT:` prefix removed before they can be joined.
llvm::BumpPtrAllocator allocator_;
llvm::StringSaver strings_ = llvm::StringSaver(allocator_);
};
} // namespace Carbon::LanguageServer
#endif // CARBON_TOOLCHAIN_LANGUAGE_SERVER_SEM_IR_TEXT_H_
@@ -15,7 +15,7 @@
// CHECK:STDERR: error: Input/output error [LanguageServerTransportError]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -27,6 +27,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -20,7 +20,7 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 393{{\r}}
// CHECK:STDOUT: Content-Length: 431{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -32,13 +32,14 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-8",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 394{{\r}}
// CHECK:STDOUT: }Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 2,
@@ -50,13 +51,14 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 394{{\r}}
// CHECK:STDOUT: }Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 3,
@@ -68,6 +70,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -19,7 +19,7 @@
// CHECK:STDERR: warning: -32602: in call to `textDocument/didOpen`, JSON parse failed: missing value at (root).textDocument [LanguageServerNotificationParseError]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -31,6 +31,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -36,7 +36,7 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -48,6 +48,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -30,7 +30,7 @@ choice Colors {
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -42,6 +42,7 @@ choice Colors {
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -33,7 +33,7 @@ fn G() {}
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -45,6 +45,7 @@ fn G() {}
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -29,7 +29,7 @@ fn Builtin() = "int.make_type_32";
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -41,6 +41,7 @@ fn Builtin() = "int.make_type_32";
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -27,7 +27,7 @@ class Incomplete {
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -39,6 +39,7 @@ class Incomplete {
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -20,7 +20,7 @@
// CHECK:STDERR: /test.cpp: warning: non-Carbon file requested [LanguageServerFileUnsupported]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -32,6 +32,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -28,7 +28,7 @@ fn Bar() {}
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -40,6 +40,7 @@ fn Bar() {}
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -20,7 +20,7 @@
// CHECK:STDERR: /test.carbon: warning: unknown file requested [LanguageServerFileUnknown]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -32,6 +32,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
+2 -1
View File
@@ -37,7 +37,7 @@
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -49,6 +49,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -21,7 +21,7 @@
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -33,6 +33,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -19,7 +19,7 @@
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -31,6 +31,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -51,7 +51,7 @@
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -63,6 +63,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -59,7 +59,7 @@ fn Run() {
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -71,6 +71,7 @@ fn Run() {
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -0,0 +1,600 @@
// 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
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/position/sem_ir_text.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/position/sem_ir_text.carbon
// Navigation within the SemIR in a test file's `// CHECK:STDOUT:` lines.
//
// The split below is the document under test, not part of this test: `CHECK`
// lines only form expectations inside the `AUTOUPDATE-SPLIT`, and the
// document's own `// ---` marker is written as `[[@0x2f]]/ ---` so that it
// doesn't split this file.
// --- sem_ir_text.carbon
[[@0x2f]]/ --- a.carbon
library "a";
class C {}
class D {}
//@dump-sem-ir-begin
fn F[T: type](x: T) {}
//@dump-sem-ir-end
fn Run(c: C, d: D) {
F(c);
F(d);
}
// CHECK:STDOUT: --- a.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %C: type = class_type @C [concrete]
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
// CHECK:STDOUT: %D: type = class_type @D [concrete]
// CHECK:STDOUT: %type: type = facet_type <type> [concrete]
// CHECK:STDOUT: %.Self.frozen: %type = symbolic_binding .Self [symbolic_self]
// CHECK:STDOUT: %pattern_type.98f: type = pattern_type type [concrete]
// CHECK:STDOUT: %T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [symbolic]
// CHECK:STDOUT: %T: type = symbolic_binding T, 0 [symbolic]
// CHECK:STDOUT: %pattern_type.51d: type = pattern_type %T [symbolic]
// CHECK:STDOUT: %x.param_patt.91d: %pattern_type.51d = value_param_pattern [symbolic]
// CHECK:STDOUT: %x.patt.260: %pattern_type.51d = wrapper_binding_pattern x, %x.param_patt.91d [symbolic]
// CHECK:STDOUT: %F.type: type = fn_type @F [concrete]
// CHECK:STDOUT: %F: %F.type = struct_value () [concrete]
// CHECK:STDOUT: %require_complete: <witness> = require_complete_type %T [symbolic]
// CHECK:STDOUT: %pattern_type.98b: type = pattern_type %C [concrete]
// CHECK:STDOUT: %pattern_type.d8d: type = pattern_type %D [concrete]
// CHECK:STDOUT: %x.param_patt.0f9: %pattern_type.98b = value_param_pattern [concrete]
// CHECK:STDOUT: %x.patt.953: %pattern_type.98b = wrapper_binding_pattern x, %x.param_patt.0f9 [concrete]
// CHECK:STDOUT: %x.param_patt.23b: %pattern_type.d8d = value_param_pattern [concrete]
// CHECK:STDOUT: %x.patt.c51: %pattern_type.d8d = wrapper_binding_pattern x, %x.param_patt.23b [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
// CHECK:STDOUT: %T.patt.loc7_7.1: %pattern_type.98f = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc7_7.2 (constants.%T.patt)]
// CHECK:STDOUT: %x.param_patt.loc7_16.1: @F.%pattern_type (%pattern_type.51d) = value_param_pattern [symbolic = %x.param_patt.loc7_16.2 (constants.%x.param_patt.91d)]
// CHECK:STDOUT: %x.patt.loc7_16.1: @F.%pattern_type (%pattern_type.51d) = wrapper_binding_pattern x, %x.param_patt.loc7_16.1 [symbolic = %x.patt.loc7_16.2 (constants.%x.patt.260)]
// CHECK:STDOUT: } {
// CHECK:STDOUT: %.loc7_9.1: type = splice_block %.loc7_9.2 [concrete = type] {
// CHECK:STDOUT: %.Self.frozen: %type = symbolic_binding .Self [symbolic_self = constants.%.Self.frozen]
// CHECK:STDOUT: %.loc7_9.2: type = type_literal type [concrete = type]
// CHECK:STDOUT: }
// CHECK:STDOUT: %T.loc7_7.2: type = symbolic_binding T, 0 [symbolic = %T.loc7_7.1 (constants.%T)]
// CHECK:STDOUT: %x.param: @F.%T.loc7_7.1 (%T) = value_param call_param0
// CHECK:STDOUT: %T.ref: type = name_ref T, %T.loc7_7.2 [symbolic = %T.loc7_7.1 (constants.%T)]
// CHECK:STDOUT: %x: @F.%T.loc7_7.1 (%T) = wrapper_binding x, %x.param
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: generic fn @F(%T.loc7_7.2: type) {
// CHECK:STDOUT: %T.patt.loc7_7.2: %pattern_type.98f = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc7_7.2 (constants.%T.patt)]
// CHECK:STDOUT: %T.loc7_7.1: type = symbolic_binding T, 0 [symbolic = %T.loc7_7.1 (constants.%T)]
// CHECK:STDOUT: %pattern_type: type = pattern_type %T.loc7_7.1 [symbolic = %pattern_type (constants.%pattern_type.51d)]
// CHECK:STDOUT: %x.param_patt.loc7_16.2: @F.%pattern_type (%pattern_type.51d) = value_param_pattern [symbolic = %x.param_patt.loc7_16.2 (constants.%x.param_patt.91d)]
// CHECK:STDOUT: %x.patt.loc7_16.2: @F.%pattern_type (%pattern_type.51d) = wrapper_binding_pattern x, %x.param_patt.loc7_16.2 [symbolic = %x.patt.loc7_16.2 (constants.%x.patt.260)]
// CHECK:STDOUT:
// CHECK:STDOUT: !definition:
// CHECK:STDOUT: %require_complete: <witness> = require_complete_type %T.loc7_7.1 [symbolic = %require_complete (constants.%require_complete)]
// CHECK:STDOUT:
// CHECK:STDOUT: fn(%x.param: @F.%T.loc7_7.1 (%T)) {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: specific @F(constants.%T) {
// CHECK:STDOUT: %T.patt.loc7_7.2 => constants.%T.patt
// CHECK:STDOUT: %T.loc7_7.1 => constants.%T
// CHECK:STDOUT: %pattern_type => constants.%pattern_type.51d
// CHECK:STDOUT: %x.param_patt.loc7_16.2 => constants.%x.param_patt.91d
// CHECK:STDOUT: %x.patt.loc7_16.2 => constants.%x.patt.260
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: specific @F(constants.%C) {
// CHECK:STDOUT: %T.patt.loc7_7.2 => constants.%T.patt
// CHECK:STDOUT: %T.loc7_7.1 => constants.%C
// CHECK:STDOUT: %pattern_type => constants.%pattern_type.98b
// CHECK:STDOUT: %x.param_patt.loc7_16.2 => constants.%x.param_patt.0f9
// CHECK:STDOUT: %x.patt.loc7_16.2 => constants.%x.patt.953
// CHECK:STDOUT:
// CHECK:STDOUT: !definition:
// CHECK:STDOUT: %require_complete => constants.%complete_type
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: specific @F(constants.%D) {
// CHECK:STDOUT: %T.patt.loc7_7.2 => constants.%T.patt
// CHECK:STDOUT: %T.loc7_7.1 => constants.%D
// CHECK:STDOUT: %pattern_type => constants.%pattern_type.d8d
// CHECK:STDOUT: %x.param_patt.loc7_16.2 => constants.%x.param_patt.23b
// CHECK:STDOUT: %x.patt.loc7_16.2 => constants.%x.patt.c51
// CHECK:STDOUT:
// CHECK:STDOUT: !definition:
// CHECK:STDOUT: %require_complete => constants.%complete_type
// CHECK:STDOUT: }
// --- STDIN
[[@LSP-CALL:initialize:"capabilities": {}]]
[[@LSP-NOTIFY:textDocument/didOpen:
"textDocument": {
"uri": "file:/sem_ir_text.carbon",
"languageId": "carbon-testdata",
"text": "FROM_FILE_SPLIT"
}
]]
// An operand with an explicit scope resolves in that scope.
// Line 42, character 71 is the `constants.%F` in:
// // CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"position": {"line": 42, "character": 71}
]]
// A type annotation resolves in `constants`, not the enclosing `file` scope.
// Line 42, character 29 is the `%F.type` in:
// // CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"position": {"line": 42, "character": 29}
]]
// Inside a `fn_decl @F` the scope is `@F`, not the lexically enclosing `file`.
// This name is also defined in a generic and has a `locN_M` suffix, so it has
// specific values and a source location too. Each specific value is shown as the
// definition of the constant it names, not just the name.
// Line 43, character 102 is the `%T.patt.loc7_7.2` in:
// // CHECK:STDOUT: %T.patt.loc7_7.1: %pattern_type.98f = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc7_7.2 (constants.%T.patt)]
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"position": {"line": 43, "character": 102}
]]
// Line 43, character 102 is the `%T.patt.loc7_7.2` in:
// // CHECK:STDOUT: %T.patt.loc7_7.1: %pattern_type.98f = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc7_7.2 (constants.%T.patt)]
[[@LSP-CALL:textDocument/definition:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"position": {"line": 43, "character": 102}
]]
// Line 43, character 102 is the `%T.patt.loc7_7.2` in:
// // CHECK:STDOUT: %T.patt.loc7_7.1: %pattern_type.98f = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc7_7.2 (constants.%T.patt)]
[[@LSP-CALL:textDocument/declaration:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"position": {"line": 43, "character": 102}
]]
// Line 43, character 102 is the `%T.patt.loc7_7.2` in:
// // CHECK:STDOUT: %T.patt.loc7_7.1: %pattern_type.98f = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc7_7.2 (constants.%T.patt)]
[[@LSP-CALL:textDocument/implementation:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"position": {"line": 43, "character": 102}
]]
// Line 43, character 102 is the `%T.patt.loc7_7.2` in:
// // CHECK:STDOUT: %T.patt.loc7_7.1: %pattern_type.98f = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc7_7.2 (constants.%T.patt)]
[[@LSP-CALL:textDocument/references:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"position": {"line": 43, "character": 102},
"context": {"includeDeclaration": true}
]]
// The left side of a `specific` row names an instruction of the generic.
// Line 76, character 20 is the `%T.loc7_7.1` in:
// // CHECK:STDOUT: %T.loc7_7.1 => constants.%T
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"position": {"line": 76, "character": 20}
]]
// Line 76, character 20 is the `%T.loc7_7.1` in:
// // CHECK:STDOUT: %T.loc7_7.1 => constants.%T
[[@LSP-CALL:textDocument/definition:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"position": {"line": 76, "character": 20}
]]
// A definition that opens a brace group runs to its closing brace, and is shown
// in full.
// Line 47, character 22 is the `%.loc7_9.1` in:
// // CHECK:STDOUT: %.loc7_9.1: type = splice_block %.loc7_9.2 [concrete = type] {
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"position": {"line": 47, "character": 22}
]]
// A scope keyword is not an operand.
// Line 17, character 18 is the `constants` in:
// // CHECK:STDOUT: constants {
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"position": {"line": 17, "character": 18}
]]
// Test files aren't compiled, so the Carbon source has no information.
// Line 7, character 3 is the `F[` in:
// fn F[T: type](x: T) {}
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"position": {"line": 7, "character": 3}
]]
// Line 7, character 3 is the `F[` in:
// fn F[T: type](x: T) {}
[[@LSP-CALL:textDocument/definition:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"position": {"line": 7, "character": 3}
]]
// Test files have no parse tree, so these produce empty results rather than
// crashing.
[[@LSP-CALL:textDocument/documentSymbol:
"textDocument": {"uri": "file:/sem_ir_text.carbon"}
]]
[[@LSP-CALL:textDocument/formatting:
"textDocument": {"uri": "file:/sem_ir_text.carbon"},
"options": {"tabSize": 2, "insertSpaces": true}
]]
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 151{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "method": "textDocument/publishDiagnostics",
// CHECK:STDOUT: "params": {
// CHECK:STDOUT: "diagnostics": [],
// CHECK:STDOUT: "uri": "file:///sem_ir_text.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 346{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 2,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "contents": {
// CHECK:STDOUT: "kind": "markdown",
// CHECK:STDOUT: "value": "```semir\nconstants.%F: %F.type = struct_value () [concrete]\n```\n"
// CHECK:STDOUT: },
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 73,
// CHECK:STDOUT: "line": 42
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 61,
// CHECK:STDOUT: "line": 42
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 343{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 3,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "contents": {
// CHECK:STDOUT: "kind": "markdown",
// CHECK:STDOUT: "value": "```semir\nconstants.%F.type: type = fn_type @F [concrete]\n```\n"
// CHECK:STDOUT: },
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 35,
// CHECK:STDOUT: "line": 42
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 28,
// CHECK:STDOUT: "line": 42
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 847{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 4,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "contents": {
// CHECK:STDOUT: "kind": "markdown",
// CHECK:STDOUT: "value": "```semir\n@F.%T.patt.loc7_7.2: %pattern_type.98f = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc7_7.2 (constants.%T.patt)]\n```\n\nSpecific values:\n\n```semir\nspecific @F(constants.%T) =>\n constants.%T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [symbolic]\nspecific @F(constants.%C) =>\n constants.%T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [symbolic]\nspecific @F(constants.%D) =>\n constants.%T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [symbolic]\n```\n\nSource:\n\n```carbon\nfn F[T: type](x: T) {}\n```\n"
// CHECK:STDOUT: },
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 117,
// CHECK:STDOUT: "line": 43
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 101,
// CHECK:STDOUT: "line": 43
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 285{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 5,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": [
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 35,
// CHECK:STDOUT: "line": 59
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 19,
// CHECK:STDOUT: "line": 59
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///sem_ir_text.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: ]
// CHECK:STDOUT: }Content-Length: 281{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 6,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": [
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 6,
// CHECK:STDOUT: "line": 7
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 6,
// CHECK:STDOUT: "line": 7
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///sem_ir_text.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: ]
// CHECK:STDOUT: }Content-Length: 753{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 7,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": [
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 35,
// CHECK:STDOUT: "line": 75
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 19,
// CHECK:STDOUT: "line": 75
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///sem_ir_text.carbon"
// CHECK:STDOUT: },
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 35,
// CHECK:STDOUT: "line": 83
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 19,
// CHECK:STDOUT: "line": 83
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///sem_ir_text.carbon"
// CHECK:STDOUT: },
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 35,
// CHECK:STDOUT: "line": 94
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 19,
// CHECK:STDOUT: "line": 94
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///sem_ir_text.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: ]
// CHECK:STDOUT: }Content-Length: 1458{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 8,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": [
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 117,
// CHECK:STDOUT: "line": 43
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 101,
// CHECK:STDOUT: "line": 43
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///sem_ir_text.carbon"
// CHECK:STDOUT: },
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 35,
// CHECK:STDOUT: "line": 59
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 19,
// CHECK:STDOUT: "line": 59
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///sem_ir_text.carbon"
// CHECK:STDOUT: },
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 115,
// CHECK:STDOUT: "line": 59
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 99,
// CHECK:STDOUT: "line": 59
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///sem_ir_text.carbon"
// CHECK:STDOUT: },
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 35,
// CHECK:STDOUT: "line": 75
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 19,
// CHECK:STDOUT: "line": 75
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///sem_ir_text.carbon"
// CHECK:STDOUT: },
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 35,
// CHECK:STDOUT: "line": 83
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 19,
// CHECK:STDOUT: "line": 83
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///sem_ir_text.carbon"
// CHECK:STDOUT: },
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 35,
// CHECK:STDOUT: "line": 94
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 19,
// CHECK:STDOUT: "line": 94
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///sem_ir_text.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: ]
// CHECK:STDOUT: }Content-Length: 715{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 9,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "contents": {
// CHECK:STDOUT: "kind": "markdown",
// CHECK:STDOUT: "value": "```semir\n@F.%T.loc7_7.1: type = symbolic_binding T, 0 [symbolic = %T.loc7_7.1 (constants.%T)]\n```\n\nSpecific values:\n\n```semir\nspecific @F(constants.%T) =>\n constants.%T: type = symbolic_binding T, 0 [symbolic]\nspecific @F(constants.%C) =>\n constants.%C: type = class_type @C [concrete]\nspecific @F(constants.%D) =>\n constants.%D: type = class_type @D [concrete]\n```\n\nSource:\n\n```carbon\nfn F[T: type](x: T) {}\n```\n"
// CHECK:STDOUT: },
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 30,
// CHECK:STDOUT: "line": 76
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 19,
// CHECK:STDOUT: "line": 76
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 286{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 10,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": [
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 30,
// CHECK:STDOUT: "line": 60
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 19,
// CHECK:STDOUT: "line": 60
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///sem_ir_text.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: ]
// CHECK:STDOUT: }Content-Length: 567{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 11,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "contents": {
// CHECK:STDOUT: "kind": "markdown",
// CHECK:STDOUT: "value": "```semir\n@F.%.loc7_9.1: type = splice_block %.loc7_9.2 [concrete = type] {\n %.Self.frozen: %type = symbolic_binding .Self [symbolic_self = constants.%.Self.frozen]\n %.loc7_9.2: type = type_literal type [concrete = type]\n}\n```\n\nSource:\n\n```carbon\nfn F[T: type](x: T) {}\n```\n"
// CHECK:STDOUT: },
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 31,
// CHECK:STDOUT: "line": 47
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 21,
// CHECK:STDOUT: "line": 47
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 74{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 12,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "contents": null
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 74{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 13,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "contents": null
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 50{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 14,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": []
// CHECK:STDOUT: }Content-Length: 50{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 15,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": []
// CHECK:STDOUT: }Content-Length: 50{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 16,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": []
// CHECK:STDOUT: }Content-Length: 52{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 17,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": null
// CHECK:STDOUT: }
@@ -0,0 +1,253 @@
// 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
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/position/sem_ir_text_with_self.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/position/sem_ir_text_with_self.carbon
// A `!with Self:` label switches an interface body into the interface's
// `WithSelf` scope until the `!members:` label switches it back, without
// opening a brace. See the `SemIRText` reader's label handling.
// --- with_self.carbon
[[@0x2f]]/ --- a.carbon
library "a";
interface I {
fn G();
}
// CHECK:STDOUT: --- a.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %I.type: type = facet_type <@I> [concrete]
// CHECK:STDOUT: %Self: %I.type = symbolic_binding Self, 0 [symbolic]
// CHECK:STDOUT: %I.WithSelf.G.type: type = fn_type @I.WithSelf.G, @I.WithSelf(%Self) [symbolic]
// CHECK:STDOUT: %I.WithSelf.G: %I.WithSelf.G.type = struct_value () [symbolic]
// CHECK:STDOUT: %I.assoc_type: type = assoc_entity_type @I [concrete]
// CHECK:STDOUT: %assoc0: %I.assoc_type = assoc_entity element0, @I.WithSelf.%I.WithSelf.G.decl [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [concrete] {
// CHECK:STDOUT: .I = %I.decl
// CHECK:STDOUT: }
// CHECK:STDOUT: %I.decl: type = interface_decl @I [concrete = constants.%I.type] {} {}
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: interface @I {
// CHECK:STDOUT: %Self: %I.type = symbolic_binding Self, 0 [symbolic = constants.%Self]
// CHECK:STDOUT: %I.WithSelf.decl = interface_with_self_decl @I [concrete]
// CHECK:STDOUT:
// CHECK:STDOUT: !with Self:
// CHECK:STDOUT: %I.WithSelf.G.decl: @I.WithSelf.%I.WithSelf.G.type (%I.WithSelf.G.type) = fn_decl @I.WithSelf.G [symbolic = @I.WithSelf.%I.WithSelf.G (constants.%I.WithSelf.G)] {} {}
// CHECK:STDOUT: %assoc0: %I.assoc_type = assoc_entity element0, %I.WithSelf.G.decl [concrete = constants.%assoc0]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = %Self
// CHECK:STDOUT: .G = @I.WithSelf.%assoc0
// CHECK:STDOUT: witness = (@I.WithSelf.%I.WithSelf.G.decl)
// CHECK:STDOUT:
// CHECK:STDOUT: !requires:
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: generic fn @I.WithSelf.G(@I.%Self: %I.type) {
// CHECK:STDOUT: fn();
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: specific @I.WithSelf(constants.%Self) {}
// CHECK:STDOUT:
// CHECK:STDOUT: specific @I.WithSelf.G(constants.%Self) {}
// --- STDIN
[[@LSP-CALL:initialize:"capabilities": {}]]
[[@LSP-NOTIFY:textDocument/didOpen:
"textDocument": {
"uri": "file:/with_self.carbon",
"languageId": "carbon-testdata",
"text": "FROM_FILE_SPLIT"
}
]]
// A bare name after `!with Self:` resolves in `@I.WithSelf`.
// Line 31, character 68 is the `%I.WithSelf.G.decl` in:
// // CHECK:STDOUT: %assoc0: %I.assoc_type = assoc_entity element0, %I.WithSelf.G.decl [concrete = constants.%assoc0]
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/with_self.carbon"},
"position": {"line": 31, "character": 68}
]]
// `!members:` switches back to `@I`, so an explicit `@I.WithSelf` prefix is
// needed to reach the same name again.
// Line 35, character 37 is the `%assoc0` in:
// // CHECK:STDOUT: .G = @I.WithSelf.%assoc0
[[@LSP-CALL:textDocument/definition:
"textDocument": {"uri": "file:/with_self.carbon"},
"position": {"line": 35, "character": 37}
]]
// The `constants` block reaches into `@I.WithSelf` the same way.
// Line 15, character 80 is the `%I.WithSelf.G.decl` in:
// // CHECK:STDOUT: %assoc0: %I.assoc_type = assoc_entity element0, @I.WithSelf.%I.WithSelf.G.decl [concrete]
[[@LSP-CALL:textDocument/definition:
"textDocument": {"uri": "file:/with_self.carbon"},
"position": {"line": 15, "character": 80}
]]
// A bare name after `!members:` resolves in `@I`, not `@I.WithSelf`.
// Line 34, character 28 is the `%Self` in:
// // CHECK:STDOUT: .Self = %Self
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/with_self.carbon"},
"position": {"line": 34, "character": 28}
]]
// `@I.WithSelf` is named here but the formatter only ever defines this name in
// `constants`, so the whole-block fallback finds it there.
// Line 30, character 52 is the `%I.WithSelf.G.type` in:
// // CHECK:STDOUT: %I.WithSelf.G.decl: @I.WithSelf.%I.WithSelf.G.type (%I.WithSelf.G.type) = fn_decl @I.WithSelf.G [symbolic = @I.WithSelf.%I.WithSelf.G (constants.%I.WithSelf.G)] {} {}
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/with_self.carbon"},
"position": {"line": 30, "character": 52}
]]
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "capabilities": {
// CHECK:STDOUT: "declarationProvider": true,
// CHECK:STDOUT: "definitionProvider": true,
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
// CHECK:STDOUT: "typeDefinitionProvider": true
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 149{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "method": "textDocument/publishDiagnostics",
// CHECK:STDOUT: "params": {
// CHECK:STDOUT: "diagnostics": [],
// CHECK:STDOUT: "uri": "file:///with_self.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 474{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 2,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "contents": {
// CHECK:STDOUT: "kind": "markdown",
// CHECK:STDOUT: "value": "```semir\n@I.WithSelf.%I.WithSelf.G.decl: @I.WithSelf.%I.WithSelf.G.type (%I.WithSelf.G.type) = fn_decl @I.WithSelf.G [symbolic = @I.WithSelf.%I.WithSelf.G (constants.%I.WithSelf.G)] {} {}\n```\n"
// CHECK:STDOUT: },
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 85,
// CHECK:STDOUT: "line": 31
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 67,
// CHECK:STDOUT: "line": 31
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 283{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 3,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": [
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 26,
// CHECK:STDOUT: "line": 31
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 19,
// CHECK:STDOUT: "line": 31
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///with_self.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: ]
// CHECK:STDOUT: }Content-Length: 283{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 4,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": [
// CHECK:STDOUT: {
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 37,
// CHECK:STDOUT: "line": 30
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 19,
// CHECK:STDOUT: "line": 30
// CHECK:STDOUT: }
// CHECK:STDOUT: },
// CHECK:STDOUT: "uri": "file:///with_self.carbon"
// CHECK:STDOUT: }
// CHECK:STDOUT: ]
// CHECK:STDOUT: }Content-Length: 369{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 5,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "contents": {
// CHECK:STDOUT: "kind": "markdown",
// CHECK:STDOUT: "value": "```semir\n@I.%Self: %I.type = symbolic_binding Self, 0 [symbolic = constants.%Self]\n```\n"
// CHECK:STDOUT: },
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 32,
// CHECK:STDOUT: "line": 34
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 27,
// CHECK:STDOUT: "line": 34
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 385{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 6,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": {
// CHECK:STDOUT: "contents": {
// CHECK:STDOUT: "kind": "markdown",
// CHECK:STDOUT: "value": "```semir\nconstants.%I.WithSelf.G.type: type = fn_type @I.WithSelf.G, @I.WithSelf(%Self) [symbolic]\n```\n"
// CHECK:STDOUT: },
// CHECK:STDOUT: "range": {
// CHECK:STDOUT: "end": {
// CHECK:STDOUT: "character": 69,
// CHECK:STDOUT: "line": 30
// CHECK:STDOUT: },
// CHECK:STDOUT: "start": {
// CHECK:STDOUT: "character": 39,
// CHECK:STDOUT: "line": 30
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: }Content-Length: 51{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 7,
// CHECK:STDOUT: "jsonrpc": "2.0",
// CHECK:STDOUT: "result": null
// CHECK:STDOUT: }
@@ -21,7 +21,7 @@
// CHECK:STDERR: /test.carbon: warning: unknown file requested [LanguageServerFileUnknown]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -33,6 +33,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -20,7 +20,7 @@
// CHECK:STDERR: /test.carbon: warning: tried closing unknown file; ignoring request [LanguageServerCloseUnknownFile]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -32,6 +32,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -26,7 +26,7 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -38,6 +38,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -28,7 +28,7 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -40,6 +40,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -107,7 +107,7 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -119,6 +119,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -35,7 +35,7 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -47,6 +47,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -25,7 +25,7 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -37,6 +37,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -26,7 +26,7 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -38,6 +38,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -25,7 +25,7 @@
// CHECK:STDERR: /test.carbon: warning: duplicate open file request; updating content [LanguageServerOpenDuplicateFile]
// CHECK:STDERR:
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -37,6 +37,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
@@ -22,7 +22,7 @@
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: Content-Length: 394{{\r}}
// CHECK:STDOUT: Content-Length: 432{{\r}}
// CHECK:STDOUT: {{\r}}
// CHECK:STDOUT: {
// CHECK:STDOUT: "id": 1,
@@ -34,6 +34,7 @@
// CHECK:STDOUT: "documentFormattingProvider": true,
// CHECK:STDOUT: "documentSymbolProvider": true,
// CHECK:STDOUT: "hoverProvider": true,
// CHECK:STDOUT: "implementationProvider": true,
// CHECK:STDOUT: "positionEncoding": "utf-16",
// CHECK:STDOUT: "referencesProvider": true,
// CHECK:STDOUT: "textDocumentSync": 2,
+3
View File
@@ -291,6 +291,9 @@ export function activate(context: ExtensionContext) {
const clientOptions: LanguageClientOptions = {
documentSelector: [
{ scheme: 'file', language: 'carbon' },
// Test files are sent to the server so that it can provide navigation
// within the SemIR in their `// CHECK:STDOUT:` lines.
{ scheme: 'file', language: 'carbon-testdata' },
],
};