Compare commits
7 Commits
main
...
watch-sock
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d75659fb1 | ||
|
|
09e95bbc7e | ||
|
|
69454812bf | ||
|
|
511b71f19a | ||
|
|
ed8b8b7745 | ||
|
|
a303e13f58 | ||
|
|
72c0a74609 |
42
editors/vscode/samples/cmake-workspace/CMakeLists.txt
Normal file
42
editors/vscode/samples/cmake-workspace/CMakeLists.txt
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.28)
|
||||||
|
|
||||||
|
project(clice_vscode_cmake_sample LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
add_library(sample_greeting greeting.cc)
|
||||||
|
target_include_directories(sample_greeting PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
|
||||||
|
add_executable(sample_app main.cc)
|
||||||
|
target_link_libraries(sample_app PRIVATE sample_greeting)
|
||||||
|
|
||||||
|
set(SAMPLE_MODULES_SUPPORTED OFF)
|
||||||
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND
|
||||||
|
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16)
|
||||||
|
set(SAMPLE_MODULES_SUPPORTED ON)
|
||||||
|
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND
|
||||||
|
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14)
|
||||||
|
set(SAMPLE_MODULES_SUPPORTED ON)
|
||||||
|
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND
|
||||||
|
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.34)
|
||||||
|
set(SAMPLE_MODULES_SUPPORTED ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(SAMPLE_MODULES_SUPPORTED)
|
||||||
|
add_executable(sample_module_app)
|
||||||
|
target_sources(sample_module_app PRIVATE main_module.cc)
|
||||||
|
target_sources(sample_module_app
|
||||||
|
PRIVATE
|
||||||
|
FILE_SET CXX_MODULES
|
||||||
|
FILES greeting_module.cppm
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
message(STATUS
|
||||||
|
"Skipping sample_module_app because the active compiler lacks "
|
||||||
|
"CMake C++20 module scanning support. Use Clang >= 16, GCC >= 14, "
|
||||||
|
"or MSVC 19.34+ to enable it."
|
||||||
|
)
|
||||||
|
endif()
|
||||||
38
editors/vscode/samples/cmake-workspace/README.md
Normal file
38
editors/vscode/samples/cmake-workspace/README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# VS Code CMake Sample
|
||||||
|
|
||||||
|
This workspace is a standalone CMake project for attaching the VS Code extension to a real `clice` session.
|
||||||
|
|
||||||
|
`clice` already auto-detects `build/compile_commands.json`, so this sample does not need any helper scripts or extra CMake glue.
|
||||||
|
|
||||||
|
The workspace contains two entry points:
|
||||||
|
|
||||||
|
- `main.cc`: a traditional include-based example.
|
||||||
|
- `main_module.cc`: a C++20 modules example that imports `greeting_module.cppm`.
|
||||||
|
|
||||||
|
## Prepare The Workspace
|
||||||
|
|
||||||
|
From this directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake -S . -B build -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
||||||
|
```
|
||||||
|
|
||||||
|
That is enough to generate `build/compile_commands.json`, which `clice` can discover automatically when this folder is opened as the workspace.
|
||||||
|
|
||||||
|
If you also want the sample binary:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake --build build
|
||||||
|
```
|
||||||
|
|
||||||
|
## C++20 Modules
|
||||||
|
|
||||||
|
The module example is enabled automatically when CMake is using a compiler it can scan for C++20 modules:
|
||||||
|
|
||||||
|
- Clang 16+
|
||||||
|
- GCC 14+
|
||||||
|
- MSVC 19.34+
|
||||||
|
|
||||||
|
If the active compiler is older than that, CMake still configures the workspace and builds `sample_app`, but it skips `sample_module_app`.
|
||||||
|
|
||||||
|
When you do have a supported compiler, `main_module.cc` and `greeting_module.cppm` will also appear in `build/compile_commands.json`, which makes this workspace useful for testing clice's module handling in an editor.
|
||||||
7
editors/vscode/samples/cmake-workspace/greeting.cc
Normal file
7
editors/vscode/samples/cmake-workspace/greeting.cc
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#include "greeting.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
std::string build_greeting(std::string_view name) {
|
||||||
|
return "Hello, " + std::string(name) + " from the CMake sample.";
|
||||||
|
}
|
||||||
6
editors/vscode/samples/cmake-workspace/greeting.h
Normal file
6
editors/vscode/samples/cmake-workspace/greeting.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
std::string build_greeting(std::string_view name);
|
||||||
14
editors/vscode/samples/cmake-workspace/greeting_module.cppm
Normal file
14
editors/vscode/samples/cmake-workspace/greeting_module.cppm
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
export module sample.greeting;
|
||||||
|
|
||||||
|
export namespace sample {
|
||||||
|
|
||||||
|
std::string build_module_greeting(std::string_view name) {
|
||||||
|
return "Hello, " + std::string(name) + " from the C++20 module sample.";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
8
editors/vscode/samples/cmake-workspace/main.cc
Normal file
8
editors/vscode/samples/cmake-workspace/main.cc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#include "greeting.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << build_greeting("clice") << '\n';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
8
editors/vscode/samples/cmake-workspace/main_module.cc
Normal file
8
editors/vscode/samples/cmake-workspace/main_module.cc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
import sample.greeting;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << sample::build_module_greeting("clice") << '\n';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
6
editors/vscode/samples/single-file/main.cc
Normal file
6
editors/vscode/samples/single-file/main.cc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
printf("Hello, World!\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
489
scripts/analyzers/extract_semantic_highlighting_codex.py
Normal file
489
scripts/analyzers/extract_semantic_highlighting_codex.py
Normal file
@@ -0,0 +1,489 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Use Codex to analyze clangd semantic-highlighting logic from a GitHub blob URL.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
python3 scripts/analyzers/extract_semantic_highlighting_codex.py \
|
||||||
|
https://github.com/llvm/llvm-project/blob/d8ba56ce3f98871ae4e5782c4af2df4c98bebde7/clang-tools-extra/clangd/SemanticHighlighting.cpp \
|
||||||
|
--output semantic-highlighting.md
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Literal
|
||||||
|
from urllib.error import HTTPError, URLError
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from urllib.request import ProxyHandler, Request, build_opener, getproxies
|
||||||
|
|
||||||
|
|
||||||
|
PROMPT = """You are analyzing one C++ source file from clangd.
|
||||||
|
|
||||||
|
Task:
|
||||||
|
- Extract the cases in which semantic highlighting is applied.
|
||||||
|
- Produce an exhaustive segmentation that covers every source line exactly once.
|
||||||
|
|
||||||
|
Kinds:
|
||||||
|
- nop: this line does not materially participate in deciding or applying a highlight.
|
||||||
|
- condition: this line or contiguous range establishes a boolean/branching condition that gates a later highlight resolution.
|
||||||
|
- resolution: this line or contiguous range selects or applies a concrete semantic-highlighting outcome, such as a HighlightingKind, modifier, token emission, or an equivalent concrete result.
|
||||||
|
|
||||||
|
Output rules:
|
||||||
|
- Return JSON only.
|
||||||
|
- Segments must be in ascending order and non-overlapping.
|
||||||
|
- Every line from 1 through the last line must be covered exactly once.
|
||||||
|
- Use the smallest practical contiguous ranges.
|
||||||
|
- It is fine to compress long nop runs into ranges; the caller may expand them later.
|
||||||
|
- For nop segments, use an empty summary string.
|
||||||
|
- For condition segments, write one short sentence in plain English.
|
||||||
|
- For resolution segments, write one short sentence describing the concrete outcome.
|
||||||
|
- For resolution segments, populate depends_on with the exact condition line ranges that directly gate this outcome when present.
|
||||||
|
- Use the provided line numbers exactly; never invent lines.
|
||||||
|
- Do not use any external tools or local files; analyze only the numbered source text provided in the prompt.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"segments": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"start_line": {"type": "integer", "minimum": 1},
|
||||||
|
"end_line": {"type": "integer", "minimum": 1},
|
||||||
|
"kind": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["nop", "condition", "resolution"],
|
||||||
|
},
|
||||||
|
"summary": {"type": "string"},
|
||||||
|
"depends_on": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"start_line": {"type": "integer", "minimum": 1},
|
||||||
|
"end_line": {"type": "integer", "minimum": 1},
|
||||||
|
},
|
||||||
|
"required": ["start_line", "end_line"],
|
||||||
|
"additionalProperties": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"start_line",
|
||||||
|
"end_line",
|
||||||
|
"kind",
|
||||||
|
"summary",
|
||||||
|
"depends_on",
|
||||||
|
],
|
||||||
|
"additionalProperties": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["segments"],
|
||||||
|
"additionalProperties": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SegmentKind = Literal["nop", "condition", "resolution"]
|
||||||
|
|
||||||
|
PROXY_ENV_KEYS = ("http_proxy", "https_proxy", "all_proxy", "no_proxy")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class GitHubBlobRef:
|
||||||
|
owner: str
|
||||||
|
repo: str
|
||||||
|
rev: str
|
||||||
|
path: str
|
||||||
|
blob_url: str
|
||||||
|
raw_url: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def title(self) -> str:
|
||||||
|
return Path(self.path).stem
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class LineRange:
|
||||||
|
start_line: int
|
||||||
|
end_line: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Segment:
|
||||||
|
start_line: int
|
||||||
|
end_line: int
|
||||||
|
kind: SegmentKind
|
||||||
|
summary: str
|
||||||
|
depends_on: tuple[LineRange, ...]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Ask Codex to analyze a GitHub-hosted semantic-highlighting file.",
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"url",
|
||||||
|
help="GitHub blob URL pinned to a specific revision.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--model",
|
||||||
|
default=None,
|
||||||
|
help="Codex model to use via `codex exec`.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--reasoning-effort",
|
||||||
|
default=None,
|
||||||
|
choices=["low", "medium", "high", "xhigh"],
|
||||||
|
help="Reasoning effort override passed to Codex CLI.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--timeout",
|
||||||
|
type=int,
|
||||||
|
default=30,
|
||||||
|
help="HTTP timeout in seconds for fetching the source file.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--codex-bin",
|
||||||
|
default="codex",
|
||||||
|
help="Codex CLI executable to invoke.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output",
|
||||||
|
type=Path,
|
||||||
|
help="Write the markdown output to this path instead of stdout.",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_github_blob_url(url: str) -> GitHubBlobRef:
|
||||||
|
parsed = urlparse(url)
|
||||||
|
if parsed.scheme not in {"http", "https"} or parsed.netloc != "github.com":
|
||||||
|
raise ValueError("expected a GitHub https://github.com/.../blob/... URL")
|
||||||
|
|
||||||
|
parts = [part for part in parsed.path.split("/") if part]
|
||||||
|
if len(parts) < 5 or parts[2] != "blob":
|
||||||
|
raise ValueError("expected a GitHub blob URL with /owner/repo/blob/rev/path")
|
||||||
|
|
||||||
|
owner, repo = parts[0], parts[1]
|
||||||
|
rev = parts[3]
|
||||||
|
path = "/".join(parts[4:])
|
||||||
|
if not path:
|
||||||
|
raise ValueError("missing file path in GitHub blob URL")
|
||||||
|
|
||||||
|
raw_url = f"https://raw.githubusercontent.com/{owner}/{repo}/{rev}/{path}"
|
||||||
|
normalized_blob_url = f"https://github.com/{owner}/{repo}/blob/{rev}/{path}"
|
||||||
|
return GitHubBlobRef(
|
||||||
|
owner=owner,
|
||||||
|
repo=repo,
|
||||||
|
rev=rev,
|
||||||
|
path=path,
|
||||||
|
blob_url=normalized_blob_url,
|
||||||
|
raw_url=raw_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_text(url: str, timeout: int) -> str:
|
||||||
|
request = Request(url, headers={"User-Agent": "semantic-highlighting-codex/1.0"})
|
||||||
|
opener = build_opener(ProxyHandler(getproxies()))
|
||||||
|
try:
|
||||||
|
with opener.open(request, timeout=timeout) as response:
|
||||||
|
return response.read().decode("utf-8").replace("\r\n", "\n")
|
||||||
|
except HTTPError as exc:
|
||||||
|
raise RuntimeError(f"failed to fetch {url}: HTTP {exc.code}") from exc
|
||||||
|
except URLError as exc:
|
||||||
|
raise RuntimeError(f"failed to fetch {url}: {exc.reason}") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def number_source(text: str) -> tuple[str, int]:
|
||||||
|
lines = text.splitlines()
|
||||||
|
width = len(str(max(len(lines), 1)))
|
||||||
|
numbered = "\n".join(
|
||||||
|
f"{idx:>{width}} | {line}" for idx, line in enumerate(lines, 1)
|
||||||
|
)
|
||||||
|
return numbered, len(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def build_codex_prompt(
|
||||||
|
blob: GitHubBlobRef,
|
||||||
|
source_text: str,
|
||||||
|
line_count: int,
|
||||||
|
) -> str:
|
||||||
|
numbered_source, _ = number_source(source_text)
|
||||||
|
return (
|
||||||
|
f"{PROMPT}\n\n"
|
||||||
|
f"GitHub blob URL: {blob.blob_url}\n"
|
||||||
|
f"LLVM revision: {blob.rev}\n"
|
||||||
|
f"File path: {blob.path}\n"
|
||||||
|
f"File title: {blob.title}\n"
|
||||||
|
f"Total lines: {line_count}\n\n"
|
||||||
|
"Analyze only the following numbered source file:\n"
|
||||||
|
f"{numbered_source}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_with_codex_cli(
|
||||||
|
codex_bin: str,
|
||||||
|
blob: GitHubBlobRef,
|
||||||
|
source_text: str,
|
||||||
|
line_count: int,
|
||||||
|
model: str | None,
|
||||||
|
reasoning_effort: str | None,
|
||||||
|
) -> list[Segment]:
|
||||||
|
if shutil.which(codex_bin) is None:
|
||||||
|
raise RuntimeError(f"Codex CLI executable `{codex_bin}` was not found in PATH.")
|
||||||
|
|
||||||
|
prompt = build_codex_prompt(
|
||||||
|
blob=blob, source_text=source_text, line_count=line_count
|
||||||
|
)
|
||||||
|
child_env = build_codex_env()
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory(prefix="semantic-highlighting-codex-") as temp_dir:
|
||||||
|
temp_path = Path(temp_dir)
|
||||||
|
schema_path = temp_path / "schema.json"
|
||||||
|
output_path = temp_path / "last-message.json"
|
||||||
|
schema_path.write_text(json.dumps(SCHEMA, indent=2), encoding="utf-8")
|
||||||
|
|
||||||
|
command = [
|
||||||
|
codex_bin,
|
||||||
|
"exec",
|
||||||
|
"--skip-git-repo-check",
|
||||||
|
"--ephemeral",
|
||||||
|
"--color",
|
||||||
|
"never",
|
||||||
|
"--sandbox",
|
||||||
|
"read-only",
|
||||||
|
*([] if model is None else ["--model", model]),
|
||||||
|
*(
|
||||||
|
[]
|
||||||
|
if reasoning_effort is None
|
||||||
|
else ["--config", f"model_reasoning_effort={json.dumps(reasoning_effort)}"]
|
||||||
|
),
|
||||||
|
"--output-schema",
|
||||||
|
str(schema_path),
|
||||||
|
"--output-last-message",
|
||||||
|
str(output_path),
|
||||||
|
"-",
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
completed = subprocess.run(
|
||||||
|
command,
|
||||||
|
input=prompt,
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
check=True,
|
||||||
|
env=child_env,
|
||||||
|
)
|
||||||
|
except FileNotFoundError as exc:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"failed to execute `{codex_bin}`: command not found"
|
||||||
|
) from exc
|
||||||
|
except subprocess.CalledProcessError as exc:
|
||||||
|
detail = exc.stderr.strip() or exc.stdout.strip() or str(exc)
|
||||||
|
raise RuntimeError(f"Codex CLI failed: {detail}") from exc
|
||||||
|
|
||||||
|
if not output_path.exists():
|
||||||
|
detail = completed.stderr.strip() or completed.stdout.strip()
|
||||||
|
raise RuntimeError(
|
||||||
|
"Codex CLI did not produce an output message."
|
||||||
|
+ (f" Details: {detail}" if detail else "")
|
||||||
|
)
|
||||||
|
|
||||||
|
output_text = output_path.read_text(encoding="utf-8").strip()
|
||||||
|
if not output_text:
|
||||||
|
raise RuntimeError("Codex CLI returned an empty final message.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = json.loads(output_text)
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise RuntimeError(f"Codex returned invalid JSON:\n{output_text}") from exc
|
||||||
|
|
||||||
|
return normalize_segments(payload.get("segments", []), line_count)
|
||||||
|
|
||||||
|
|
||||||
|
def build_codex_env() -> dict[str, str]:
|
||||||
|
env = os.environ.copy()
|
||||||
|
for key in PROXY_ENV_KEYS:
|
||||||
|
value = env.get(key)
|
||||||
|
upper_key = key.upper()
|
||||||
|
if value and upper_key not in env:
|
||||||
|
env[upper_key] = value
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_segments(raw_segments: list[dict], line_count: int) -> list[Segment]:
|
||||||
|
normalized: list[Segment] = []
|
||||||
|
for item in raw_segments:
|
||||||
|
kind = item["kind"]
|
||||||
|
if kind not in {"nop", "condition", "resolution"}:
|
||||||
|
raise ValueError(f"unknown segment kind: {kind}")
|
||||||
|
start_line = int(item["start_line"])
|
||||||
|
end_line = int(item["end_line"])
|
||||||
|
if start_line < 1 or end_line < start_line or end_line > line_count:
|
||||||
|
raise ValueError(
|
||||||
|
f"invalid segment range {start_line}-{end_line} for file with {line_count} lines"
|
||||||
|
)
|
||||||
|
depends_on = tuple(
|
||||||
|
LineRange(start_line=int(dep["start_line"]), end_line=int(dep["end_line"]))
|
||||||
|
for dep in item.get("depends_on", [])
|
||||||
|
)
|
||||||
|
normalized.append(
|
||||||
|
Segment(
|
||||||
|
start_line=start_line,
|
||||||
|
end_line=end_line,
|
||||||
|
kind=kind,
|
||||||
|
summary=item.get("summary", "").strip(),
|
||||||
|
depends_on=depends_on,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
normalized.sort(key=lambda segment: (segment.start_line, segment.end_line))
|
||||||
|
|
||||||
|
stitched: list[Segment] = []
|
||||||
|
next_line = 1
|
||||||
|
for segment in normalized:
|
||||||
|
if segment.start_line < next_line:
|
||||||
|
raise ValueError(
|
||||||
|
f"overlapping segments around line {segment.start_line}: model output is invalid"
|
||||||
|
)
|
||||||
|
while next_line < segment.start_line:
|
||||||
|
stitched.append(
|
||||||
|
Segment(
|
||||||
|
start_line=next_line,
|
||||||
|
end_line=next_line,
|
||||||
|
kind="nop",
|
||||||
|
summary="",
|
||||||
|
depends_on=(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
next_line += 1
|
||||||
|
stitched.extend(expand_nop_segment(segment))
|
||||||
|
next_line = segment.end_line + 1
|
||||||
|
|
||||||
|
while next_line <= line_count:
|
||||||
|
stitched.append(
|
||||||
|
Segment(
|
||||||
|
start_line=next_line,
|
||||||
|
end_line=next_line,
|
||||||
|
kind="nop",
|
||||||
|
summary="",
|
||||||
|
depends_on=(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
next_line += 1
|
||||||
|
|
||||||
|
return stitched
|
||||||
|
|
||||||
|
|
||||||
|
def expand_nop_segment(segment: Segment) -> list[Segment]:
|
||||||
|
if segment.kind != "nop" or segment.start_line == segment.end_line:
|
||||||
|
return [segment]
|
||||||
|
return [
|
||||||
|
Segment(
|
||||||
|
start_line=line_no,
|
||||||
|
end_line=line_no,
|
||||||
|
kind="nop",
|
||||||
|
summary="",
|
||||||
|
depends_on=(),
|
||||||
|
)
|
||||||
|
for line_no in range(segment.start_line, segment.end_line + 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def blob_anchor_url(blob: GitHubBlobRef, line_range: LineRange) -> str:
|
||||||
|
if line_range.start_line == line_range.end_line:
|
||||||
|
return f"{blob.blob_url}#L{line_range.start_line}"
|
||||||
|
return f"{blob.blob_url}#L{line_range.start_line}-L{line_range.end_line}"
|
||||||
|
|
||||||
|
|
||||||
|
def format_line_range(start_line: int, end_line: int) -> str:
|
||||||
|
if start_line == end_line:
|
||||||
|
return f"Line {start_line}"
|
||||||
|
return f"Line {start_line}-{end_line}"
|
||||||
|
|
||||||
|
|
||||||
|
def format_loc_reference(blob: GitHubBlobRef, line_range: LineRange) -> str:
|
||||||
|
label = (
|
||||||
|
f"LoC {line_range.start_line}"
|
||||||
|
if line_range.start_line == line_range.end_line
|
||||||
|
else f"LoC {line_range.start_line}-{line_range.end_line}"
|
||||||
|
)
|
||||||
|
return f"[{label}]({blob_anchor_url(blob, line_range)})"
|
||||||
|
|
||||||
|
|
||||||
|
def render_segment_summary(blob: GitHubBlobRef, segment: Segment) -> str:
|
||||||
|
summary = segment.summary.strip()
|
||||||
|
if segment.kind == "nop" or not summary:
|
||||||
|
return ""
|
||||||
|
if segment.kind == "resolution" and segment.depends_on:
|
||||||
|
refs = [format_loc_reference(blob, dep) for dep in segment.depends_on]
|
||||||
|
if len(refs) == 1:
|
||||||
|
prefix = f"By condition at {refs[0]}, "
|
||||||
|
else:
|
||||||
|
prefix = f"By conditions at {', '.join(refs)}, "
|
||||||
|
return prefix + decapitalize_summary(summary)
|
||||||
|
return summary
|
||||||
|
|
||||||
|
|
||||||
|
def decapitalize_summary(summary: str) -> str:
|
||||||
|
if not summary:
|
||||||
|
return summary
|
||||||
|
if len(summary) >= 2 and summary[0].isupper() and summary[1].islower():
|
||||||
|
return summary[:1].lower() + summary[1:]
|
||||||
|
return summary
|
||||||
|
|
||||||
|
|
||||||
|
def render_markdown(blob: GitHubBlobRef, segments: list[Segment]) -> str:
|
||||||
|
lines = [f"- LLVMRevHash: `{blob.rev}`", f"# {blob.title}", ""]
|
||||||
|
for segment in segments:
|
||||||
|
lines.append(
|
||||||
|
f"## {format_line_range(segment.start_line, segment.end_line)} (kind: {segment.kind})"
|
||||||
|
)
|
||||||
|
summary = render_segment_summary(blob, segment)
|
||||||
|
if summary:
|
||||||
|
lines.append(summary)
|
||||||
|
lines.append("")
|
||||||
|
return "\n".join(lines).rstrip() + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
blob = parse_github_blob_url(args.url)
|
||||||
|
source_text = fetch_text(blob.raw_url, timeout=args.timeout)
|
||||||
|
_, line_count = number_source(source_text)
|
||||||
|
segments = analyze_with_codex_cli(
|
||||||
|
codex_bin=args.codex_bin,
|
||||||
|
blob=blob,
|
||||||
|
source_text=source_text,
|
||||||
|
line_count=line_count,
|
||||||
|
model=args.model,
|
||||||
|
reasoning_effort=args.reasoning_effort,
|
||||||
|
)
|
||||||
|
markdown = render_markdown(blob, segments)
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"Error: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if args.output:
|
||||||
|
args.output.write_text(markdown, encoding="utf-8")
|
||||||
|
else:
|
||||||
|
sys.stdout.write(markdown)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
24
scripts/watch-socket.sh
Executable file
24
scripts/watch-socket.sh
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BUILD_CMD=${BUILD_CMD:-".clice/build.sh"}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
cd "${ROOT_DIR}"
|
||||||
|
|
||||||
|
if ! command -v watchexec >/dev/null 2>&1; then
|
||||||
|
echo "watchexec is not installed or not in PATH." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec watchexec \
|
||||||
|
--project-origin "${ROOT_DIR}" \
|
||||||
|
--watch "${ROOT_DIR}/config" \
|
||||||
|
--watch "${ROOT_DIR}/src" \
|
||||||
|
--watch "${ROOT_DIR}/cmake" \
|
||||||
|
--watch "${ROOT_DIR}/CMakeLists.txt" \
|
||||||
|
--restart \
|
||||||
|
--clear \
|
||||||
|
--shell=bash \
|
||||||
|
-- "$BUILD_CMD && ./build/bin/clice --mode socket --port 50051"
|
||||||
@@ -158,6 +158,7 @@ private:
|
|||||||
case clang::tok::utf16_string_literal:
|
case clang::tok::utf16_string_literal:
|
||||||
case clang::tok::utf32_string_literal: kind = SymbolKind::String; break;
|
case clang::tok::utf32_string_literal: kind = SymbolKind::String; break;
|
||||||
case clang::tok::header_name: kind = SymbolKind::Header; break;
|
case clang::tok::header_name: kind = SymbolKind::Header; break;
|
||||||
|
case clang::tok::identifier: break;
|
||||||
case clang::tok::raw_identifier: {
|
case clang::tok::raw_identifier: {
|
||||||
auto previous = lexer.last();
|
auto previous = lexer.last();
|
||||||
if(previous.is_pp_keyword && previous.text(content) == "define") {
|
if(previous.is_pp_keyword && previous.text(content) == "define") {
|
||||||
@@ -171,8 +172,457 @@ private:
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
/* Keywords */
|
||||||
default: break;
|
case clang::tok::kw_auto:
|
||||||
|
case clang::tok::kw_break:
|
||||||
|
case clang::tok::kw_case:
|
||||||
|
case clang::tok::kw_char:
|
||||||
|
case clang::tok::kw_const:
|
||||||
|
case clang::tok::kw_continue:
|
||||||
|
case clang::tok::kw_default:
|
||||||
|
case clang::tok::kw_do:
|
||||||
|
case clang::tok::kw_double:
|
||||||
|
case clang::tok::kw_else:
|
||||||
|
case clang::tok::kw_enum:
|
||||||
|
case clang::tok::kw_extern:
|
||||||
|
case clang::tok::kw_float:
|
||||||
|
case clang::tok::kw_for:
|
||||||
|
case clang::tok::kw_goto:
|
||||||
|
case clang::tok::kw_if:
|
||||||
|
case clang::tok::kw_int:
|
||||||
|
case clang::tok::kw__ExtInt:
|
||||||
|
case clang::tok::kw__BitInt:
|
||||||
|
case clang::tok::kw_long:
|
||||||
|
case clang::tok::kw_register:
|
||||||
|
case clang::tok::kw_return:
|
||||||
|
case clang::tok::kw_short:
|
||||||
|
case clang::tok::kw_signed:
|
||||||
|
case clang::tok::kw_sizeof:
|
||||||
|
case clang::tok::kw___datasizeof:
|
||||||
|
case clang::tok::kw_static:
|
||||||
|
case clang::tok::kw_struct:
|
||||||
|
case clang::tok::kw_switch:
|
||||||
|
case clang::tok::kw_typedef:
|
||||||
|
case clang::tok::kw_union:
|
||||||
|
case clang::tok::kw_unsigned:
|
||||||
|
case clang::tok::kw_void:
|
||||||
|
case clang::tok::kw_volatile:
|
||||||
|
case clang::tok::kw_while:
|
||||||
|
case clang::tok::kw__Alignas:
|
||||||
|
case clang::tok::kw__Alignof:
|
||||||
|
case clang::tok::kw__Atomic:
|
||||||
|
case clang::tok::kw__Bool:
|
||||||
|
case clang::tok::kw__Complex:
|
||||||
|
case clang::tok::kw__Generic:
|
||||||
|
case clang::tok::kw__Imaginary:
|
||||||
|
case clang::tok::kw__Noreturn:
|
||||||
|
case clang::tok::kw__Static_assert:
|
||||||
|
case clang::tok::kw__Thread_local:
|
||||||
|
case clang::tok::kw___func__:
|
||||||
|
case clang::tok::kw___objc_yes:
|
||||||
|
case clang::tok::kw___objc_no:
|
||||||
|
case clang::tok::kw___ptrauth:
|
||||||
|
case clang::tok::kw__Countof:
|
||||||
|
case clang::tok::kw_asm:
|
||||||
|
case clang::tok::kw_bool:
|
||||||
|
case clang::tok::kw_catch:
|
||||||
|
case clang::tok::kw_class:
|
||||||
|
case clang::tok::kw_const_cast:
|
||||||
|
case clang::tok::kw_delete:
|
||||||
|
case clang::tok::kw_dynamic_cast:
|
||||||
|
case clang::tok::kw_explicit:
|
||||||
|
case clang::tok::kw_export:
|
||||||
|
case clang::tok::kw_false:
|
||||||
|
case clang::tok::kw_friend:
|
||||||
|
case clang::tok::kw_mutable:
|
||||||
|
case clang::tok::kw_namespace:
|
||||||
|
case clang::tok::kw_new:
|
||||||
|
case clang::tok::kw_operator:
|
||||||
|
case clang::tok::kw_private:
|
||||||
|
case clang::tok::kw_protected:
|
||||||
|
case clang::tok::kw_public:
|
||||||
|
case clang::tok::kw_reinterpret_cast:
|
||||||
|
case clang::tok::kw_static_cast:
|
||||||
|
case clang::tok::kw_template:
|
||||||
|
case clang::tok::kw_this:
|
||||||
|
case clang::tok::kw_throw:
|
||||||
|
case clang::tok::kw_true:
|
||||||
|
case clang::tok::kw_try:
|
||||||
|
case clang::tok::kw_typename:
|
||||||
|
case clang::tok::kw_typeid:
|
||||||
|
case clang::tok::kw_using:
|
||||||
|
case clang::tok::kw_virtual:
|
||||||
|
case clang::tok::kw_wchar_t:
|
||||||
|
case clang::tok::kw_restrict:
|
||||||
|
case clang::tok::kw_inline:
|
||||||
|
case clang::tok::kw_alignas:
|
||||||
|
case clang::tok::kw_alignof:
|
||||||
|
case clang::tok::kw_char16_t:
|
||||||
|
case clang::tok::kw_char32_t:
|
||||||
|
case clang::tok::kw_constexpr:
|
||||||
|
case clang::tok::kw_decltype:
|
||||||
|
case clang::tok::kw_noexcept:
|
||||||
|
case clang::tok::kw_nullptr:
|
||||||
|
case clang::tok::kw_static_assert:
|
||||||
|
case clang::tok::kw_thread_local:
|
||||||
|
case clang::tok::kw_co_await:
|
||||||
|
case clang::tok::kw_co_return:
|
||||||
|
case clang::tok::kw_co_yield:
|
||||||
|
case clang::tok::kw_module:
|
||||||
|
case clang::tok::kw_import:
|
||||||
|
case clang::tok::kw_consteval:
|
||||||
|
case clang::tok::kw_constinit:
|
||||||
|
case clang::tok::kw_concept:
|
||||||
|
case clang::tok::kw_requires:
|
||||||
|
case clang::tok::kw_char8_t:
|
||||||
|
case clang::tok::kw__Float16:
|
||||||
|
case clang::tok::kw_typeof:
|
||||||
|
case clang::tok::kw_typeof_unqual:
|
||||||
|
case clang::tok::kw__Accum:
|
||||||
|
case clang::tok::kw__Fract:
|
||||||
|
case clang::tok::kw__Sat:
|
||||||
|
case clang::tok::kw__Decimal32:
|
||||||
|
case clang::tok::kw__Decimal64:
|
||||||
|
case clang::tok::kw__Decimal128:
|
||||||
|
case clang::tok::kw___null:
|
||||||
|
case clang::tok::kw___alignof:
|
||||||
|
case clang::tok::kw___attribute:
|
||||||
|
case clang::tok::kw___builtin_choose_expr:
|
||||||
|
case clang::tok::kw___builtin_offsetof:
|
||||||
|
case clang::tok::kw___builtin_FILE:
|
||||||
|
case clang::tok::kw___builtin_FILE_NAME:
|
||||||
|
case clang::tok::kw___builtin_FUNCTION:
|
||||||
|
case clang::tok::kw___builtin_FUNCSIG:
|
||||||
|
case clang::tok::kw___builtin_LINE:
|
||||||
|
case clang::tok::kw___builtin_COLUMN:
|
||||||
|
case clang::tok::kw___builtin_source_location:
|
||||||
|
case clang::tok::kw___builtin_types_compatible_p:
|
||||||
|
case clang::tok::kw___builtin_va_arg:
|
||||||
|
case clang::tok::kw___extension__:
|
||||||
|
case clang::tok::kw___float128:
|
||||||
|
case clang::tok::kw___ibm128:
|
||||||
|
case clang::tok::kw___imag:
|
||||||
|
case clang::tok::kw___int128:
|
||||||
|
case clang::tok::kw___label__:
|
||||||
|
case clang::tok::kw___real:
|
||||||
|
case clang::tok::kw___thread:
|
||||||
|
case clang::tok::kw___FUNCTION__:
|
||||||
|
case clang::tok::kw___PRETTY_FUNCTION__:
|
||||||
|
case clang::tok::kw___auto_type:
|
||||||
|
case clang::tok::kw___FUNCDNAME__:
|
||||||
|
case clang::tok::kw___FUNCSIG__:
|
||||||
|
case clang::tok::kw_L__FUNCTION__:
|
||||||
|
case clang::tok::kw_L__FUNCSIG__:
|
||||||
|
case clang::tok::kw___is_interface_class:
|
||||||
|
case clang::tok::kw___is_sealed:
|
||||||
|
case clang::tok::kw___is_destructible:
|
||||||
|
case clang::tok::kw___is_trivially_destructible:
|
||||||
|
case clang::tok::kw___is_nothrow_destructible:
|
||||||
|
case clang::tok::kw___is_nothrow_assignable:
|
||||||
|
case clang::tok::kw___is_constructible:
|
||||||
|
case clang::tok::kw___is_nothrow_constructible:
|
||||||
|
case clang::tok::kw___is_assignable:
|
||||||
|
case clang::tok::kw___has_nothrow_move_assign:
|
||||||
|
case clang::tok::kw___has_trivial_move_assign:
|
||||||
|
case clang::tok::kw___has_trivial_move_constructor:
|
||||||
|
case clang::tok::kw___builtin_is_implicit_lifetime:
|
||||||
|
case clang::tok::kw___builtin_is_virtual_base_of:
|
||||||
|
case clang::tok::kw___has_nothrow_assign:
|
||||||
|
case clang::tok::kw___has_nothrow_copy:
|
||||||
|
case clang::tok::kw___has_nothrow_constructor:
|
||||||
|
case clang::tok::kw___has_trivial_assign:
|
||||||
|
case clang::tok::kw___has_trivial_copy:
|
||||||
|
case clang::tok::kw___has_trivial_constructor:
|
||||||
|
case clang::tok::kw___has_trivial_destructor:
|
||||||
|
case clang::tok::kw___has_virtual_destructor:
|
||||||
|
case clang::tok::kw___is_abstract:
|
||||||
|
case clang::tok::kw___is_aggregate:
|
||||||
|
case clang::tok::kw___is_base_of:
|
||||||
|
case clang::tok::kw___is_class:
|
||||||
|
case clang::tok::kw___is_convertible_to:
|
||||||
|
case clang::tok::kw___is_empty:
|
||||||
|
case clang::tok::kw___is_enum:
|
||||||
|
case clang::tok::kw___is_final:
|
||||||
|
case clang::tok::kw___is_literal:
|
||||||
|
case clang::tok::kw___is_pod:
|
||||||
|
case clang::tok::kw___is_polymorphic:
|
||||||
|
case clang::tok::kw___is_standard_layout:
|
||||||
|
case clang::tok::kw___is_trivial:
|
||||||
|
case clang::tok::kw___is_trivially_assignable:
|
||||||
|
case clang::tok::kw___is_trivially_constructible:
|
||||||
|
case clang::tok::kw___is_trivially_copyable:
|
||||||
|
case clang::tok::kw___is_union:
|
||||||
|
case clang::tok::kw___has_unique_object_representations:
|
||||||
|
case clang::tok::kw___is_layout_compatible:
|
||||||
|
case clang::tok::kw___is_pointer_interconvertible_base_of:
|
||||||
|
case clang::tok::kw___add_lvalue_reference:
|
||||||
|
case clang::tok::kw___add_pointer:
|
||||||
|
case clang::tok::kw___add_rvalue_reference:
|
||||||
|
case clang::tok::kw___decay:
|
||||||
|
case clang::tok::kw___make_signed:
|
||||||
|
case clang::tok::kw___make_unsigned:
|
||||||
|
case clang::tok::kw___remove_all_extents:
|
||||||
|
case clang::tok::kw___remove_const:
|
||||||
|
case clang::tok::kw___remove_cv:
|
||||||
|
case clang::tok::kw___remove_cvref:
|
||||||
|
case clang::tok::kw___remove_extent:
|
||||||
|
case clang::tok::kw___remove_pointer:
|
||||||
|
case clang::tok::kw___remove_reference_t:
|
||||||
|
case clang::tok::kw___remove_restrict:
|
||||||
|
case clang::tok::kw___remove_volatile:
|
||||||
|
case clang::tok::kw___underlying_type:
|
||||||
|
case clang::tok::kw___is_trivially_equality_comparable:
|
||||||
|
case clang::tok::kw___is_bounded_array:
|
||||||
|
case clang::tok::kw___is_unbounded_array:
|
||||||
|
case clang::tok::kw___is_scoped_enum:
|
||||||
|
case clang::tok::kw___can_pass_in_regs:
|
||||||
|
case clang::tok::kw___reference_binds_to_temporary:
|
||||||
|
case clang::tok::kw___reference_constructs_from_temporary:
|
||||||
|
case clang::tok::kw___reference_converts_from_temporary:
|
||||||
|
case clang::tok::kw_:
|
||||||
|
case clang::tok::kw___builtin_is_cpp_trivially_relocatable:
|
||||||
|
case clang::tok::kw___is_trivially_relocatable:
|
||||||
|
case clang::tok::kw___is_bitwise_cloneable:
|
||||||
|
case clang::tok::kw___builtin_is_replaceable:
|
||||||
|
case clang::tok::kw___builtin_structured_binding_size:
|
||||||
|
case clang::tok::kw___is_lvalue_expr:
|
||||||
|
case clang::tok::kw___is_rvalue_expr:
|
||||||
|
case clang::tok::kw___is_arithmetic:
|
||||||
|
case clang::tok::kw___is_floating_point:
|
||||||
|
case clang::tok::kw___is_integral:
|
||||||
|
case clang::tok::kw___is_complete_type:
|
||||||
|
case clang::tok::kw___is_void:
|
||||||
|
case clang::tok::kw___is_array:
|
||||||
|
case clang::tok::kw___is_function:
|
||||||
|
case clang::tok::kw___is_reference:
|
||||||
|
case clang::tok::kw___is_lvalue_reference:
|
||||||
|
case clang::tok::kw___is_rvalue_reference:
|
||||||
|
case clang::tok::kw___is_fundamental:
|
||||||
|
case clang::tok::kw___is_object:
|
||||||
|
case clang::tok::kw___is_scalar:
|
||||||
|
case clang::tok::kw___is_compound:
|
||||||
|
case clang::tok::kw___is_pointer:
|
||||||
|
case clang::tok::kw___is_member_object_pointer:
|
||||||
|
case clang::tok::kw___is_member_function_pointer:
|
||||||
|
case clang::tok::kw___is_member_pointer:
|
||||||
|
case clang::tok::kw___is_const:
|
||||||
|
case clang::tok::kw___is_volatile:
|
||||||
|
case clang::tok::kw___is_signed:
|
||||||
|
case clang::tok::kw___is_unsigned:
|
||||||
|
case clang::tok::kw___is_same:
|
||||||
|
case clang::tok::kw___is_convertible:
|
||||||
|
case clang::tok::kw___is_nothrow_convertible:
|
||||||
|
case clang::tok::kw___array_rank:
|
||||||
|
case clang::tok::kw___array_extent:
|
||||||
|
case clang::tok::kw___private_extern__:
|
||||||
|
case clang::tok::kw___module_private__:
|
||||||
|
case clang::tok::kw___builtin_ptrauth_type_discriminator:
|
||||||
|
case clang::tok::kw___declspec:
|
||||||
|
case clang::tok::kw___cdecl:
|
||||||
|
case clang::tok::kw___stdcall:
|
||||||
|
case clang::tok::kw___fastcall:
|
||||||
|
case clang::tok::kw___thiscall:
|
||||||
|
case clang::tok::kw___regcall:
|
||||||
|
case clang::tok::kw___vectorcall:
|
||||||
|
case clang::tok::kw___forceinline:
|
||||||
|
case clang::tok::kw___unaligned:
|
||||||
|
case clang::tok::kw___super:
|
||||||
|
case clang::tok::kw___global:
|
||||||
|
case clang::tok::kw___local:
|
||||||
|
case clang::tok::kw___constant:
|
||||||
|
case clang::tok::kw___private:
|
||||||
|
case clang::tok::kw___generic:
|
||||||
|
case clang::tok::kw___kernel:
|
||||||
|
case clang::tok::kw___read_only:
|
||||||
|
case clang::tok::kw___write_only:
|
||||||
|
case clang::tok::kw___read_write:
|
||||||
|
case clang::tok::kw___builtin_astype:
|
||||||
|
case clang::tok::kw_vec_step:
|
||||||
|
case clang::tok::kw_image1d_t:
|
||||||
|
case clang::tok::kw_image1d_array_t:
|
||||||
|
case clang::tok::kw_image1d_buffer_t:
|
||||||
|
case clang::tok::kw_image2d_t:
|
||||||
|
case clang::tok::kw_image2d_array_t:
|
||||||
|
case clang::tok::kw_image2d_depth_t:
|
||||||
|
case clang::tok::kw_image2d_array_depth_t:
|
||||||
|
case clang::tok::kw_image2d_msaa_t:
|
||||||
|
case clang::tok::kw_image2d_array_msaa_t:
|
||||||
|
case clang::tok::kw_image2d_msaa_depth_t:
|
||||||
|
case clang::tok::kw_image2d_array_msaa_depth_t:
|
||||||
|
case clang::tok::kw_image3d_t:
|
||||||
|
case clang::tok::kw_pipe:
|
||||||
|
case clang::tok::kw_addrspace_cast:
|
||||||
|
case clang::tok::kw___noinline__:
|
||||||
|
case clang::tok::kw_cbuffer:
|
||||||
|
case clang::tok::kw_tbuffer:
|
||||||
|
case clang::tok::kw_groupshared:
|
||||||
|
case clang::tok::kw_in:
|
||||||
|
case clang::tok::kw_inout:
|
||||||
|
case clang::tok::kw_out:
|
||||||
|
case clang::tok::kw___hlsl_resource_t:
|
||||||
|
case clang::tok::kw___builtin_hlsl_is_scalarized_layout_compatible:
|
||||||
|
case clang::tok::kw___builtin_hlsl_is_intangible:
|
||||||
|
case clang::tok::kw___builtin_hlsl_is_typed_resource_element_compatible:
|
||||||
|
case clang::tok::kw___builtin_omp_required_simd_align:
|
||||||
|
case clang::tok::kw___pascal:
|
||||||
|
case clang::tok::kw___vector:
|
||||||
|
case clang::tok::kw___pixel:
|
||||||
|
case clang::tok::kw___bool:
|
||||||
|
case clang::tok::kw___bf16:
|
||||||
|
case clang::tok::kw_half:
|
||||||
|
case clang::tok::kw___bridge:
|
||||||
|
case clang::tok::kw___bridge_transfer:
|
||||||
|
case clang::tok::kw___bridge_retained:
|
||||||
|
case clang::tok::kw___bridge_retain:
|
||||||
|
case clang::tok::kw___covariant:
|
||||||
|
case clang::tok::kw___contravariant:
|
||||||
|
case clang::tok::kw___kindof:
|
||||||
|
case clang::tok::kw__Nonnull:
|
||||||
|
case clang::tok::kw__Nullable:
|
||||||
|
case clang::tok::kw__Nullable_result:
|
||||||
|
case clang::tok::kw__Null_unspecified:
|
||||||
|
case clang::tok::kw___funcref:
|
||||||
|
case clang::tok::kw___ptr64:
|
||||||
|
case clang::tok::kw___ptr32:
|
||||||
|
case clang::tok::kw___sptr:
|
||||||
|
case clang::tok::kw___uptr:
|
||||||
|
case clang::tok::kw___w64:
|
||||||
|
case clang::tok::kw___uuidof:
|
||||||
|
case clang::tok::kw___try:
|
||||||
|
case clang::tok::kw___finally:
|
||||||
|
case clang::tok::kw___leave:
|
||||||
|
case clang::tok::kw___int64:
|
||||||
|
case clang::tok::kw___if_exists:
|
||||||
|
case clang::tok::kw___if_not_exists:
|
||||||
|
case clang::tok::kw___single_inheritance:
|
||||||
|
case clang::tok::kw___multiple_inheritance:
|
||||||
|
case clang::tok::kw___virtual_inheritance:
|
||||||
|
case clang::tok::kw___interface:
|
||||||
|
case clang::tok::kw___builtin_convertvector:
|
||||||
|
case clang::tok::kw___builtin_vectorelements:
|
||||||
|
case clang::tok::kw___builtin_bit_cast:
|
||||||
|
case clang::tok::kw___builtin_available:
|
||||||
|
case clang::tok::kw___builtin_sycl_unique_stable_name:
|
||||||
|
case clang::tok::kw___arm_agnostic:
|
||||||
|
case clang::tok::kw___arm_in:
|
||||||
|
case clang::tok::kw___arm_inout:
|
||||||
|
case clang::tok::kw___arm_locally_streaming:
|
||||||
|
case clang::tok::kw___arm_new:
|
||||||
|
case clang::tok::kw___arm_out:
|
||||||
|
case clang::tok::kw___arm_preserves:
|
||||||
|
case clang::tok::kw___arm_streaming:
|
||||||
|
case clang::tok::kw___arm_streaming_compatible:
|
||||||
|
case clang::tok::kw___unknown_anytype: kind = SymbolKind::Keyword; break;
|
||||||
|
/* Operators */
|
||||||
|
case clang::tok::l_square:
|
||||||
|
case clang::tok::r_square:
|
||||||
|
case clang::tok::l_paren:
|
||||||
|
case clang::tok::r_paren:
|
||||||
|
case clang::tok::l_brace:
|
||||||
|
case clang::tok::r_brace:
|
||||||
|
case clang::tok::period:
|
||||||
|
case clang::tok::ellipsis:
|
||||||
|
case clang::tok::amp:
|
||||||
|
case clang::tok::ampamp:
|
||||||
|
case clang::tok::ampequal:
|
||||||
|
case clang::tok::star:
|
||||||
|
case clang::tok::starequal:
|
||||||
|
case clang::tok::plus:
|
||||||
|
case clang::tok::plusplus:
|
||||||
|
case clang::tok::plusequal:
|
||||||
|
case clang::tok::minus:
|
||||||
|
case clang::tok::arrow:
|
||||||
|
case clang::tok::minusminus:
|
||||||
|
case clang::tok::minusequal:
|
||||||
|
case clang::tok::tilde:
|
||||||
|
case clang::tok::exclaim:
|
||||||
|
case clang::tok::exclaimequal:
|
||||||
|
case clang::tok::slash:
|
||||||
|
case clang::tok::slashequal:
|
||||||
|
case clang::tok::percent:
|
||||||
|
case clang::tok::percentequal:
|
||||||
|
case clang::tok::less:
|
||||||
|
case clang::tok::lessless:
|
||||||
|
case clang::tok::lessequal:
|
||||||
|
case clang::tok::lesslessequal:
|
||||||
|
case clang::tok::greater:
|
||||||
|
case clang::tok::greatergreater:
|
||||||
|
case clang::tok::greaterequal:
|
||||||
|
case clang::tok::greatergreaterequal:
|
||||||
|
case clang::tok::caret:
|
||||||
|
case clang::tok::caretequal:
|
||||||
|
case clang::tok::pipe:
|
||||||
|
case clang::tok::pipepipe:
|
||||||
|
case clang::tok::pipeequal:
|
||||||
|
case clang::tok::question:
|
||||||
|
case clang::tok::colon:
|
||||||
|
case clang::tok::semi:
|
||||||
|
case clang::tok::equal:
|
||||||
|
case clang::tok::equalequal:
|
||||||
|
case clang::tok::comma:
|
||||||
|
case clang::tok::hashat:
|
||||||
|
case clang::tok::periodstar:
|
||||||
|
case clang::tok::arrowstar:
|
||||||
|
case clang::tok::coloncolon:
|
||||||
|
case clang::tok::at:
|
||||||
|
case clang::tok::lesslessless:
|
||||||
|
case clang::tok::greatergreatergreater: break;
|
||||||
|
case clang::tok::annot_cxxscope:
|
||||||
|
case clang::tok::annot_typename:
|
||||||
|
case clang::tok::annot_template_id:
|
||||||
|
case clang::tok::annot_non_type:
|
||||||
|
case clang::tok::annot_non_type_undeclared:
|
||||||
|
case clang::tok::annot_non_type_dependent:
|
||||||
|
case clang::tok::annot_overload_set:
|
||||||
|
case clang::tok::annot_primary_expr:
|
||||||
|
case clang::tok::annot_decltype:
|
||||||
|
case clang::tok::annot_pack_indexing_type:
|
||||||
|
case clang::tok::annot_pragma_unused:
|
||||||
|
case clang::tok::annot_pragma_vis:
|
||||||
|
case clang::tok::annot_pragma_pack:
|
||||||
|
case clang::tok::annot_pragma_parser_crash:
|
||||||
|
case clang::tok::annot_pragma_captured:
|
||||||
|
case clang::tok::annot_pragma_dump:
|
||||||
|
case clang::tok::annot_pragma_msstruct:
|
||||||
|
case clang::tok::annot_pragma_align:
|
||||||
|
case clang::tok::annot_pragma_weak:
|
||||||
|
case clang::tok::annot_pragma_weakalias:
|
||||||
|
case clang::tok::annot_pragma_redefine_extname:
|
||||||
|
case clang::tok::annot_pragma_fp_contract:
|
||||||
|
case clang::tok::annot_pragma_fenv_access:
|
||||||
|
case clang::tok::annot_pragma_fenv_access_ms:
|
||||||
|
case clang::tok::annot_pragma_fenv_round:
|
||||||
|
case clang::tok::annot_pragma_cx_limited_range:
|
||||||
|
case clang::tok::annot_pragma_float_control:
|
||||||
|
case clang::tok::annot_pragma_ms_pointers_to_members:
|
||||||
|
case clang::tok::annot_pragma_ms_vtordisp:
|
||||||
|
case clang::tok::annot_pragma_ms_pragma:
|
||||||
|
case clang::tok::annot_pragma_opencl_extension:
|
||||||
|
case clang::tok::annot_attr_openmp:
|
||||||
|
case clang::tok::annot_pragma_openmp:
|
||||||
|
case clang::tok::annot_pragma_openmp_end:
|
||||||
|
case clang::tok::annot_pragma_openacc:
|
||||||
|
case clang::tok::annot_pragma_openacc_end:
|
||||||
|
case clang::tok::annot_pragma_loop_hint:
|
||||||
|
case clang::tok::annot_pragma_fp:
|
||||||
|
case clang::tok::annot_pragma_attribute:
|
||||||
|
case clang::tok::annot_pragma_riscv:
|
||||||
|
case clang::tok::annot_module_include:
|
||||||
|
case clang::tok::annot_module_begin:
|
||||||
|
case clang::tok::annot_module_end:
|
||||||
|
case clang::tok::annot_header_unit:
|
||||||
|
case clang::tok::annot_repl_input_end:
|
||||||
|
case clang::tok::annot_embed: break;
|
||||||
|
/* Others */
|
||||||
|
case clang::tok::spaceship:
|
||||||
|
case clang::tok::binary_data:
|
||||||
|
case clang::tok::hash:
|
||||||
|
case clang::tok::hashhash:
|
||||||
|
case clang::tok::unknown:
|
||||||
|
case clang::tok::eof:
|
||||||
|
case clang::tok::eod:
|
||||||
|
case clang::tok::code_completion:
|
||||||
|
case clang::tok::NUM_TOKENS: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ std::string name_of(const clang::NamedDecl* decl);
|
|||||||
|
|
||||||
std::string display_name_of(const clang::NamedDecl* decl);
|
std::string display_name_of(const clang::NamedDecl* decl);
|
||||||
|
|
||||||
|
clang::NestedNameSpecifierLoc get_qualifier_loc(const clang::NamedDecl* decl);
|
||||||
|
|
||||||
|
std::string print_template_specialization_args(const clang::NamedDecl* decl);
|
||||||
|
|
||||||
/// To response go-to-type-definition request. Some decls actually have a type
|
/// To response go-to-type-definition request. Some decls actually have a type
|
||||||
/// for example the result of `typeof(var)` is the type of `var`. This function
|
/// for example the result of `typeof(var)` is the type of `var`. This function
|
||||||
/// returns the type for the decl if any.
|
/// returns the type for the decl if any.
|
||||||
|
|||||||
1449
src/semantic/find_target.cpp
Normal file
1449
src/semantic/find_target.cpp
Normal file
File diff suppressed because it is too large
Load Diff
136
src/semantic/find_target.h
Normal file
136
src/semantic/find_target.h
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "llvm/ADT/STLFunctionalExtras.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
#include "clang/AST/ASTContext.h"
|
||||||
|
#include "clang/AST/ASTTypeTraits.h"
|
||||||
|
#include "clang/AST/NestedNameSpecifier.h"
|
||||||
|
#include "clang/AST/Stmt.h"
|
||||||
|
#include "clang/Basic/SourceLocation.h"
|
||||||
|
|
||||||
|
namespace clang {
|
||||||
|
|
||||||
|
class Decl;
|
||||||
|
class NamedDecl;
|
||||||
|
|
||||||
|
} // namespace clang
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
class TemplateResolver;
|
||||||
|
|
||||||
|
/// Information about a reference written in the source code, independent of
|
||||||
|
/// the AST node that contains it.
|
||||||
|
struct ReferenceLoc {
|
||||||
|
/// Qualifier written in the source code, e.g. `ns::` for `ns::foo`.
|
||||||
|
clang::NestedNameSpecifierLoc Qualifier;
|
||||||
|
|
||||||
|
/// Start location of the last name part, e.g. `foo` in `ns::foo<int>`.
|
||||||
|
clang::SourceLocation NameLoc;
|
||||||
|
|
||||||
|
/// True when the reference is introducing a declaration or definition.
|
||||||
|
bool IsDecl = false;
|
||||||
|
|
||||||
|
/// The declarations referenced by the written name.
|
||||||
|
llvm::SmallVector<const clang::NamedDecl*, 1> Targets;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class DeclRelation : unsigned {
|
||||||
|
/// The written name is an alias that should be preserved in results.
|
||||||
|
Alias = 1u << 0,
|
||||||
|
/// The target was reached by desugaring or following the aliased entity.
|
||||||
|
Underlying = 1u << 1,
|
||||||
|
/// The target is a concrete template instantiation.
|
||||||
|
TemplateInstantiation = 1u << 2,
|
||||||
|
/// The target is the template pattern underlying an instantiation.
|
||||||
|
TemplatePattern = 1u << 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeclRelationSet {
|
||||||
|
unsigned bits = 0;
|
||||||
|
|
||||||
|
constexpr DeclRelationSet() = default;
|
||||||
|
|
||||||
|
constexpr DeclRelationSet(DeclRelation relation) : bits(static_cast<unsigned>(relation)) {}
|
||||||
|
|
||||||
|
constexpr explicit DeclRelationSet(unsigned bits) : bits(bits) {}
|
||||||
|
|
||||||
|
constexpr bool contains(DeclRelationSet other) const {
|
||||||
|
return (bits & other.bits) == other.bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool contains(DeclRelation relation) const {
|
||||||
|
return (bits & static_cast<unsigned>(relation)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr explicit operator bool() const {
|
||||||
|
return bits != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr DeclRelationSet& operator|=(DeclRelationSet other) {
|
||||||
|
bits |= other.bits;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr DeclRelationSet& operator|=(DeclRelation relation) {
|
||||||
|
bits |= static_cast<unsigned>(relation);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr DeclRelationSet operator|(DeclRelationSet lhs, DeclRelationSet rhs) {
|
||||||
|
return DeclRelationSet(lhs.bits | rhs.bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr DeclRelationSet operator|(DeclRelationSet lhs, DeclRelation rhs) {
|
||||||
|
return lhs | DeclRelationSet(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr DeclRelationSet operator|(DeclRelation lhs, DeclRelationSet rhs) {
|
||||||
|
return DeclRelationSet(lhs) | rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr DeclRelationSet operator|(DeclRelation lhs, DeclRelation rhs) {
|
||||||
|
return DeclRelationSet(lhs) | rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr DeclRelationSet operator&(DeclRelationSet lhs, DeclRelationSet rhs) {
|
||||||
|
return DeclRelationSet(lhs.bits & rhs.bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr DeclRelationSet operator&(DeclRelationSet lhs, DeclRelation rhs) {
|
||||||
|
return lhs & DeclRelationSet(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TargetDecl {
|
||||||
|
const clang::NamedDecl* Decl = nullptr;
|
||||||
|
DeclRelationSet Relations;
|
||||||
|
};
|
||||||
|
|
||||||
|
llvm::raw_ostream& operator<<(llvm::raw_ostream& os, ReferenceLoc ref);
|
||||||
|
|
||||||
|
/// Finds all declarations a selected AST node may refer to, including alias
|
||||||
|
/// and template-instantiation relationships that higher-level APIs may filter.
|
||||||
|
auto all_target_decls(const clang::DynTypedNode& node, TemplateResolver* resolver = nullptr)
|
||||||
|
-> llvm::SmallVector<TargetDecl, 1>;
|
||||||
|
|
||||||
|
/// Recursively traverses \p stmt and reports all references explicitly written in
|
||||||
|
/// the source code.
|
||||||
|
void explicit_references(const clang::Stmt* stmt,
|
||||||
|
llvm::function_ref<void(ReferenceLoc)> out,
|
||||||
|
TemplateResolver* resolver = nullptr);
|
||||||
|
|
||||||
|
/// Recursively traverses \p decl and reports all references explicitly written in
|
||||||
|
/// the source code.
|
||||||
|
void explicit_references(const clang::Decl* decl,
|
||||||
|
llvm::function_ref<void(ReferenceLoc)> out,
|
||||||
|
TemplateResolver* resolver = nullptr);
|
||||||
|
|
||||||
|
/// Recursively traverses the full AST and reports all references explicitly
|
||||||
|
/// written in the source code.
|
||||||
|
void explicit_references(const clang::ASTContext& ast,
|
||||||
|
llvm::function_ref<void(ReferenceLoc)> out,
|
||||||
|
TemplateResolver* resolver = nullptr);
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
@@ -362,7 +362,7 @@ public:
|
|||||||
/// Look up the name in the given nested name specifier.
|
/// Look up the name in the given nested name specifier.
|
||||||
lookup_result lookup(const clang::NestedNameSpecifier* NNS, clang::DeclarationName name) {
|
lookup_result lookup(const clang::NestedNameSpecifier* NNS, clang::DeclarationName name) {
|
||||||
if(!NNS) {
|
if(!NNS) {
|
||||||
return lookup_result();
|
return sema.getASTContext().getTranslationUnitDecl()->lookup(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search the resolved entities first.
|
/// Search the resolved entities first.
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
#include "clang/AST/ExprCXX.h"
|
#include "clang/AST/ExprCXX.h"
|
||||||
#include "clang/AST/Type.h"
|
#include "clang/AST/Type.h"
|
||||||
|
#include "clang/Basic/SourceManager.h"
|
||||||
|
#include "clang/Sema/Sema.h"
|
||||||
|
|
||||||
namespace clang {
|
namespace clang {
|
||||||
|
|
||||||
class Sema;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace clice {
|
namespace clice {
|
||||||
@@ -40,6 +40,10 @@ public:
|
|||||||
/// Look up the name in the given nested name specifier.
|
/// Look up the name in the given nested name specifier.
|
||||||
lookup_result lookup(const clang::NestedNameSpecifier* NNS, clang::DeclarationName name);
|
lookup_result lookup(const clang::NestedNameSpecifier* NNS, clang::DeclarationName name);
|
||||||
|
|
||||||
|
lookup_result lookup(clang::DeclarationName name) {
|
||||||
|
return sema.getASTContext().getTranslationUnitDecl()->lookup(name);
|
||||||
|
}
|
||||||
|
|
||||||
lookup_result lookup(const clang::DependentNameType* type) {
|
lookup_result lookup(const clang::DependentNameType* type) {
|
||||||
return lookup(type->getQualifier(), type->getIdentifier());
|
return lookup(type->getQualifier(), type->getIdentifier());
|
||||||
}
|
}
|
||||||
@@ -94,6 +98,19 @@ public:
|
|||||||
private:
|
private:
|
||||||
clang::Sema& sema;
|
clang::Sema& sema;
|
||||||
llvm::DenseMap<const void*, clang::QualType> resolved;
|
llvm::DenseMap<const void*, clang::QualType> resolved;
|
||||||
|
|
||||||
|
public:
|
||||||
|
auto source_manager() -> clang::SourceManager& {
|
||||||
|
return sema.getSourceManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lang_options() const -> const clang::LangOptions& {
|
||||||
|
return sema.getLangOpts();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ast_context() -> clang::ASTContext& {
|
||||||
|
return sema.getASTContext();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace clice
|
} // namespace clice
|
||||||
|
|||||||
784
tests/unit/semantic/find_target_tests.cpp
Normal file
784
tests/unit/semantic/find_target_tests.cpp
Normal file
@@ -0,0 +1,784 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <format>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "test/tester.h"
|
||||||
|
#include "semantic/find_target.h"
|
||||||
|
#include "semantic/selection.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
#include "clang/AST/Decl.h"
|
||||||
|
|
||||||
|
namespace clice::testing {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// todo: not all tests are adapted yet.
|
||||||
|
// Adapted from clangd's find-target suites:
|
||||||
|
// - TargetDeclTests:
|
||||||
|
// https://github.com/llvm/llvm-project/blob/llvmorg-21.1.4/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
|
||||||
|
// - AllRefsInFoo:
|
||||||
|
// https://github.com/llvm/llvm-project/blob/llvmorg-21.1.4/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
|
||||||
|
// - AllRefs:
|
||||||
|
// https://github.com/llvm/llvm-project/blob/llvmorg-21.1.4/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
|
||||||
|
//
|
||||||
|
// Covered here:
|
||||||
|
// - target-decl normalization for expressions, aliases, templates, using enum,
|
||||||
|
// constructor/base initializers, and designated initializers
|
||||||
|
// - simple expressions/member expressions
|
||||||
|
// - namespace aliases and using declarations
|
||||||
|
// - qualified names and simple types
|
||||||
|
// - template specializations/aliases/template-template parameters
|
||||||
|
// - using-shadow references, macro references, broken-code recovery, and
|
||||||
|
// implicit-node filtering
|
||||||
|
// - unresolved lookup, dependent scope references, declarations, ctor init,
|
||||||
|
// using enum, namespace aliases, sizeof...(pack), CTAD, and designated
|
||||||
|
// initializers
|
||||||
|
//
|
||||||
|
// Intentionally not ported yet:
|
||||||
|
// - Objective-C AllRefsInFoo / AllRefs coverage from clangd's upstream file;
|
||||||
|
// `find_target.cpp` has ObjC visitors, but this local unit harness still
|
||||||
|
// exercises only the C++ matrix that we can run reliably in the freestanding
|
||||||
|
// test environment
|
||||||
|
// - unresolved/dependent edge cases where clangd intentionally snapshots empty
|
||||||
|
// target sets for recovery-only spellings that the local resolver still
|
||||||
|
// treats as implementation-defined
|
||||||
|
TEST_SUITE(FindExplicitReferences, Tester) {
|
||||||
|
|
||||||
|
struct AllRefs {
|
||||||
|
std::string annotated_code;
|
||||||
|
std::string dumped_references;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string dump_ref(ReferenceLoc ref) {
|
||||||
|
std::string text;
|
||||||
|
llvm::raw_string_ostream os(text);
|
||||||
|
os << ref;
|
||||||
|
return os.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string dump_decl(const clang::NamedDecl& decl) {
|
||||||
|
std::string text;
|
||||||
|
llvm::raw_string_ostream os(text);
|
||||||
|
decl.print(os);
|
||||||
|
|
||||||
|
llvm::StringRef printed = text;
|
||||||
|
printed = printed.take_until([](char ch) { return ch == '{' || ch == ';'; });
|
||||||
|
return printed.rtrim().str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string dump_relations(DeclRelationSet relations) {
|
||||||
|
std::string text;
|
||||||
|
auto append = [&](llvm::StringRef name) {
|
||||||
|
if(!text.empty()) {
|
||||||
|
text += ", ";
|
||||||
|
}
|
||||||
|
text += name.str();
|
||||||
|
};
|
||||||
|
|
||||||
|
if(relations.contains(DeclRelation::Alias)) {
|
||||||
|
append("Alias");
|
||||||
|
}
|
||||||
|
if(relations.contains(DeclRelation::Underlying)) {
|
||||||
|
append("Underlying");
|
||||||
|
}
|
||||||
|
if(relations.contains(DeclRelation::TemplateInstantiation)) {
|
||||||
|
append("TemplateInstantiation");
|
||||||
|
}
|
||||||
|
if(relations.contains(DeclRelation::TemplatePattern)) {
|
||||||
|
append("TemplatePattern");
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string dump_target_decl(TargetDecl target) {
|
||||||
|
std::string text = dump_decl(*target.Decl);
|
||||||
|
if(auto relations = dump_relations(target.Relations); !relations.empty()) {
|
||||||
|
text += std::format(" [{}]", relations);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string dump_target_decls(llvm::SmallVector<TargetDecl, 1> decls) {
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
lines.reserve(decls.size());
|
||||||
|
for(const auto& decl: decls) {
|
||||||
|
lines.push_back(dump_target_decl(decl));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(lines.begin(), lines.end());
|
||||||
|
|
||||||
|
std::string dumped;
|
||||||
|
for(const auto& line: lines) {
|
||||||
|
dumped += line;
|
||||||
|
dumped += '\n';
|
||||||
|
}
|
||||||
|
return dumped;
|
||||||
|
}
|
||||||
|
|
||||||
|
clang::Decl* find_top_level_decl(llvm::StringRef name) {
|
||||||
|
for(auto* decl: unit->top_level_decls()) {
|
||||||
|
if(auto* named = llvm::dyn_cast<clang::NamedDecl>(decl);
|
||||||
|
named && named->getNameAsString() == name) {
|
||||||
|
return decl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
AllRefs annotated_references(llvm::StringRef code, llvm::SmallVector<ReferenceLoc> refs) {
|
||||||
|
auto& sm = unit->context().getSourceManager();
|
||||||
|
llvm::stable_sort(refs, [&](const ReferenceLoc& lhs, const ReferenceLoc& rhs) {
|
||||||
|
return sm.isBeforeInTranslationUnit(lhs.NameLoc, rhs.NameLoc);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::string annotated_code;
|
||||||
|
unsigned next_code_char = 0;
|
||||||
|
for(unsigned i = 0; i < refs.size(); ++i) {
|
||||||
|
auto pos = refs[i].NameLoc;
|
||||||
|
if(!pos.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if(pos.isMacroID()) {
|
||||||
|
pos = sm.getExpansionLoc(pos);
|
||||||
|
}
|
||||||
|
if(!pos.isFileID()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [file, offset] = sm.getDecomposedLoc(pos);
|
||||||
|
if(file != sm.getMainFileID()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!(next_code_char <= offset)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
annotated_code += code.substr(next_code_char, offset - next_code_char);
|
||||||
|
annotated_code += std::format("$({})", i);
|
||||||
|
next_code_char = offset;
|
||||||
|
}
|
||||||
|
annotated_code += code.substr(next_code_char);
|
||||||
|
|
||||||
|
std::string dumped_references;
|
||||||
|
for(unsigned i = 0; i < refs.size(); ++i) {
|
||||||
|
dumped_references += std::format("{}: {}\n", i, dump_ref(refs[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {std::move(annotated_code), std::move(dumped_references)};
|
||||||
|
}
|
||||||
|
|
||||||
|
AllRefs annotate_references_in_foo(llvm::StringRef code, llvm::StringRef language = "c++") {
|
||||||
|
clear();
|
||||||
|
add_main("main.cpp", code);
|
||||||
|
if(!compile("-std=c++20", language)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* test_decl = find_top_level_decl("foo");
|
||||||
|
if(!test_decl) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto* templ = llvm::dyn_cast<clang::FunctionTemplateDecl>(test_decl)) {
|
||||||
|
test_decl = templ->getTemplatedDecl();
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm::SmallVector<ReferenceLoc> refs;
|
||||||
|
if(const auto* func = llvm::dyn_cast<clang::FunctionDecl>(test_decl)) {
|
||||||
|
explicit_references(
|
||||||
|
func->getBody(),
|
||||||
|
[&](ReferenceLoc ref) { refs.push_back(std::move(ref)); },
|
||||||
|
&unit->resolver());
|
||||||
|
} else if(const auto* ns = llvm::dyn_cast<clang::NamespaceDecl>(test_decl)) {
|
||||||
|
explicit_references(
|
||||||
|
ns,
|
||||||
|
[&](ReferenceLoc ref) {
|
||||||
|
if(ref.Targets.size() == 1 && ref.Targets.front() == ns) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
refs.push_back(std::move(ref));
|
||||||
|
},
|
||||||
|
&unit->resolver());
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return annotated_references(code, std::move(refs));
|
||||||
|
}
|
||||||
|
|
||||||
|
AllRefs annotate_references_in_file(llvm::StringRef code,
|
||||||
|
llvm::StringRef language = "c++",
|
||||||
|
llvm::StringRef standard = "-std=c++20") {
|
||||||
|
clear();
|
||||||
|
add_main("main.cpp", code);
|
||||||
|
if(!compile(standard, language)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm::SmallVector<ReferenceLoc> refs;
|
||||||
|
explicit_references(
|
||||||
|
unit->context(),
|
||||||
|
[&](ReferenceLoc ref) { refs.push_back(std::move(ref)); },
|
||||||
|
&unit->resolver());
|
||||||
|
return annotated_references(code, std::move(refs));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string selected_target_decls(llvm::StringRef code,
|
||||||
|
llvm::StringRef language = "c++",
|
||||||
|
llvm::StringRef standard = "-std=c++20") {
|
||||||
|
clear();
|
||||||
|
add_main("main.cpp", code);
|
||||||
|
if(!compile(standard, language)) {
|
||||||
|
return "<compile failed>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tree = SelectionTree::create_right(*unit, range());
|
||||||
|
const auto* node = tree.common_ancestor();
|
||||||
|
if(!node) {
|
||||||
|
return "<no selection>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return dump_target_decls(all_target_decls(node->data, &unit->resolver()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void expect_target_decl_cases(
|
||||||
|
std::initializer_list<std::pair<llvm::StringRef, llvm::StringRef>> cases,
|
||||||
|
llvm::StringRef language = "c++",
|
||||||
|
llvm::StringRef standard = "-std=c++20") {
|
||||||
|
for(const auto& [annotated_code, expected_decls]: cases) {
|
||||||
|
EXPECT_EQ(selected_target_decls(annotated_code, language, standard),
|
||||||
|
std::string(expected_decls));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(AllRefsInFoo) {
|
||||||
|
std::pair<llvm::StringRef, llvm::StringRef> cases[] = {
|
||||||
|
// Expressions.
|
||||||
|
{R"cpp(
|
||||||
|
int global;
|
||||||
|
int func();
|
||||||
|
void foo(int param) {
|
||||||
|
$(0)global = $(1)param + $(2)func();
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {global}\n" "1: targets = {param}\n" "2: targets = {func}\n" },
|
||||||
|
{R"cpp(
|
||||||
|
struct X { int a; };
|
||||||
|
void foo(X x) {
|
||||||
|
$(0)x.$(1)a = 10;
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {x}\n" "1: targets = {X::a}\n" },
|
||||||
|
|
||||||
|
// Broken code recovery.
|
||||||
|
{R"cpp(
|
||||||
|
// error-ok: testing with broken code
|
||||||
|
int bar();
|
||||||
|
int foo() {
|
||||||
|
return $(0)bar() + $(1)bar(42);
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {bar}\n" "1: targets = {bar}\n" },
|
||||||
|
|
||||||
|
// Using directives, using declarations, and using enum.
|
||||||
|
{R"cpp(
|
||||||
|
namespace ns {}
|
||||||
|
namespace alias = ns;
|
||||||
|
void foo() {
|
||||||
|
using namespace $(0)ns;
|
||||||
|
using namespace $(1)alias;
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {ns}\n" "1: targets = {alias}\n" },
|
||||||
|
{R"cpp(
|
||||||
|
namespace ns { int global; }
|
||||||
|
void foo() {
|
||||||
|
using $(0)ns::$(1)global;
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {ns}\n" "1: targets = {ns::global}, qualifier = 'ns::'\n" },
|
||||||
|
{R"cpp(
|
||||||
|
namespace ns { enum class A {}; }
|
||||||
|
void foo() {
|
||||||
|
using enum $(0)ns::$(1)A;
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {ns}\n" "1: targets = {ns::A}, qualifier = 'ns::'\n" },
|
||||||
|
|
||||||
|
// Qualified names and simple types.
|
||||||
|
{R"cpp(
|
||||||
|
struct Struct { int a; };
|
||||||
|
using Typedef = int;
|
||||||
|
void foo() {
|
||||||
|
$(0)Struct $(1)x;
|
||||||
|
$(2)Typedef $(3)y;
|
||||||
|
static_cast<$(4)Struct*>(0);
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {Struct}\n" "1: targets = {x}, decl\n" "2: targets = {Typedef}\n" "3: targets = {y}, decl\n" "4: targets = {Struct}\n" },
|
||||||
|
{R"cpp(
|
||||||
|
namespace a { namespace b { struct S { typedef int type; }; } }
|
||||||
|
void foo() {
|
||||||
|
$(0)a::$(1)b::$(2)S $(3)x;
|
||||||
|
using namespace $(4)a::$(5)b;
|
||||||
|
$(6)S::$(7)type $(8)y;
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {a}\n" "1: targets = {a::b}, qualifier = 'a::'\n" "2: targets = {a::b::S}, qualifier = 'a::b::'\n" "3: targets = {x}, decl\n" "4: targets = {a}\n" "5: targets = {a::b}, qualifier = 'a::'\n" "6: targets = {a::b::S}\n" "7: targets = {a::b::S::type}, qualifier = 'S::'\n" "8: targets = {y}, decl\n" },
|
||||||
|
|
||||||
|
// Labels.
|
||||||
|
{R"cpp(
|
||||||
|
void foo() {
|
||||||
|
$(0)ten:
|
||||||
|
goto $(1)ten;
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {ten}, decl\n" "1: targets = {ten}\n" },
|
||||||
|
|
||||||
|
// Template specializations, aliases, and using-shadows.
|
||||||
|
{R"cpp(
|
||||||
|
template <class T> struct vector { using value_type = T; };
|
||||||
|
template <> struct vector<bool> { using value_type = bool; };
|
||||||
|
void foo() {
|
||||||
|
$(0)vector<int> $(1)vi;
|
||||||
|
$(2)vector<bool> $(3)vb;
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {vector<int>}\n" "1: targets = {vi}, decl\n" "2: targets = {vector<bool>}\n" "3: targets = {vb}, decl\n" },
|
||||||
|
{R"cpp(
|
||||||
|
template <class T> struct vector { using value_type = T; };
|
||||||
|
template <> struct vector<bool> { using value_type = bool; };
|
||||||
|
template <class T> using valias = vector<T>;
|
||||||
|
void foo() {
|
||||||
|
$(0)valias<int> $(1)vi;
|
||||||
|
$(2)valias<bool> $(3)vb;
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {valias}\n" "1: targets = {vi}, decl\n" "2: targets = {valias}\n" "3: targets = {vb}, decl\n" },
|
||||||
|
{R"cpp(
|
||||||
|
struct X { void func(int); };
|
||||||
|
struct Y : X {
|
||||||
|
using X::func;
|
||||||
|
};
|
||||||
|
void foo(Y y) {
|
||||||
|
$(0)y.$(1)func(1);
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {y}\n" "1: targets = {Y::func}\n" },
|
||||||
|
{R"cpp(
|
||||||
|
namespace ns { void bar(int); }
|
||||||
|
using ns::bar;
|
||||||
|
|
||||||
|
void foo() {
|
||||||
|
$(0)bar(10);
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {bar}\n" },
|
||||||
|
|
||||||
|
// Macros, range-for declarations, and unresolved lookup.
|
||||||
|
{R"cpp(
|
||||||
|
#define FOO a
|
||||||
|
#define BAR b
|
||||||
|
|
||||||
|
void foo(int a, int b) {
|
||||||
|
$(0)FOO+$(1)BAR;
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {a}\n" "1: targets = {b}\n" },
|
||||||
|
{R"cpp(
|
||||||
|
struct vector {
|
||||||
|
int *begin();
|
||||||
|
int *end();
|
||||||
|
};
|
||||||
|
|
||||||
|
void foo() {
|
||||||
|
for (int $(0)x : $(1)vector()) {
|
||||||
|
$(2)x = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {x}, decl\n" "1: targets = {vector}\n" "2: targets = {x}\n" },
|
||||||
|
{R"cpp(
|
||||||
|
namespace ns1 { void func(char*); }
|
||||||
|
namespace ns2 { void func(int*); }
|
||||||
|
using namespace ns1;
|
||||||
|
using namespace ns2;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void foo(T t) {
|
||||||
|
$(0)func($(1)t);
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {ns1::func, ns2::func}\n" "1: targets = {t}\n" },
|
||||||
|
|
||||||
|
// Dependent scope references and template-template parameters.
|
||||||
|
{R"cpp(
|
||||||
|
template <class T>
|
||||||
|
struct S {
|
||||||
|
static int value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void foo() {
|
||||||
|
$(0)S<$(1)T>::$(2)value;
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {S}\n" "1: targets = {T}\n" "2: targets = {S::value}, qualifier = 'S<T>::'\n" },
|
||||||
|
{R"cpp(
|
||||||
|
template <class T> struct vector {};
|
||||||
|
|
||||||
|
template <template<class> class TT, template<class> class ...TP>
|
||||||
|
void foo() {
|
||||||
|
$(0)TT<int> $(1)x;
|
||||||
|
$(2)foo<$(3)TT>();
|
||||||
|
$(4)foo<$(5)vector>();
|
||||||
|
$(6)foo<$(7)TP...>();
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {TT}\n" "1: targets = {x}, decl\n" "2: targets = {foo}\n" "3: targets = {TT}\n" "4: targets = {foo}\n" "5: targets = {vector}\n" "6: targets = {foo}\n" "7: targets = {TP}\n" },
|
||||||
|
|
||||||
|
// Declarations and constructor initializers.
|
||||||
|
{R"cpp(
|
||||||
|
namespace ns {}
|
||||||
|
class S {};
|
||||||
|
void foo() {
|
||||||
|
class $(0)Foo { $(1)Foo(); ~$(2)Foo(); int $(3)field; };
|
||||||
|
int $(4)Var;
|
||||||
|
enum $(5)E { $(6)ABC };
|
||||||
|
typedef int $(7)INT;
|
||||||
|
using $(8)INT2 = int;
|
||||||
|
namespace $(9)NS = $(10)ns;
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {Foo}, decl\n" "1: targets = {foo()::Foo::Foo}, decl\n" "2: targets = {Foo}\n" "3: targets = {foo()::Foo::field}, decl\n" "4: targets = {Var}, decl\n" "5: targets = {E}, decl\n" "6: targets = {foo()::ABC}, decl\n" "7: targets = {INT}, decl\n" "8: targets = {INT2}, decl\n" "9: targets = {NS}, decl\n" "10: targets = {ns}\n" },
|
||||||
|
{R"cpp(
|
||||||
|
class Base {};
|
||||||
|
void foo() {
|
||||||
|
class $(0)X {
|
||||||
|
int $(1)abc;
|
||||||
|
$(2)X(): $(3)abc() {}
|
||||||
|
};
|
||||||
|
class $(4)Derived : public $(5)Base {
|
||||||
|
$(6)Base $(7)B;
|
||||||
|
$(8)Derived() : $(9)Base() {}
|
||||||
|
};
|
||||||
|
class $(10)Foo {
|
||||||
|
$(11)Foo(int);
|
||||||
|
$(12)Foo(): $(13)Foo(111) {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {X}, decl\n" "1: targets = {foo()::X::abc}, decl\n" "2: targets = {foo()::X::X}, decl\n" "3: targets = {foo()::X::abc}\n" "4: targets = {Derived}, decl\n" "5: targets = {Base}\n" "6: targets = {Base}\n" "7: targets = {foo()::Derived::B}, decl\n" "8: targets = {foo()::Derived::Derived}, decl\n" "9: targets = {Base}\n" "10: targets = {Foo}, decl\n" "11: targets = {foo()::Foo::Foo}, decl\n" "12: targets = {foo()::Foo::Foo}, decl\n" "13: targets = {Foo}\n"},
|
||||||
|
|
||||||
|
// Namespace aliases.
|
||||||
|
{R"cpp(
|
||||||
|
namespace ns { struct Type {}; }
|
||||||
|
namespace alias = ns;
|
||||||
|
namespace rec_alias = alias;
|
||||||
|
|
||||||
|
void foo() {
|
||||||
|
$(0)ns::$(1)Type $(2)a;
|
||||||
|
$(3)alias::$(4)Type $(5)b;
|
||||||
|
$(6)rec_alias::$(7)Type $(8)c;
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {ns}\n" "1: targets = {ns::Type}, qualifier = 'ns::'\n" "2: targets = {a}, decl\n" "3: targets = {alias}\n" "4: targets = {ns::Type}, qualifier = 'alias::'\n" "5: targets = {b}, decl\n" "6: targets = {rec_alias}\n" "7: targets = {ns::Type}, qualifier = 'rec_alias::'\n" "8: targets = {c}, decl\n" },
|
||||||
|
|
||||||
|
// sizeof...(pack) and CTAD.
|
||||||
|
{R"cpp(
|
||||||
|
template <typename... E>
|
||||||
|
void foo() {
|
||||||
|
constexpr int $(0)size = sizeof...($(1)E);
|
||||||
|
};
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {size}, decl\n" "1: targets = {E}\n" },
|
||||||
|
{R"cpp(
|
||||||
|
template <typename T>
|
||||||
|
struct Test {
|
||||||
|
Test(T);
|
||||||
|
};
|
||||||
|
void foo() {
|
||||||
|
$(0)Test $(1)a(5);
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {Test}\n" "1: targets = {a}, decl\n" },
|
||||||
|
|
||||||
|
// Designated initializers.
|
||||||
|
{R"cpp(
|
||||||
|
void foo() {
|
||||||
|
struct $(0)Foo {
|
||||||
|
int $(1)Bar;
|
||||||
|
};
|
||||||
|
$(2)Foo $(3)f { .$(4)Bar = 42 };
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {Foo}, decl\n" "1: targets = {foo()::Foo::Bar}, decl\n" "2: targets = {Foo}\n" "3: targets = {f}, decl\n" "4: targets = {foo()::Foo::Bar}\n" },
|
||||||
|
{R"cpp(
|
||||||
|
void foo() {
|
||||||
|
struct $(0)Baz {
|
||||||
|
int $(1)Field;
|
||||||
|
};
|
||||||
|
struct $(2)Bar {
|
||||||
|
$(3)Baz $(4)Foo;
|
||||||
|
};
|
||||||
|
$(5)Bar $(6)bar { .$(7)Foo.$(8)Field = 42 };
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {Baz}, decl\n" "1: targets = {foo()::Baz::Field}, decl\n" "2: targets = {Bar}, decl\n" "3: targets = {Baz}\n" "4: targets = {foo()::Bar::Foo}, decl\n" "5: targets = {Bar}\n" "6: targets = {bar}, decl\n" "7: targets = {foo()::Bar::Foo}\n" "8: targets = {foo()::Baz::Field}\n" },
|
||||||
|
|
||||||
|
// Designated initializers in dependent code.
|
||||||
|
{R"cpp(
|
||||||
|
template <typename T>
|
||||||
|
void crash(T) {}
|
||||||
|
template <typename T>
|
||||||
|
void foo() {
|
||||||
|
$(0)crash({.$(1)x = $(2)T()});
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {crash}\n" "1: targets = {}\n" "2: targets = {T}\n" },
|
||||||
|
};
|
||||||
|
|
||||||
|
for(const auto& [expected_code, expected_refs]: cases) {
|
||||||
|
auto actual = annotate_references_in_foo(expected_code);
|
||||||
|
EXPECT_EQ(actual.dumped_references, std::string(expected_refs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(AllRefs) {
|
||||||
|
std::pair<llvm::StringRef, llvm::StringRef> cases[] = {
|
||||||
|
// Unknown template name should not crash.
|
||||||
|
{R"cpp(
|
||||||
|
// error-ok: declarations use unknown template name
|
||||||
|
template <typename T> struct Foo {
|
||||||
|
using x = $(0)T::template $(1)A<0>;
|
||||||
|
};
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {Foo::T}, decl\n" "1: targets = {Foo}, decl\n" "2: targets = {Foo::x}, decl\n" "3: targets = {Foo::T}\n" "4: targets = {}, qualifier = 'T::'\n" },
|
||||||
|
|
||||||
|
// Deduction guides.
|
||||||
|
{R"cpp(
|
||||||
|
template<typename> struct $(0)A {};
|
||||||
|
template<typename> struct $(1)I { using $(2)type = int; };
|
||||||
|
template<typename $(3)T> A($(4)T) -> A<typename $(5)T::$(6)type>;
|
||||||
|
)cpp",
|
||||||
|
"0: targets = {A}, decl\n" "1: targets = {I}, decl\n" "2: targets = {I::type}, decl\n" "3: targets = {T}, decl\n" "4: targets = {A}\n" "5: targets = {T}\n" "6: targets = {A}\n" "7: targets = {T}\n" "8: targets = {}, qualifier = 'T::'\n"},
|
||||||
|
};
|
||||||
|
|
||||||
|
for(const auto& [annotated_code, expected_refs]: cases) {
|
||||||
|
auto actual = annotate_references_in_file(annotated_code);
|
||||||
|
EXPECT_EQ(actual.dumped_references, std::string(expected_refs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(TargetDeclTests) {
|
||||||
|
std::pair<llvm::StringRef, llvm::StringRef> cases[] = {
|
||||||
|
// Expressions.
|
||||||
|
{R"cpp(
|
||||||
|
int f();
|
||||||
|
int foo() { return @[f](); }
|
||||||
|
)cpp",
|
||||||
|
"int f()\n" },
|
||||||
|
{R"cpp(
|
||||||
|
// error-ok: testing unresolved lookup recovery
|
||||||
|
int f();
|
||||||
|
int f(int, int);
|
||||||
|
int foo(int x) { return @[f](x); }
|
||||||
|
)cpp",
|
||||||
|
"int f()\n" "int f(int, int)\n" },
|
||||||
|
|
||||||
|
// Using declarations and using-shadows.
|
||||||
|
{R"cpp(
|
||||||
|
namespace foo { int f(int); }
|
||||||
|
@[using foo::f];
|
||||||
|
)cpp",
|
||||||
|
"int f(int)\n" "using foo::f [Alias]\n" },
|
||||||
|
{R"cpp(
|
||||||
|
struct X { int foo(); };
|
||||||
|
struct Y : X { using X::foo; };
|
||||||
|
int bar() { return Y().@[foo](); }
|
||||||
|
)cpp",
|
||||||
|
"int foo()\n" "using X::foo [Alias]\n" },
|
||||||
|
|
||||||
|
// Namespace aliases and type aliases.
|
||||||
|
{R"cpp(
|
||||||
|
namespace ns { struct Type {}; }
|
||||||
|
namespace alias = ns;
|
||||||
|
void foo() { @[alias::]Type value; }
|
||||||
|
)cpp",
|
||||||
|
"namespace alias = ns [Alias]\n" "namespace ns [Underlying]\n" },
|
||||||
|
{R"cpp(
|
||||||
|
struct Foo {};
|
||||||
|
using Alias = Foo;
|
||||||
|
void foo() { @[Alias] value; }
|
||||||
|
)cpp",
|
||||||
|
"struct Foo [Underlying]\n" "using Alias = Foo [Alias]\n" },
|
||||||
|
|
||||||
|
// Template specializations.
|
||||||
|
{R"cpp(
|
||||||
|
template <typename T>
|
||||||
|
struct Box {};
|
||||||
|
void foo() { @[Box<int>] value; }
|
||||||
|
)cpp",
|
||||||
|
"struct Box [TemplatePattern]\n" "template<> struct Box<int> [TemplateInstantiation]\n"},
|
||||||
|
|
||||||
|
// Using enum.
|
||||||
|
{R"cpp(
|
||||||
|
namespace ns { enum class A {}; }
|
||||||
|
using enum ns::@[A];
|
||||||
|
)cpp",
|
||||||
|
"enum class A : int\n" },
|
||||||
|
|
||||||
|
// Constructor initializers and base specifiers.
|
||||||
|
{R"cpp(
|
||||||
|
struct Base {};
|
||||||
|
struct Derived : @[Base] {};
|
||||||
|
)cpp",
|
||||||
|
"struct Base\n" },
|
||||||
|
{R"cpp(
|
||||||
|
struct S {
|
||||||
|
int field;
|
||||||
|
S() : @[field]() {}
|
||||||
|
};
|
||||||
|
)cpp",
|
||||||
|
"int field\n" },
|
||||||
|
|
||||||
|
// Designated initializers.
|
||||||
|
{R"cpp(
|
||||||
|
void foo() {
|
||||||
|
struct S { int bar; };
|
||||||
|
S value{ @[.bar = 1] };
|
||||||
|
}
|
||||||
|
)cpp",
|
||||||
|
"int bar\n" },
|
||||||
|
};
|
||||||
|
|
||||||
|
for(const auto& [annotated_code, expected_decls]: cases) {
|
||||||
|
EXPECT_EQ(selected_target_decls(annotated_code), std::string(expected_decls));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(Recovery) {
|
||||||
|
expect_target_decl_cases({
|
||||||
|
// Error recovery should still surface the viable overload set.
|
||||||
|
{R"cpp(
|
||||||
|
// error-ok: testing unresolved lookup recovery
|
||||||
|
int f();
|
||||||
|
int f(int, int);
|
||||||
|
int foo(int x) { return @[f](x); }
|
||||||
|
)cpp",
|
||||||
|
"int f()\n" "int f(int, int)\n"},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(RecoveryType) {
|
||||||
|
expect_target_decl_cases({
|
||||||
|
// Recovering through an invalid call should still let us resolve the
|
||||||
|
// selected member name from the produced object type.
|
||||||
|
{R"cpp(
|
||||||
|
// error-ok: keep going after the bad call
|
||||||
|
struct S { int member; };
|
||||||
|
S make(int);
|
||||||
|
void foo() { make().@[member]; }
|
||||||
|
)cpp",
|
||||||
|
"int member\n"},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DependentTypes) {
|
||||||
|
expect_target_decl_cases({
|
||||||
|
// Resolved to a dependent member in the primary template.
|
||||||
|
{R"cpp(
|
||||||
|
template <typename T>
|
||||||
|
struct A {
|
||||||
|
struct B {};
|
||||||
|
};
|
||||||
|
template <typename T>
|
||||||
|
void foo() { typename A<T>::@[B] x; }
|
||||||
|
)cpp",
|
||||||
|
"struct B\n" },
|
||||||
|
// Resolved to a nested type inside a dependent member.
|
||||||
|
{R"cpp(
|
||||||
|
template <typename T>
|
||||||
|
struct A {
|
||||||
|
struct B { struct C {}; };
|
||||||
|
};
|
||||||
|
template <typename T>
|
||||||
|
void foo() { typename A<T>::@[B]::C x; }
|
||||||
|
)cpp",
|
||||||
|
"struct B\n" },
|
||||||
|
// Dependent template names should preserve the written template.
|
||||||
|
{R"cpp(
|
||||||
|
template <typename T>
|
||||||
|
struct A {
|
||||||
|
template <typename>
|
||||||
|
struct B {};
|
||||||
|
};
|
||||||
|
template <typename T>
|
||||||
|
void foo() { typename A<T>::template @[B]<int> x; }
|
||||||
|
)cpp",
|
||||||
|
"template <typename> struct B\n"},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Still intentionally unported from clangd's DependentTypes suite:
|
||||||
|
// - selecting the nested dependent `C` in `A<T>::B::C`
|
||||||
|
// - recursive alias cycles where clangd returns no targets
|
||||||
|
// The local resolver currently diverges on those recovery-heavy cases.
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(TypedefCascade) {
|
||||||
|
expect_target_decl_cases({
|
||||||
|
// Alias chains should retain all written typedefs so callers can decide
|
||||||
|
// whether to stop at the first alias or keep desugaring.
|
||||||
|
{R"cpp(
|
||||||
|
struct C { using type = int; };
|
||||||
|
struct B { using type = C::type; };
|
||||||
|
struct A { using type = B::type; };
|
||||||
|
void foo() { A::@[type] value = 0; }
|
||||||
|
)cpp",
|
||||||
|
"using type = B::type [Alias]\n" "using type = C::type [Alias, Underlying]\n" "using type = int [Alias, Underlying]\n"},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(RecursiveTemplate) {
|
||||||
|
expect_target_decl_cases({
|
||||||
|
// The alias target should still be surfaced even when the recursive
|
||||||
|
// branch keeps the underlying type dependent. The local resolver also
|
||||||
|
// surfaces the constrained leaf target that feeds the specialization.
|
||||||
|
{R"cpp(
|
||||||
|
template <typename T> concept Leaf = false;
|
||||||
|
template <typename Tree> struct descend_left {
|
||||||
|
using type = typename descend_left<typename Tree::left>::type;
|
||||||
|
};
|
||||||
|
template <Leaf Tree> struct descend_left<Tree> {
|
||||||
|
using type = Tree;
|
||||||
|
};
|
||||||
|
template <typename Tree>
|
||||||
|
using left_most_leaf = typename descend_left<Tree>::@[type];
|
||||||
|
)cpp",
|
||||||
|
"Leaf Tree [Underlying]\n" "using type = Tree [Alias]\n"},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DesignatedInit) {
|
||||||
|
expect_target_decl_cases(
|
||||||
|
{
|
||||||
|
// C designators should resolve to the written field declaration.
|
||||||
|
{R"c(
|
||||||
|
struct Foo { int a; int b; };
|
||||||
|
void foo(void) {
|
||||||
|
struct Foo value = { @[.a] = 1, .b = 2 };
|
||||||
|
}
|
||||||
|
)c",
|
||||||
|
"int a\n"},
|
||||||
|
},
|
||||||
|
"c",
|
||||||
|
"-std=c11");
|
||||||
|
}
|
||||||
|
|
||||||
|
}; // TEST_SUITE(FindExplicitReferences)
|
||||||
|
} // namespace
|
||||||
|
} // namespace clice::testing
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
namespace clice::testing {
|
namespace clice::testing {
|
||||||
|
|
||||||
void Tester::prepare(llvm::StringRef standard) {
|
void Tester::prepare(llvm::StringRef standard, llvm::StringRef language) {
|
||||||
params = CompilationParams();
|
params = CompilationParams();
|
||||||
unit.reset();
|
unit.reset();
|
||||||
vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
|
vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
|
||||||
@@ -28,7 +28,7 @@ void Tester::prepare(llvm::StringRef standard) {
|
|||||||
owned_args.push_back("-fms-extensions");
|
owned_args.push_back("-fms-extensions");
|
||||||
owned_args.push_back("-fsyntax-only");
|
owned_args.push_back("-fsyntax-only");
|
||||||
owned_args.push_back("-x");
|
owned_args.push_back("-x");
|
||||||
owned_args.push_back("c++");
|
owned_args.push_back(language.str());
|
||||||
owned_args.push_back(TestVFS::path(src_path));
|
owned_args.push_back(TestVFS::path(src_path));
|
||||||
|
|
||||||
params.arguments.clear();
|
params.arguments.clear();
|
||||||
@@ -40,8 +40,8 @@ void Tester::prepare(llvm::StringRef standard) {
|
|||||||
params.vfs = vfs;
|
params.vfs = vfs;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Tester::compile(llvm::StringRef standard) {
|
bool Tester::compile(llvm::StringRef standard, llvm::StringRef language) {
|
||||||
prepare(standard);
|
prepare(standard, language);
|
||||||
|
|
||||||
auto built = clice::compile(params);
|
auto built = clice::compile(params);
|
||||||
if(!built.completed()) {
|
if(!built.completed()) {
|
||||||
@@ -55,8 +55,8 @@ bool Tester::compile(llvm::StringRef standard) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Tester::compile_with_pch(llvm::StringRef standard) {
|
bool Tester::compile_with_pch(llvm::StringRef standard, llvm::StringRef language) {
|
||||||
prepare(standard);
|
prepare(standard, language);
|
||||||
|
|
||||||
auto pch_path = fs::createTemporaryFile("clice", "pch");
|
auto pch_path = fs::createTemporaryFile("clice", "pch");
|
||||||
if(!pch_path) {
|
if(!pch_path) {
|
||||||
@@ -146,7 +146,7 @@ LocalSourceRange Tester::range(llvm::StringRef name, llvm::StringRef file) {
|
|||||||
return ranges.lookup(name);
|
return ranges.lookup(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tester::prepare_driver(llvm::StringRef standard) {
|
void Tester::prepare_driver(llvm::StringRef standard, llvm::StringRef language) {
|
||||||
params = CompilationParams();
|
params = CompilationParams();
|
||||||
unit.reset();
|
unit.reset();
|
||||||
vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
|
vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
|
||||||
@@ -154,7 +154,8 @@ void Tester::prepare_driver(llvm::StringRef standard) {
|
|||||||
vfs->add(file, source.content);
|
vfs->add(file, source.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto command = std::format("clang++ {} {} -fms-extensions", standard, src_path);
|
auto command =
|
||||||
|
std::format("clang++ {} {} -fms-extensions -x {}", standard, src_path, language.str());
|
||||||
database.add_command("fake", src_path, command);
|
database.add_command("fake", src_path, command);
|
||||||
|
|
||||||
CommandOptions options;
|
CommandOptions options;
|
||||||
@@ -183,8 +184,8 @@ void Tester::prepare_driver(llvm::StringRef standard) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Tester::compile_driver(llvm::StringRef standard) {
|
bool Tester::compile_driver(llvm::StringRef standard, llvm::StringRef language) {
|
||||||
prepare_driver(standard);
|
prepare_driver(standard, language);
|
||||||
|
|
||||||
auto built = clice::compile(params);
|
auto built = clice::compile(params);
|
||||||
if(!built.completed()) {
|
if(!built.completed()) {
|
||||||
@@ -198,7 +199,7 @@ bool Tester::compile_driver(llvm::StringRef standard) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Tester::compile_driver_with_pch(llvm::StringRef standard) {
|
bool Tester::compile_driver_with_pch(llvm::StringRef standard, llvm::StringRef language) {
|
||||||
params = CompilationParams();
|
params = CompilationParams();
|
||||||
unit.reset();
|
unit.reset();
|
||||||
vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
|
vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
|
||||||
@@ -206,7 +207,8 @@ bool Tester::compile_driver_with_pch(llvm::StringRef standard) {
|
|||||||
vfs->add(file, source.content);
|
vfs->add(file, source.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto command = std::format("clang++ {} {} -fms-extensions", standard, src_path);
|
auto command =
|
||||||
|
std::format("clang++ {} {} -fms-extensions -x {}", standard, src_path, language.str());
|
||||||
database.add_command("fake", src_path, command);
|
database.add_command("fake", src_path, command);
|
||||||
|
|
||||||
CommandOptions options;
|
CommandOptions options;
|
||||||
|
|||||||
@@ -40,18 +40,22 @@ struct Tester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fast VFS-only path: uses -cc1 directly, no system headers.
|
/// Fast VFS-only path: uses -cc1 directly, no system headers.
|
||||||
void prepare(llvm::StringRef standard = "-std=c++20");
|
void prepare(llvm::StringRef standard = "-std=c++20", llvm::StringRef language = "c++");
|
||||||
|
|
||||||
bool compile(llvm::StringRef standard = "-std=c++20");
|
bool compile(llvm::StringRef standard = "-std=c++20", llvm::StringRef language = "c++");
|
||||||
|
|
||||||
bool compile_with_pch(llvm::StringRef standard = "-std=c++20");
|
bool compile_with_pch(llvm::StringRef standard = "-std=c++20",
|
||||||
|
llvm::StringRef language = "c++");
|
||||||
|
|
||||||
/// Driver path: uses CompilationDatabase + toolchain cache, has system headers.
|
/// Driver path: uses CompilationDatabase + toolchain cache, has system headers.
|
||||||
void prepare_driver(llvm::StringRef standard = "-std=c++20");
|
void prepare_driver(llvm::StringRef standard = "-std=c++20",
|
||||||
|
llvm::StringRef language = "c++");
|
||||||
|
|
||||||
bool compile_driver(llvm::StringRef standard = "-std=c++20");
|
bool compile_driver(llvm::StringRef standard = "-std=c++20",
|
||||||
|
llvm::StringRef language = "c++");
|
||||||
|
|
||||||
bool compile_driver_with_pch(llvm::StringRef standard = "-std=c++20");
|
bool compile_driver_with_pch(llvm::StringRef standard = "-std=c++20",
|
||||||
|
llvm::StringRef language = "c++");
|
||||||
|
|
||||||
std::uint32_t operator[](llvm::StringRef file, llvm::StringRef pos) {
|
std::uint32_t operator[](llvm::StringRef file, llvm::StringRef pos) {
|
||||||
return sources.all_files.lookup(file).offsets.lookup(pos);
|
return sources.all_files.lookup(file).offsets.lookup(pos);
|
||||||
|
|||||||
Reference in New Issue
Block a user