Files
clice/src/feature/hover.cpp
ykiko 6d3b6acc82 feat: initial CompileGraph integration into MasterServer (#376)
## 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>
2026-03-29 20:05:58 +08:00

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