## Summary Initial integration of `CompileGraph` (#375) into `MasterServer`, enabling basic end-to-end C++20 module support: on-demand PCM building, dependency-ordered compilation, cascade invalidation on save, and diagnostic integration. This is a **first-pass implementation** — the core pipeline works, but there are known areas for follow-up: - PCM files go to system temp dir instead of `.clice/cache/`; no disk cleanup on invalidation - `run_build_drain` scans imports itself rather than delegating fully to CompileGraph - No incremental/partial rebuild (full PCM rebuild on any change) - Cycle detection is tested at unit level but integration-level coverage is minimal ## Changes ### Module dependency compilation (`master_server.cpp`) Before sending a file to the stateful worker, `run_build_drain` now: 1. Scans imports via `scan_precise()` to discover module dependencies 2. Compiles each dep through `compile_graph->compile()`, which recursively builds transitive PCMs 3. Handles implementation units — `module M;` implicitly needs the interface PCM 4. Passes all built PCMs to the stateful worker, excluding the file's own PCM 5. Skips compile on dep failure and resets `build_running` / `drain_scheduled` 6. Re-lookups iterators after `co_await` to avoid use-after-invalidation ### Cascade invalidation (`didSave` / `didClose`) - `didSave`: calls `compile_graph->update()` to mark transitive dependents dirty, removes stale PCM paths, schedules rebuilds for open dirtied files - `didClose`: cancels in-flight compilations for the closed file ### Other fixes in this PR - Debounce timers switched to `shared_ptr` to prevent use-after-free when `didClose` destroys the timer mid-wait - `fill_compile_args` returns `bool`; callers handle empty CDB gracefully - Adapt all `PositionMapper` call sites to the new `optional` return API from eventide ## Test plan - [x] 25 C++ unit tests for CompileGraph (cycles, partial failure, cancel, update, empty graph) - [x] 24 C++ integration tests with real clang PCM compilation - [x] 3 worker-level module tests (BuildPCM, PCM-dependent compile, multi-module) - [x] 26 Python LSP integration tests (single module through circular deps, hover, error diagnostics) - [x] 371 unit tests + 54 integration tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
113 lines
3.3 KiB
C++
113 lines
3.3 KiB
C++
#include <format>
|
|
#include <optional>
|
|
#include <string>
|
|
|
|
#include "feature/feature.h"
|
|
#include "semantic/ast_utility.h"
|
|
#include "semantic/selection.h"
|
|
#include "semantic/symbol_kind.h"
|
|
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/AST/Expr.h"
|
|
|
|
namespace clice::feature {
|
|
|
|
namespace {
|
|
|
|
auto symbol_name(SymbolKind kind) -> llvm::StringRef {
|
|
switch(kind) {
|
|
case SymbolKind::Module: return "module";
|
|
case SymbolKind::Namespace: return "namespace";
|
|
case SymbolKind::Class: return "class";
|
|
case SymbolKind::Struct: return "struct";
|
|
case SymbolKind::Union: return "union";
|
|
case SymbolKind::Enum: return "enum";
|
|
case SymbolKind::Type: return "type";
|
|
case SymbolKind::Concept: return "concept";
|
|
case SymbolKind::Field: return "field";
|
|
case SymbolKind::EnumMember: return "enum member";
|
|
case SymbolKind::Function: return "function";
|
|
case SymbolKind::Method: return "method";
|
|
case SymbolKind::Variable: return "variable";
|
|
case SymbolKind::Parameter: return "parameter";
|
|
case SymbolKind::Macro: return "macro";
|
|
default: return "symbol";
|
|
}
|
|
}
|
|
|
|
auto hover_markdown(const clang::NamedDecl& decl) -> std::string {
|
|
auto kind = SymbolKind::from(&decl);
|
|
auto name = ast::name_of(&decl);
|
|
return std::format("{}: {}", symbol_name(kind), name);
|
|
}
|
|
|
|
auto hover_range(CompilationUnitRef unit,
|
|
const clang::NamedDecl& decl,
|
|
const PositionMapper& converter) -> std::optional<protocol::Range> {
|
|
auto [fid, range] = unit.decompose_expansion_range(decl.getSourceRange());
|
|
if(fid != unit.interested_file() || !range.valid()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return protocol::Range{
|
|
.start = *converter.to_position(range.begin),
|
|
.end = *converter.to_position(range.end),
|
|
};
|
|
}
|
|
|
|
auto build_hover(CompilationUnitRef unit, const clang::NamedDecl& decl, PositionEncoding encoding)
|
|
-> protocol::Hover {
|
|
PositionMapper converter(unit.interested_content(), encoding);
|
|
|
|
protocol::MarkupContent content{
|
|
.kind = protocol::MarkupKind::Markdown,
|
|
.value = hover_markdown(decl),
|
|
};
|
|
|
|
protocol::Hover result{
|
|
.contents = content,
|
|
};
|
|
|
|
if(auto range = hover_range(unit, decl, converter)) {
|
|
result.range = *range;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
auto hover(CompilationUnitRef unit,
|
|
const clang::NamedDecl* decl,
|
|
const HoverOptions&,
|
|
PositionEncoding encoding) -> std::optional<protocol::Hover> {
|
|
if(!decl) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return build_hover(unit, *decl, encoding);
|
|
}
|
|
|
|
auto hover(CompilationUnitRef unit,
|
|
std::uint32_t offset,
|
|
const HoverOptions& options,
|
|
PositionEncoding encoding) -> std::optional<protocol::Hover> {
|
|
auto tree = SelectionTree::create_right(unit, LocalSourceRange(offset, offset));
|
|
auto* node = tree.common_ancestor();
|
|
if(!node) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
if(const auto* decl = node->get<clang::NamedDecl>()) {
|
|
return hover(unit, decl, options, encoding);
|
|
}
|
|
|
|
if(const auto* ref = node->get<clang::DeclRefExpr>()) {
|
|
return hover(unit, ref->getDecl(), options, encoding);
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
} // namespace clice::feature
|