mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 13:50:10 +01:00
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:
@@ -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,
|
([SKILL.md](../builtins/SKILL.md)) for guidelines on registering, mapping,
|
||||||
constant evaluating, and lowering compiler builtin primitives (e.g.
|
constant evaluating, and lowering compiler builtin primitives (e.g.
|
||||||
`"int.convert_float"`).
|
`"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.
|
- **Phases**: Lex -> Parse -> Check -> Lower.
|
||||||
- **Definitions**: Many kinds (tokens, parse nodes, SemIR instructions) are
|
- **Definitions**: Many kinds (tokens, parse nodes, SemIR instructions) are
|
||||||
defined in `.def` files and expanded by way of macros.
|
defined in `.def` files and expanded by way of macros.
|
||||||
|
|||||||
@@ -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
|
and optionally Lowering. Output (for example SemIR dumps, Clang errors) is
|
||||||
captured and validated using inline CHECK records.
|
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
|
## Structure and Authoring
|
||||||
|
|
||||||
### File Layout and Headers
|
### File Layout and Headers
|
||||||
|
|||||||
@@ -284,6 +284,13 @@ Supported comment markers are:
|
|||||||
Output line matchers may contain `[[@LINE+offset]` and `{{regex}}` syntaxes,
|
Output line matchers may contain `[[@LINE+offset]` and `{{regex}}` syntaxes,
|
||||||
similar to `FileCheck`.
|
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>
|
// TIP: <tip>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ using ::testing::Matcher;
|
|||||||
using ::testing::MatchesRegex;
|
using ::testing::MatchesRegex;
|
||||||
using ::testing::StrEq;
|
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
|
// Represents the different kinds of version-control conflict markers that are
|
||||||
// relevant for the autoupdater. One key concern here is the distinction between
|
// relevant for the autoupdater. One key concern here is the distinction between
|
||||||
// "snapshot" and "diff" conflict regions. Snapshot regions are the more
|
// "snapshot" and "diff" conflict regions. Snapshot regions are the more
|
||||||
@@ -735,6 +738,22 @@ static auto TryConsumeSetFlag(llvm::StringRef line_trimmed,
|
|||||||
return true;
|
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
|
// Process content for either the main file (with `test_file` and
|
||||||
// `found_autoupdate` provided) or an included file (with those arguments null).
|
// `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.
|
// Otherwise conflict markers are errors.
|
||||||
auto previous_conflict_marker = MarkerKind::None;
|
auto previous_conflict_marker = MarkerKind::None;
|
||||||
|
|
||||||
|
const bool uses_autoupdate_split = UsesAutoupdateSplit(content_cursor);
|
||||||
|
|
||||||
SplitState split_state;
|
SplitState split_state;
|
||||||
|
|
||||||
while (!content_cursor.empty()) {
|
while (!content_cursor.empty()) {
|
||||||
@@ -802,13 +823,18 @@ static auto ProcessFileContent(llvm::StringRef filename,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
CARBON_ASSIGN_OR_RETURN(
|
// `CHECK` lines are only expectations where autoupdate would write them.
|
||||||
is_consumed,
|
// Everywhere else they're input, which is how a split can hold a test file
|
||||||
TryConsumeCheck(running_autoupdate, line_index, line, line_trimmed,
|
// that itself contains `CHECK` lines.
|
||||||
test_file ? &test_file->expected_stdout : nullptr,
|
if (!uses_autoupdate_split || split_state.filename == AutoupdateSplit) {
|
||||||
test_file ? &test_file->expected_stderr : nullptr));
|
CARBON_ASSIGN_OR_RETURN(
|
||||||
if (is_consumed) {
|
is_consumed,
|
||||||
continue;
|
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) {
|
if (test_file) {
|
||||||
@@ -895,8 +921,6 @@ auto ProcessTestFile(llvm::StringRef test_name, bool running_autoupdate)
|
|||||||
return ErrorBuilder() << "Missing AUTOUPDATE/NOAUTOUPDATE setting";
|
return ErrorBuilder() << "Missing AUTOUPDATE/NOAUTOUPDATE setting";
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr llvm::StringLiteral AutoupdateSplit = "AUTOUPDATE-SPLIT";
|
|
||||||
|
|
||||||
// Validate AUTOUPDATE-SPLIT use, and remove it from test files if present.
|
// Validate AUTOUPDATE-SPLIT use, and remove it from test files if present.
|
||||||
if (test_file.has_splits) {
|
if (test_file.has_splits) {
|
||||||
for (const auto& test_file :
|
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
|
||||||
@@ -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(
|
cc_library(
|
||||||
name = "context",
|
name = "context",
|
||||||
srcs = ["context.cpp"],
|
srcs = ["context.cpp"],
|
||||||
hdrs = ["context.h"],
|
hdrs = ["context.h"],
|
||||||
deps = [
|
deps = [
|
||||||
":sem_ir_index",
|
":sem_ir_index",
|
||||||
|
":sem_ir_text",
|
||||||
"//common:check",
|
"//common:check",
|
||||||
"//common:map",
|
"//common:map",
|
||||||
"//common:raw_string_ostream",
|
"//common:raw_string_ostream",
|
||||||
@@ -93,12 +105,16 @@ cc_library(
|
|||||||
|
|
||||||
cc_library(
|
cc_library(
|
||||||
name = "handle",
|
name = "handle",
|
||||||
srcs = glob(["handle_*"]),
|
srcs = glob(["handle_*.cpp"]),
|
||||||
hdrs = ["handle.h"],
|
hdrs = [
|
||||||
|
"handle.h",
|
||||||
|
"handle_sem_ir_text.h",
|
||||||
|
],
|
||||||
deps = [
|
deps = [
|
||||||
":context",
|
":context",
|
||||||
":position",
|
":position",
|
||||||
":sem_ir_index",
|
":sem_ir_index",
|
||||||
|
":sem_ir_text",
|
||||||
"//common:check",
|
"//common:check",
|
||||||
"//common:raw_string_ostream",
|
"//common:raw_string_ostream",
|
||||||
"//toolchain/base:shared_value_stores",
|
"//toolchain/base:shared_value_stores",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "common/check.h"
|
#include "common/check.h"
|
||||||
#include "common/raw_string_ostream.h"
|
#include "common/raw_string_ostream.h"
|
||||||
|
#include "llvm/ADT/StringExtras.h"
|
||||||
#include "llvm/Support/VirtualFileSystem.h"
|
#include "llvm/Support/VirtualFileSystem.h"
|
||||||
#include "llvm/TargetParser/Host.h"
|
#include "llvm/TargetParser/Host.h"
|
||||||
#include "toolchain/base/clang_invocation.h"
|
#include "toolchain/base/clang_invocation.h"
|
||||||
@@ -216,17 +217,41 @@ auto Context::File::sem_ir_index() const -> const SemIRIndex* {
|
|||||||
return &*sem_ir_index_;
|
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,
|
auto Context::File::SetText(Context& context, std::optional<int64_t> version,
|
||||||
llvm::StringRef text) -> void {
|
llvm::StringRef text) -> void {
|
||||||
// Clear state dependent on the source text.
|
// Clear state dependent on the source text.
|
||||||
compile_driver_.reset();
|
compile_driver_.reset();
|
||||||
sem_ir_index_.reset();
|
sem_ir_index_.reset();
|
||||||
|
sem_ir_text_.reset();
|
||||||
|
|
||||||
text_ = text.str();
|
text_ = text.str();
|
||||||
|
is_test_file_ = language_id_ == "carbon-testdata";
|
||||||
|
|
||||||
// A consumer to gather diagnostics for the file.
|
// A consumer to gather diagnostics for the file.
|
||||||
DiagnosticConsumer consumer(&context, uri_, version);
|
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
|
// TODO: Make the processing asynchronous, to better handle rapid text
|
||||||
// updates.
|
// updates.
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include "toolchain/driver/compile_driver.h"
|
#include "toolchain/driver/compile_driver.h"
|
||||||
#include "toolchain/driver/compile_options.h"
|
#include "toolchain/driver/compile_options.h"
|
||||||
#include "toolchain/language_server/sem_ir_index.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/lex/tokenized_buffer.h"
|
||||||
#include "toolchain/parse/tree_and_subtrees.h"
|
#include "toolchain/parse/tree_and_subtrees.h"
|
||||||
#include "toolchain/sem_ir/file.h"
|
#include "toolchain/sem_ir/file.h"
|
||||||
@@ -31,9 +32,12 @@ class Context {
|
|||||||
// Cached information for an open file.
|
// Cached information for an open file.
|
||||||
class File {
|
class File {
|
||||||
public:
|
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)),
|
: uri_(std::move(uri)),
|
||||||
filename_(uri_.file().str()),
|
filename_(uri_.file().str()),
|
||||||
|
language_id_(language_id.str()),
|
||||||
options_(&codegen_options_) {}
|
options_(&codegen_options_) {}
|
||||||
|
|
||||||
// Changes the file's text, updating dependent state.
|
// Changes the file's text, updating dependent state.
|
||||||
@@ -44,6 +48,13 @@ class Context {
|
|||||||
auto filename() const -> llvm::StringRef { return filename_; }
|
auto filename() const -> llvm::StringRef { return filename_; }
|
||||||
auto text() const -> llvm::StringRef { return text_; }
|
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& {
|
auto tree_and_subtrees() const -> const Parse::TreeAndSubtrees& {
|
||||||
return unit().parse_tree_and_subtrees();
|
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
|
// 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* {
|
auto sem_ir() const -> const SemIR::File* {
|
||||||
|
if (!compile_driver_) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
const auto& compilation_unit = unit();
|
const auto& compilation_unit = unit();
|
||||||
return compilation_unit.has_sem_ir() ? &compilation_unit.sem_ir()
|
return compilation_unit.has_sem_ir() ? &compilation_unit.sem_ir()
|
||||||
: nullptr;
|
: nullptr;
|
||||||
@@ -70,6 +84,11 @@ class Context {
|
|||||||
// users actually notice.
|
// users actually notice.
|
||||||
auto sem_ir_index() const -> const SemIRIndex*;
|
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:
|
private:
|
||||||
auto unit() const -> const CompilationUnit& {
|
auto unit() const -> const CompilationUnit& {
|
||||||
CARBON_CHECK(compile_driver_);
|
CARBON_CHECK(compile_driver_);
|
||||||
@@ -80,15 +99,21 @@ class Context {
|
|||||||
clang::clangd::URIForFile uri_;
|
clang::clangd::URIForFile uri_;
|
||||||
std::string filename_;
|
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.
|
// Current file content, and derived values.
|
||||||
std::string text_;
|
std::string text_;
|
||||||
|
bool is_test_file_ = false;
|
||||||
|
|
||||||
CodegenOptions codegen_options_;
|
CodegenOptions codegen_options_;
|
||||||
CompileOptions options_;
|
CompileOptions options_;
|
||||||
std::unique_ptr<CompileDriver> compile_driver_;
|
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<SemIRIndex> sem_ir_index_;
|
||||||
|
mutable std::optional<SemIRText> sem_ir_text_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// `vlog_stream` is optional; other parameters are required.
|
// `vlog_stream` is optional; other parameters are required.
|
||||||
|
|||||||
@@ -10,6 +10,13 @@
|
|||||||
|
|
||||||
namespace Carbon::LanguageServer {
|
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.
|
// Locates the entity named at a position.
|
||||||
auto HandleDefinition(
|
auto HandleDefinition(
|
||||||
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
||||||
@@ -57,6 +64,13 @@ auto HandleHover(
|
|||||||
llvm::function_ref<auto(llvm::Expected<clang::clangd::Hover>)->void>
|
llvm::function_ref<auto(llvm::Expected<clang::clangd::Hover>)->void>
|
||||||
on_done) -> 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
|
// Tells the client what features are supported, and negotiates the position
|
||||||
// encoding.
|
// encoding.
|
||||||
auto HandleInitialize(
|
auto HandleInitialize(
|
||||||
|
|||||||
@@ -123,6 +123,13 @@ auto HandleDocumentSymbol(
|
|||||||
if (!file) {
|
if (!file) {
|
||||||
return;
|
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_and_subtrees = file->tree_and_subtrees();
|
||||||
const auto& tree = tree_and_subtrees.tree();
|
const auto& tree = tree_and_subtrees.tree();
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ auto HandleFormatting(
|
|||||||
if (!file) {
|
if (!file) {
|
||||||
return;
|
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;
|
RawStringOstream out;
|
||||||
if (!Format::Format(file->tokens(), out)) {
|
if (!Format::Format(file->tokens(), out)) {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ auto HandleInitialize(
|
|||||||
{"documentFormattingProvider", true},
|
{"documentFormattingProvider", true},
|
||||||
{"documentSymbolProvider", true},
|
{"documentSymbolProvider", true},
|
||||||
{"hoverProvider", true},
|
{"hoverProvider", true},
|
||||||
|
{"implementationProvider", true},
|
||||||
{"positionEncoding", encoding},
|
{"positionEncoding", encoding},
|
||||||
{"referencesProvider", true},
|
{"referencesProvider", true},
|
||||||
{"textDocumentSync", /*Incremental=*/2},
|
{"textDocumentSync", /*Incremental=*/2},
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "common/raw_string_ostream.h"
|
#include "common/raw_string_ostream.h"
|
||||||
#include "toolchain/language_server/handle.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/position.h"
|
||||||
#include "toolchain/language_server/sem_ir_index.h"
|
#include "toolchain/language_server/sem_ir_index.h"
|
||||||
#include "toolchain/sem_ir/file.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);
|
return SemIR::StringifyTypeOfInst(sem_ir, inst_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a position-based query, returns the corresponding position information.
|
// Returns the file a position-based query refers to. If the request names a
|
||||||
// If the request is invalid or there is no instruction at that position,
|
// file we don't know, produces an InvalidParams error and returns null.
|
||||||
// produces a response to the query: an InvalidParams error or a
|
|
||||||
// default-constructed reply, as appropriate.
|
|
||||||
//
|
|
||||||
// TODO: If a default-constructed response is not the correct way to handle an
|
|
||||||
// invalid location, we will need to extend this function to accept a fallback
|
|
||||||
// value. For now, it's right for all the queries we support.
|
|
||||||
template <typename ResponseType>
|
template <typename ResponseType>
|
||||||
static auto FindInstAtPositionOrFail(
|
static auto FindFileOrFail(
|
||||||
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
||||||
llvm::function_ref<auto(llvm::Expected<ResponseType>)->void> on_done)
|
llvm::function_ref<auto(llvm::Expected<ResponseType>)->void> on_done)
|
||||||
-> PositionInfo {
|
-> const Context::File* {
|
||||||
auto* file = context.LookupFile(params.textDocument.uri.file());
|
auto* file = context.LookupFile(params.textDocument.uri.file());
|
||||||
if (!file) {
|
if (!file) {
|
||||||
on_done(llvm::make_error<clang::clangd::LSPError>(
|
on_done(llvm::make_error<clang::clangd::LSPError>(
|
||||||
llvm::formatv("Unknown textDocument `{0}`",
|
llvm::formatv("Unknown textDocument `{0}`",
|
||||||
params.textDocument.uri.file()),
|
params.textDocument.uri.file()),
|
||||||
clang::clangd::ErrorCode::InvalidParams));
|
clang::clangd::ErrorCode::InvalidParams));
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
return file;
|
||||||
auto info = FindPositionInfo(*file, params.position);
|
|
||||||
if (!info.has_inst()) {
|
|
||||||
on_done(ResponseType());
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements `textDocument/hover`:
|
// Implements `textDocument/hover`:
|
||||||
@@ -68,8 +57,19 @@ auto HandleHover(
|
|||||||
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
||||||
llvm::function_ref<auto(llvm::Expected<clang::clangd::Hover>)->void>
|
llvm::function_ref<auto(llvm::Expected<clang::clangd::Hover>)->void>
|
||||||
on_done) -> 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()) {
|
if (!info.has_inst()) {
|
||||||
|
on_done(clang::clangd::Hover());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,12 +91,24 @@ auto HandleHover(
|
|||||||
// instruction they resolve to.
|
// instruction they resolve to.
|
||||||
static auto HandleGoto(
|
static auto HandleGoto(
|
||||||
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
||||||
bool use_type,
|
SemIRTextGoto sem_ir_text_goto, bool use_type,
|
||||||
llvm::function_ref<
|
llvm::function_ref<
|
||||||
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
||||||
on_done) -> 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()) {
|
if (!info.has_inst()) {
|
||||||
|
on_done(std::vector<clang::clangd::Location>());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,19 +130,58 @@ static auto HandleGoto(
|
|||||||
on_done(std::move(locations));
|
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
|
// 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(
|
auto HandleDefinition(
|
||||||
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
Context& context, const clang::clangd::TextDocumentPositionParams& params,
|
||||||
llvm::function_ref<
|
llvm::function_ref<
|
||||||
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
||||||
on_done) -> 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`:
|
// Implements `textDocument/typeDefinition`:
|
||||||
@@ -140,7 +191,10 @@ auto HandleTypeDefinition(
|
|||||||
llvm::function_ref<
|
llvm::function_ref<
|
||||||
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
||||||
on_done) -> 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`:
|
// Implements `textDocument/references`:
|
||||||
@@ -153,8 +207,20 @@ auto HandleReferences(
|
|||||||
llvm::function_ref<
|
llvm::function_ref<
|
||||||
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
auto(llvm::Expected<std::vector<clang::clangd::Location>>)->void>
|
||||||
on_done) -> 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()) {
|
if (!info.has_inst()) {
|
||||||
|
on_done(std::vector<clang::clangd::Location>());
|
||||||
return;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto insert_result = context.files().Insert(
|
auto insert_result = context.files().Insert(filename, [&] {
|
||||||
filename, [&] { return Context::File(params.textDocument.uri); });
|
return Context::File(params.textDocument.uri,
|
||||||
|
params.textDocument.languageId);
|
||||||
|
});
|
||||||
insert_result.value().SetText(context, params.textDocument.version,
|
insert_result.value().SetText(context, params.textDocument.version,
|
||||||
params.textDocument.text);
|
params.textDocument.text);
|
||||||
if (!insert_result.is_inserted()) {
|
if (!insert_result.is_inserted()) {
|
||||||
|
|||||||
@@ -73,11 +73,12 @@ auto IncomingMessages::AddNotificationHandler(
|
|||||||
IncomingMessages::IncomingMessages(clang::clangd::Transport* transport,
|
IncomingMessages::IncomingMessages(clang::clangd::Transport* transport,
|
||||||
Context* context)
|
Context* context)
|
||||||
: transport_(transport), context_(context) {
|
: transport_(transport), context_(context) {
|
||||||
AddCallHandler("textDocument/declaration", &HandleDefinition);
|
AddCallHandler("textDocument/declaration", &HandleDeclaration);
|
||||||
AddCallHandler("textDocument/definition", &HandleDefinition);
|
AddCallHandler("textDocument/definition", &HandleDefinition);
|
||||||
AddCallHandler("textDocument/documentSymbol", &HandleDocumentSymbol);
|
AddCallHandler("textDocument/documentSymbol", &HandleDocumentSymbol);
|
||||||
AddCallHandler("textDocument/formatting", &HandleFormatting);
|
AddCallHandler("textDocument/formatting", &HandleFormatting);
|
||||||
AddCallHandler("textDocument/hover", &HandleHover);
|
AddCallHandler("textDocument/hover", &HandleHover);
|
||||||
|
AddCallHandler("textDocument/implementation", &HandleImplementation);
|
||||||
AddCallHandler("textDocument/references", &HandleReferences);
|
AddCallHandler("textDocument/references", &HandleReferences);
|
||||||
AddCallHandler("textDocument/typeDefinition", &HandleTypeDefinition);
|
AddCallHandler("textDocument/typeDefinition", &HandleTypeDefinition);
|
||||||
AddCallHandler("initialize", &HandleInitialize);
|
AddCallHandler("initialize", &HandleInitialize);
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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_
|
||||||
+2
-1
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
// CHECK:STDERR: error: Input/output error [LanguageServerTransportError]
|
// CHECK:STDERR: error: Input/output error [LanguageServerTransportError]
|
||||||
// CHECK:STDERR:
|
// CHECK:STDERR:
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
// --- AUTOUPDATE-SPLIT
|
// --- AUTOUPDATE-SPLIT
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 393{{\r}}
|
// CHECK:STDOUT: Content-Length: 431{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -32,13 +32,14 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-8",
|
// CHECK:STDOUT: "positionEncoding": "utf-8",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
// CHECK:STDOUT: "typeDefinitionProvider": true
|
// CHECK:STDOUT: "typeDefinitionProvider": true
|
||||||
// CHECK:STDOUT: }
|
// CHECK:STDOUT: }
|
||||||
// CHECK:STDOUT: }
|
// CHECK:STDOUT: }
|
||||||
// CHECK:STDOUT: }Content-Length: 394{{\r}}
|
// CHECK:STDOUT: }Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 2,
|
// CHECK:STDOUT: "id": 2,
|
||||||
@@ -50,13 +51,14 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
// CHECK:STDOUT: "typeDefinitionProvider": true
|
// CHECK:STDOUT: "typeDefinitionProvider": true
|
||||||
// CHECK:STDOUT: }
|
// CHECK:STDOUT: }
|
||||||
// CHECK:STDOUT: }
|
// CHECK:STDOUT: }
|
||||||
// CHECK:STDOUT: }Content-Length: 394{{\r}}
|
// CHECK:STDOUT: }Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 3,
|
// CHECK:STDOUT: "id": 3,
|
||||||
@@ -68,6 +70,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// 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: warning: -32602: in call to `textDocument/didOpen`, JSON parse failed: missing value at (root).textDocument [LanguageServerNotificationParseError]
|
||||||
// CHECK:STDERR:
|
// CHECK:STDERR:
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
// --- AUTOUPDATE-SPLIT
|
// --- AUTOUPDATE-SPLIT
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ choice Colors {
|
|||||||
[[@LSP-CALL:shutdown]]
|
[[@LSP-CALL:shutdown]]
|
||||||
[[@LSP-NOTIFY:exit]]
|
[[@LSP-NOTIFY:exit]]
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -42,6 +42,7 @@ choice Colors {
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,7 @@ fn G() {}
|
|||||||
[[@LSP-CALL:shutdown]]
|
[[@LSP-CALL:shutdown]]
|
||||||
[[@LSP-NOTIFY:exit]]
|
[[@LSP-NOTIFY:exit]]
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -45,6 +45,7 @@ fn G() {}
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ fn Builtin() = "int.make_type_32";
|
|||||||
[[@LSP-CALL:shutdown]]
|
[[@LSP-CALL:shutdown]]
|
||||||
[[@LSP-NOTIFY:exit]]
|
[[@LSP-NOTIFY:exit]]
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -41,6 +41,7 @@ fn Builtin() = "int.make_type_32";
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class Incomplete {
|
|||||||
[[@LSP-CALL:shutdown]]
|
[[@LSP-CALL:shutdown]]
|
||||||
[[@LSP-NOTIFY:exit]]
|
[[@LSP-NOTIFY:exit]]
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -39,6 +39,7 @@ class Incomplete {
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
// CHECK:STDERR: /test.cpp: warning: non-Carbon file requested [LanguageServerFileUnsupported]
|
// CHECK:STDERR: /test.cpp: warning: non-Carbon file requested [LanguageServerFileUnsupported]
|
||||||
// CHECK:STDERR:
|
// CHECK:STDERR:
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ fn Bar() {}
|
|||||||
[[@LSP-CALL:shutdown]]
|
[[@LSP-CALL:shutdown]]
|
||||||
[[@LSP-NOTIFY:exit]]
|
[[@LSP-NOTIFY:exit]]
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -40,6 +40,7 @@ fn Bar() {}
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
// CHECK:STDERR: /test.carbon: warning: unknown file requested [LanguageServerFileUnknown]
|
// CHECK:STDERR: /test.carbon: warning: unknown file requested [LanguageServerFileUnknown]
|
||||||
// CHECK:STDERR:
|
// CHECK:STDERR:
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
[[@LSP-CALL:shutdown]]
|
[[@LSP-CALL:shutdown]]
|
||||||
[[@LSP-NOTIFY:exit]]
|
[[@LSP-NOTIFY:exit]]
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
[[@LSP-CALL:shutdown]]
|
[[@LSP-CALL:shutdown]]
|
||||||
[[@LSP-NOTIFY:exit]]
|
[[@LSP-NOTIFY:exit]]
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
[[@LSP-CALL:shutdown]]
|
[[@LSP-CALL:shutdown]]
|
||||||
[[@LSP-NOTIFY:exit]]
|
[[@LSP-NOTIFY:exit]]
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
[[@LSP-CALL:shutdown]]
|
[[@LSP-CALL:shutdown]]
|
||||||
[[@LSP-NOTIFY:exit]]
|
[[@LSP-NOTIFY:exit]]
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -63,6 +63,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ fn Run() {
|
|||||||
[[@LSP-CALL:shutdown]]
|
[[@LSP-CALL:shutdown]]
|
||||||
[[@LSP-NOTIFY:exit]]
|
[[@LSP-NOTIFY:exit]]
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -71,6 +71,7 @@ fn Run() {
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// 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: /test.carbon: warning: unknown file requested [LanguageServerFileUnknown]
|
||||||
// CHECK:STDERR:
|
// CHECK:STDERR:
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
// CHECK:STDERR: /test.carbon: warning: tried closing unknown file; ignoring request [LanguageServerCloseUnknownFile]
|
// CHECK:STDERR: /test.carbon: warning: tried closing unknown file; ignoring request [LanguageServerCloseUnknownFile]
|
||||||
// CHECK:STDERR:
|
// CHECK:STDERR:
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
// --- AUTOUPDATE-SPLIT
|
// --- AUTOUPDATE-SPLIT
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
// --- AUTOUPDATE-SPLIT
|
// --- AUTOUPDATE-SPLIT
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -40,6 +40,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -107,7 +107,7 @@
|
|||||||
|
|
||||||
// --- AUTOUPDATE-SPLIT
|
// --- AUTOUPDATE-SPLIT
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -119,6 +119,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
+2
-1
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
// --- AUTOUPDATE-SPLIT
|
// --- AUTOUPDATE-SPLIT
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
// --- AUTOUPDATE-SPLIT
|
// --- AUTOUPDATE-SPLIT
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
// --- AUTOUPDATE-SPLIT
|
// --- AUTOUPDATE-SPLIT
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
// CHECK:STDERR: /test.carbon: warning: duplicate open file request; updating content [LanguageServerOpenDuplicateFile]
|
// CHECK:STDERR: /test.carbon: warning: duplicate open file request; updating content [LanguageServerOpenDuplicateFile]
|
||||||
// CHECK:STDERR:
|
// CHECK:STDERR:
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
+2
-1
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
// --- AUTOUPDATE-SPLIT
|
// --- AUTOUPDATE-SPLIT
|
||||||
|
|
||||||
// CHECK:STDOUT: Content-Length: 394{{\r}}
|
// CHECK:STDOUT: Content-Length: 432{{\r}}
|
||||||
// CHECK:STDOUT: {{\r}}
|
// CHECK:STDOUT: {{\r}}
|
||||||
// CHECK:STDOUT: {
|
// CHECK:STDOUT: {
|
||||||
// CHECK:STDOUT: "id": 1,
|
// CHECK:STDOUT: "id": 1,
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
// CHECK:STDOUT: "documentFormattingProvider": true,
|
// CHECK:STDOUT: "documentFormattingProvider": true,
|
||||||
// CHECK:STDOUT: "documentSymbolProvider": true,
|
// CHECK:STDOUT: "documentSymbolProvider": true,
|
||||||
// CHECK:STDOUT: "hoverProvider": true,
|
// CHECK:STDOUT: "hoverProvider": true,
|
||||||
|
// CHECK:STDOUT: "implementationProvider": true,
|
||||||
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
// CHECK:STDOUT: "positionEncoding": "utf-16",
|
||||||
// CHECK:STDOUT: "referencesProvider": true,
|
// CHECK:STDOUT: "referencesProvider": true,
|
||||||
// CHECK:STDOUT: "textDocumentSync": 2,
|
// CHECK:STDOUT: "textDocumentSync": 2,
|
||||||
|
|||||||
@@ -291,6 +291,9 @@ export function activate(context: ExtensionContext) {
|
|||||||
const clientOptions: LanguageClientOptions = {
|
const clientOptions: LanguageClientOptions = {
|
||||||
documentSelector: [
|
documentSelector: [
|
||||||
{ scheme: 'file', language: 'carbon' },
|
{ 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' },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user