7 Commits

Author SHA1 Message Date
Myriad-Dreamin
9d75659fb1 feat: implement explicit_reference_targets 2026-04-04 03:18:45 +08:00
Myriad-Dreamin
09e95bbc7e feat: explore gap between clice and clangd 2026-04-04 01:51:56 +08:00
Myriad-Dreamin
69454812bf feat: explicit specify lexical tokens to handle 2026-04-04 00:38:22 +08:00
Myriad-Dreamin
511b71f19a dev: add module example 2026-04-04 00:04:55 +08:00
Myriad-Dreamin
ed8b8b7745 dev: add single-file sample 2026-04-03 23:49:03 +08:00
Myriad-Dreamin
a303e13f58 dev: add cmake-workspace sample 2026-04-03 23:48:08 +08:00
Myriad-Dreamin
72c0a74609 dev: watch socket 2026-04-03 23:47:58 +08:00
19 changed files with 3511 additions and 23 deletions

View 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()

View 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.

View 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.";
}

View File

@@ -0,0 +1,6 @@
#pragma once
#include <string>
#include <string_view>
std::string build_greeting(std::string_view name);

View 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.";
}
}

View File

@@ -0,0 +1,8 @@
#include "greeting.h"
#include <iostream>
int main() {
std::cout << build_greeting("clice") << '\n';
return 0;
}

View File

@@ -0,0 +1,8 @@
#include <iostream>
import sample.greeting;
int main() {
std::cout << sample::build_module_greeting("clice") << '\n';
return 0;
}

View File

@@ -0,0 +1,6 @@
#include <cstdio>
int main() {
printf("Hello, World!\n");
return 0;
}

View 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
View 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"

View File

@@ -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;
} }
} }

View File

@@ -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

File diff suppressed because it is too large Load Diff

136
src/semantic/find_target.h Normal file
View 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

View File

@@ -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.

View File

@@ -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

View 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

View File

@@ -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;

View File

@@ -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);