Compare commits
9 Commits
feat/docum
...
bench/pch-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f19e75e2fb | ||
|
|
ba191943b6 | ||
|
|
90f3d34768 | ||
|
|
5b24dac6c3 | ||
|
|
418e190fa0 | ||
|
|
d42d9d5b29 | ||
|
|
9c89d20e76 | ||
|
|
8bafaa8171 | ||
|
|
92dae18fd4 |
@@ -100,7 +100,7 @@ SortIncludes: true
|
||||
SortUsingDeclarations: Never
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^["<](spdlog|toml\+\+|coraing|cpptrace|flatbuffers)/'
|
||||
- Regex: '^["<](spdlog|toml\+\+|coraing|cpptrace|flatbuffers|kota)/'
|
||||
Priority: 30
|
||||
SortPriority: 31
|
||||
|
||||
|
||||
@@ -151,13 +151,13 @@ target_link_libraries(clice-core PUBLIC
|
||||
spdlog::spdlog
|
||||
roaring::roaring
|
||||
flatbuffers
|
||||
eventide::ipc::lsp
|
||||
eventide::serde::toml
|
||||
kota::ipc::lsp
|
||||
kota::codec::toml
|
||||
simdjson::simdjson
|
||||
)
|
||||
|
||||
add_executable(clice "${PROJECT_SOURCE_DIR}/src/clice.cc")
|
||||
target_link_libraries(clice PRIVATE clice::core eventide::deco)
|
||||
target_link_libraries(clice PRIVATE clice::core kota::deco)
|
||||
install(TARGETS clice RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
add_custom_target(copy_clang_resource ALL
|
||||
@@ -189,7 +189,7 @@ if(CLICE_ENABLE_TEST)
|
||||
"${PROJECT_SOURCE_DIR}/src"
|
||||
"${PROJECT_SOURCE_DIR}/tests/unit"
|
||||
)
|
||||
target_link_libraries(unit_tests PRIVATE clice::core eventide::zest eventide::deco)
|
||||
target_link_libraries(unit_tests PRIVATE clice::core kota::zest kota::deco)
|
||||
endif()
|
||||
|
||||
if(CLICE_ENABLE_BENCHMARK)
|
||||
@@ -199,7 +199,15 @@ if(CLICE_ENABLE_BENCHMARK)
|
||||
target_include_directories(scan_benchmark PRIVATE
|
||||
"${PROJECT_SOURCE_DIR}/src"
|
||||
)
|
||||
target_link_libraries(scan_benchmark PRIVATE clice::core eventide::deco)
|
||||
target_link_libraries(scan_benchmark PRIVATE clice::core kota::deco)
|
||||
|
||||
add_executable(pch_chain_benchmark
|
||||
"${PROJECT_SOURCE_DIR}/benchmarks/pch_chain/pch_chain_benchmark.cpp"
|
||||
)
|
||||
target_include_directories(pch_chain_benchmark PRIVATE
|
||||
"${PROJECT_SOURCE_DIR}/src"
|
||||
)
|
||||
target_link_libraries(pch_chain_benchmark PRIVATE clice::core)
|
||||
endif()
|
||||
|
||||
if(CLICE_RELEASE)
|
||||
|
||||
1056
benchmarks/pch_chain/pch_chain_benchmark.cpp
Normal file
1056
benchmarks/pch_chain/pch_chain_benchmark.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,17 +21,15 @@
|
||||
#include <thread>
|
||||
|
||||
#include "command/command.h"
|
||||
#include "eventide/deco/deco.h"
|
||||
#include "eventide/serde/json/serializer.h"
|
||||
#include "support/filesystem.h"
|
||||
#include "support/logging.h"
|
||||
#include "support/path_pool.h"
|
||||
#include "syntax/dependency_graph.h"
|
||||
|
||||
#include "kota/codec/json/serializer.h"
|
||||
#include "kota/deco/deco.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
|
||||
namespace et = eventide;
|
||||
|
||||
using namespace clice;
|
||||
|
||||
struct BenchmarkOptions {
|
||||
@@ -97,7 +95,7 @@ void export_graph_json(const PathPool& path_pool,
|
||||
export_data.files.push_back(std::move(node));
|
||||
}
|
||||
|
||||
auto json = et::serde::json::to_json(export_data);
|
||||
auto json = kota::codec::json::to_json(export_data);
|
||||
if(!json) {
|
||||
std::println(stderr, "Failed to serialize dependency graph");
|
||||
return;
|
||||
@@ -221,8 +219,8 @@ void print_report(const ScanReport& report) {
|
||||
}
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
auto args = deco::util::argvify(argc, argv);
|
||||
auto result = deco::cli::parse<BenchmarkOptions>(args);
|
||||
auto args = kota::deco::util::argvify(argc, argv);
|
||||
auto result = kota::deco::cli::parse<BenchmarkOptions>(args);
|
||||
|
||||
if(!result.has_value()) {
|
||||
std::println(stderr, "Error: {}", result.error().message);
|
||||
@@ -233,7 +231,7 @@ int main(int argc, const char** argv) {
|
||||
|
||||
if(opts.help.value_or(false) || !opts.cdb_path.has_value()) {
|
||||
std::ostringstream oss;
|
||||
deco::cli::write_usage_for<BenchmarkOptions>(oss, "scan_benchmark [OPTIONS] <cdb>");
|
||||
kota::deco::cli::write_usage_for<BenchmarkOptions>(oss, "scan_benchmark [OPTIONS] <cdb>");
|
||||
std::print("{}", oss.str());
|
||||
return opts.help.value_or(false) ? 0 : 1;
|
||||
}
|
||||
|
||||
@@ -39,18 +39,18 @@ set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "" FORCE)
|
||||
|
||||
FetchContent_Declare(
|
||||
eventide
|
||||
GIT_REPOSITORY https://github.com/clice-io/eventide
|
||||
kotatsu
|
||||
GIT_REPOSITORY https://github.com/clice-io/kotatsu
|
||||
GIT_TAG main
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
|
||||
set(ETD_ENABLE_ZEST ON)
|
||||
set(ETD_ENABLE_TEST OFF)
|
||||
set(ETD_SERDE_ENABLE_SIMDJSON ON)
|
||||
set(ETD_SERDE_ENABLE_YYJSON ON)
|
||||
set(ETD_SERDE_ENABLE_TOML ON)
|
||||
set(ETD_ENABLE_EXCEPTIONS OFF)
|
||||
set(ETD_ENABLE_RTTI OFF)
|
||||
set(KOTA_ENABLE_ZEST ON)
|
||||
set(KOTA_ENABLE_TEST OFF)
|
||||
set(KOTA_CODEC_ENABLE_SIMDJSON ON)
|
||||
set(KOTA_CODEC_ENABLE_YYJSON ON)
|
||||
set(KOTA_CODEC_ENABLE_TOML ON)
|
||||
set(KOTA_ENABLE_EXCEPTIONS OFF)
|
||||
set(KOTA_ENABLE_RTTI OFF)
|
||||
|
||||
FetchContent_MakeAvailable(eventide spdlog croaring flatbuffers)
|
||||
FetchContent_MakeAvailable(kotatsu spdlog croaring flatbuffers)
|
||||
|
||||
@@ -91,7 +91,7 @@ The worker pool (`src/server/worker_pool.cpp`) manages spawning and communicatin
|
||||
|
||||
### Communication
|
||||
|
||||
Workers communicate with the master via **stdio pipes** using a **bincode** serialization format (via `eventide::ipc::BincodePeer`). This is more compact and faster than JSON for internal IPC, while the master handles JSON for the external LSP protocol.
|
||||
Workers communicate with the master via **stdio pipes** using a **bincode** serialization format (via `kota::ipc::BincodePeer`). This is more compact and faster than JSON for internal IPC, while the master handles JSON for the external LSP protocol.
|
||||
|
||||
### Stateful Worker Routing
|
||||
|
||||
@@ -111,7 +111,7 @@ The stateful worker (`src/server/stateful_worker.cpp`) caches compiled ASTs in m
|
||||
- **Feature queries**: Look up the cached AST and invoke the corresponding `feature::*` function (hover, semantic tokens, etc.), serializing the result to JSON
|
||||
- **Document updates**: Received as notifications — the worker updates the stored text and marks the document as `dirty`, causing feature queries to return `null` until recompilation
|
||||
- **Eviction**: LRU-based; evicts the oldest document when capacity is exceeded, notifying the master
|
||||
- **Concurrency**: Each document has a per-document `et::mutex` (strand) to serialize compilation and feature queries. Heavy work (compilation, feature extraction) runs on a thread pool via `et::queue`.
|
||||
- **Concurrency**: Each document has a per-document `kota::mutex` (strand) to serialize compilation and feature queries. Heavy work (compilation, feature extraction) runs on a thread pool via `kota::queue`.
|
||||
|
||||
## Stateless Worker
|
||||
|
||||
@@ -123,7 +123,7 @@ The stateless worker (`src/server/stateless_worker.cpp`) handles one-shot reques
|
||||
- **Build PCM**: Compiles a C++20 module interface to a temporary file
|
||||
- **Index**: Compiles a file for indexing (TUIndex generation — currently a stub)
|
||||
|
||||
All requests are dispatched to a thread pool via `et::queue`.
|
||||
All requests are dispatched to a thread pool via `kota::queue`.
|
||||
|
||||
## Compile Graph
|
||||
|
||||
@@ -132,7 +132,7 @@ The compile graph (`src/server/compile_graph.cpp`) tracks compilation unit depen
|
||||
- **Registration**: Each file registers its included dependencies
|
||||
- **Cascade invalidation**: When a file changes, all transitive dependents are marked dirty and their ongoing compilations are cancelled
|
||||
- **Dependency compilation**: Before compiling a file, `compile_deps` ensures all dependencies (PCH, PCMs) are built first
|
||||
- **Cancellation**: Uses `et::cancellation_source` to abort in-flight compilations when files are invalidated
|
||||
- **Cancellation**: Uses `kota::cancellation_source` to abort in-flight compilations when files are invalidated
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
50
src/clice.cc
50
src/clice.cc
@@ -4,19 +4,21 @@
|
||||
#include <print>
|
||||
#include <string>
|
||||
|
||||
#include "eventide/async/async.h"
|
||||
#include "eventide/deco/deco.h"
|
||||
#include "eventide/ipc/peer.h"
|
||||
#include "eventide/ipc/recording_transport.h"
|
||||
#include "eventide/ipc/transport.h"
|
||||
#include "server/master_server.h"
|
||||
#include "server/stateful_worker.h"
|
||||
#include "server/stateless_worker.h"
|
||||
#include "support/logging.h"
|
||||
|
||||
#include "kota/async/async.h"
|
||||
#include "kota/deco/deco.h"
|
||||
#include "kota/ipc/codec/json.h"
|
||||
#include "kota/ipc/peer.h"
|
||||
#include "kota/ipc/recording_transport.h"
|
||||
#include "kota/ipc/transport.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
using deco::decl::KVStyle;
|
||||
using kota::deco::decl::KVStyle;
|
||||
|
||||
struct Options {
|
||||
DecoKV(style = KVStyle::JoinedOrSeparate,
|
||||
@@ -72,8 +74,8 @@ int main(int argc, const char** argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
auto args = deco::util::argvify(argc, argv);
|
||||
auto result = deco::cli::parse<clice::Options>(args);
|
||||
auto args = kota::deco::util::argvify(argc, argv);
|
||||
auto result = kota::deco::cli::parse<clice::Options>(args);
|
||||
|
||||
if(!result.has_value()) {
|
||||
LOG_ERROR("{}", result.error().message);
|
||||
@@ -83,7 +85,7 @@ int main(int argc, const char** argv) {
|
||||
auto& opts = result->options;
|
||||
|
||||
if(opts.help.value_or(false)) {
|
||||
deco::cli::write_usage_for<clice::Options>(std::cout, "clice [OPTIONS]");
|
||||
kota::deco::cli::write_usage_for<clice::Options>(std::cout, "clice [OPTIONS]");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -132,23 +134,22 @@ int main(int argc, const char** argv) {
|
||||
if(mode == "pipe") {
|
||||
clice::logging::stderr_logger("master", clice::logging::options);
|
||||
|
||||
namespace et = eventide;
|
||||
et::event_loop loop;
|
||||
kota::event_loop loop;
|
||||
|
||||
auto transport = et::ipc::StreamTransport::open_stdio(loop);
|
||||
auto transport = kota::ipc::StreamTransport::open_stdio(loop);
|
||||
if(!transport) {
|
||||
LOG_ERROR("failed to open stdio transport");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::unique_ptr<et::ipc::Transport> final_transport = std::move(*transport);
|
||||
std::unique_ptr<kota::ipc::Transport> final_transport = std::move(*transport);
|
||||
if(opts.record.has_value()) {
|
||||
final_transport =
|
||||
std::make_unique<et::ipc::RecordingTransport>(std::move(final_transport),
|
||||
*opts.record);
|
||||
std::make_unique<kota::ipc::RecordingTransport>(std::move(final_transport),
|
||||
*opts.record);
|
||||
}
|
||||
|
||||
et::ipc::JsonPeer peer(loop, std::move(final_transport));
|
||||
kota::ipc::JsonPeer peer(loop, std::move(final_transport));
|
||||
clice::MasterServer server(loop, peer, std::move(self_path));
|
||||
server.register_handlers();
|
||||
|
||||
@@ -160,13 +161,12 @@ int main(int argc, const char** argv) {
|
||||
if(mode == "socket") {
|
||||
clice::logging::stderr_logger("master", clice::logging::options);
|
||||
|
||||
namespace et = eventide;
|
||||
et::event_loop loop;
|
||||
kota::event_loop loop;
|
||||
|
||||
auto host = opts.host.value_or("127.0.0.1");
|
||||
auto port = opts.port.value_or(50051);
|
||||
|
||||
auto acceptor = et::tcp::listen(host, port, {}, loop);
|
||||
auto acceptor = kota::tcp::listen(host, port, {}, loop);
|
||||
if(!acceptor) {
|
||||
LOG_ERROR("failed to listen on {}:{}", host, port);
|
||||
return 1;
|
||||
@@ -174,7 +174,7 @@ int main(int argc, const char** argv) {
|
||||
|
||||
LOG_INFO("Listening on {}:{} ...", host, port);
|
||||
|
||||
auto task = [&]() -> et::task<> {
|
||||
auto task = [&]() -> kota::task<> {
|
||||
auto client = co_await acceptor->accept();
|
||||
if(!client.has_value()) {
|
||||
LOG_ERROR("failed to accept connection");
|
||||
@@ -184,13 +184,13 @@ int main(int argc, const char** argv) {
|
||||
|
||||
LOG_INFO("Client connected");
|
||||
|
||||
std::unique_ptr<et::ipc::Transport> transport =
|
||||
std::make_unique<et::ipc::StreamTransport>(std::move(client.value()));
|
||||
std::unique_ptr<kota::ipc::Transport> transport =
|
||||
std::make_unique<kota::ipc::StreamTransport>(std::move(client.value()));
|
||||
if(opts.record.has_value()) {
|
||||
transport = std::make_unique<et::ipc::RecordingTransport>(std::move(transport),
|
||||
*opts.record);
|
||||
transport = std::make_unique<kota::ipc::RecordingTransport>(std::move(transport),
|
||||
*opts.record);
|
||||
}
|
||||
et::ipc::JsonPeer peer(loop, std::move(transport));
|
||||
kota::ipc::JsonPeer peer(loop, std::move(transport));
|
||||
clice::MasterServer server(loop, peer, std::string(self_path));
|
||||
server.register_handlers();
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#include <vector>
|
||||
|
||||
#include "command/argument_parser.h"
|
||||
#include "eventide/reflection/enum.h"
|
||||
#include "support/filesystem.h"
|
||||
#include "support/logging.h"
|
||||
|
||||
#include "kota/meta/enum.h"
|
||||
#include "llvm/ADT/ScopeExit.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
@@ -363,7 +363,7 @@ std::vector<const char*> query_toolchain(const QueryParams& params) {
|
||||
case CompilerFamily::Unknown: {
|
||||
/// TODO: nvcc and intel compilers need further exploration.
|
||||
LOG_ERROR("Fail to query driver, unknown supported driver kind: {}, driver is {}",
|
||||
eventide::refl::enum_name(family),
|
||||
kota::meta::enum_name(family),
|
||||
driver);
|
||||
|
||||
std::vector<const char*> result;
|
||||
|
||||
@@ -94,7 +94,7 @@ public:
|
||||
const clang::Token& include_tok,
|
||||
llvm::StringRef,
|
||||
bool,
|
||||
clang::CharSourceRange filename_range,
|
||||
clang::CharSourceRange,
|
||||
clang::OptionalFileEntryRef,
|
||||
llvm::StringRef,
|
||||
llvm::StringRef,
|
||||
@@ -108,7 +108,6 @@ public:
|
||||
unit->directives[prev_fid].includes.emplace_back(Include{
|
||||
.fid = {},
|
||||
.location = include_tok.getLocation(),
|
||||
.filename_range = filename_range.getAsRange(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,8 @@ struct Include {
|
||||
/// The file id of included file.
|
||||
clang::FileID fid;
|
||||
|
||||
/// Location of the `include`.
|
||||
/// Location of the `include` keyword.
|
||||
clang::SourceLocation location;
|
||||
|
||||
/// The range of filename(includes `""` or `<>`).
|
||||
clang::SourceRange filename_range;
|
||||
};
|
||||
|
||||
/// Information about `__has_include` directive.
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "eventide/ipc/lsp/uri.h"
|
||||
#include "feature/feature.h"
|
||||
|
||||
#include "kota/ipc/lsp/uri.h"
|
||||
|
||||
namespace clice::feature {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace lsp = eventide::ipc::lsp;
|
||||
namespace lsp = kota::ipc::lsp;
|
||||
|
||||
auto to_uri(llvm::StringRef file) -> std::string {
|
||||
const auto file_view = std::string_view(file.data(), file.size());
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "feature/feature.h"
|
||||
#include "syntax/lexer.h"
|
||||
|
||||
namespace clice::feature {
|
||||
|
||||
namespace {} // namespace
|
||||
|
||||
auto document_links(CompilationUnitRef unit, PositionEncoding encoding)
|
||||
-> std::vector<protocol::DocumentLink> {
|
||||
std::vector<protocol::DocumentLink> links;
|
||||
@@ -22,50 +20,42 @@ auto document_links(CompilationUnitRef unit, PositionEncoding encoding)
|
||||
auto content = unit.interested_content();
|
||||
PositionMapper converter(content, encoding);
|
||||
auto& directives = directives_it->second;
|
||||
auto* lang_opts = &unit.lang_options();
|
||||
|
||||
links.reserve(directives.includes.size() + directives.has_includes.size());
|
||||
auto add_link = [&](clang::SourceLocation loc, llvm::StringRef target) {
|
||||
auto [fid, offset] = unit.decompose_location(loc);
|
||||
if(fid != interested || offset >= content.size())
|
||||
return;
|
||||
auto range = find_directive_argument(content, offset, lang_opts);
|
||||
if(!range)
|
||||
return;
|
||||
protocol::DocumentLink link{.range = to_range(converter, *range)};
|
||||
link.target = target.str();
|
||||
links.push_back(std::move(link));
|
||||
};
|
||||
|
||||
for(const auto& include: directives.includes) {
|
||||
auto [fid, range] = unit.decompose_range(include.filename_range);
|
||||
if(fid != interested || !range.valid()) {
|
||||
continue;
|
||||
if(include.fid.isValid()) {
|
||||
add_link(include.location, unit.file_path(include.fid));
|
||||
}
|
||||
|
||||
protocol::DocumentLink link{
|
||||
.range = to_range(converter, range),
|
||||
};
|
||||
link.target = std::string(unit.file_path(include.fid));
|
||||
links.push_back(std::move(link));
|
||||
}
|
||||
|
||||
for(const auto& has_include: directives.has_includes) {
|
||||
if(has_include.fid.isInvalid()) {
|
||||
continue;
|
||||
if(has_include.fid.isValid()) {
|
||||
add_link(has_include.location, unit.file_path(has_include.fid));
|
||||
}
|
||||
}
|
||||
|
||||
auto [fid, offset] = unit.decompose_location(has_include.location);
|
||||
if(fid != interested || offset >= content.size()) {
|
||||
continue;
|
||||
for(const auto& embed: directives.embeds) {
|
||||
if(embed.file) {
|
||||
add_link(embed.loc, embed.file->getName());
|
||||
}
|
||||
}
|
||||
|
||||
auto tail = content.substr(offset);
|
||||
char open = tail.front();
|
||||
if(open != '<' && open != '"') {
|
||||
continue;
|
||||
for(const auto& has_embed: directives.has_embeds) {
|
||||
if(has_embed.file) {
|
||||
add_link(has_embed.loc, has_embed.file->getName());
|
||||
}
|
||||
|
||||
char close = open == '<' ? '>' : '"';
|
||||
auto close_index = tail.find(close, 1);
|
||||
if(close_index == llvm::StringRef::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalSourceRange range(offset, offset + static_cast<std::uint32_t>(close_index + 1));
|
||||
protocol::DocumentLink link{
|
||||
.range = to_range(converter, range),
|
||||
};
|
||||
link.target = std::string(unit.file_path(has_include.fid));
|
||||
links.push_back(std::move(link));
|
||||
}
|
||||
|
||||
return links;
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
|
||||
#include "compile/compilation.h"
|
||||
#include "compile/compilation_unit.h"
|
||||
#include "eventide/ipc/lsp/position.h"
|
||||
#include "eventide/ipc/lsp/protocol.h"
|
||||
|
||||
#include "kota/ipc/lsp/position.h"
|
||||
#include "kota/ipc/lsp/protocol.h"
|
||||
|
||||
namespace clang {
|
||||
|
||||
@@ -18,11 +19,11 @@ class NamedDecl;
|
||||
|
||||
namespace clice::feature {
|
||||
|
||||
namespace protocol = eventide::ipc::protocol;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
|
||||
using eventide::ipc::lsp::PositionEncoding;
|
||||
using eventide::ipc::lsp::PositionMapper;
|
||||
using eventide::ipc::lsp::parse_position_encoding;
|
||||
using kota::ipc::lsp::PositionEncoding;
|
||||
using kota::ipc::lsp::PositionMapper;
|
||||
using kota::ipc::lsp::parse_position_encoding;
|
||||
|
||||
inline auto to_range(const PositionMapper& converter, LocalSourceRange range) -> protocol::Range {
|
||||
return protocol::Range{
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "clang/AST/Attr.h"
|
||||
#include "clang/Basic/IdentifierTable.h"
|
||||
#include "clang/Basic/Module.h"
|
||||
|
||||
namespace clice::feature {
|
||||
|
||||
@@ -168,6 +169,7 @@ public:
|
||||
auto collect() -> std::vector<RawToken> {
|
||||
highlight_lexical(unit.interested_file());
|
||||
run();
|
||||
highlight_modules();
|
||||
merge_tokens();
|
||||
return std::move(tokens);
|
||||
}
|
||||
@@ -291,6 +293,58 @@ private:
|
||||
});
|
||||
}
|
||||
|
||||
void highlight_modules() {
|
||||
auto interested = unit.interested_file();
|
||||
|
||||
auto directives_it = unit.directives().find(interested);
|
||||
if(directives_it != unit.directives().end()) {
|
||||
for(const auto& import: directives_it->second.imports) {
|
||||
add_token(import.location, SymbolKind::Keyword, 0);
|
||||
for(auto loc: import.name_locations) {
|
||||
add_token(loc, SymbolKind::Module, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto* mod = unit.context().getCurrentNamedModule();
|
||||
if(!mod) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto def_loc = mod->DefinitionLoc;
|
||||
if(!def_loc.isValid() || !def_loc.isFileID()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto [fid, offset] = unit.decompose_location(def_loc);
|
||||
if(fid != interested) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto content = unit.file_content(fid);
|
||||
auto& lang_opts = unit.lang_options();
|
||||
Lexer lexer(content.substr(offset), false, &lang_opts);
|
||||
|
||||
auto module_token = lexer.advance();
|
||||
if(module_token.is_identifier()) {
|
||||
auto range = LocalSourceRange(offset + module_token.range.begin,
|
||||
offset + module_token.range.end);
|
||||
tokens.push_back({.range = range, .kind = SymbolKind::Keyword, .modifiers = 0});
|
||||
}
|
||||
|
||||
// Scan for identifiers (module name parts) until semicolon/eof.
|
||||
while(true) {
|
||||
auto token = lexer.advance();
|
||||
if(token.is_eof() || token.kind == clang::tok::semi) {
|
||||
break;
|
||||
}
|
||||
if(token.is_identifier()) {
|
||||
auto range = LocalSourceRange(offset + token.range.begin, offset + token.range.end);
|
||||
tokens.push_back({.range = range, .kind = SymbolKind::Module, .modifiers = 0});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void highlight_lexical(clang::FileID fid) {
|
||||
auto content = unit.file_content(fid);
|
||||
auto& lang_opts = unit.lang_options();
|
||||
@@ -345,10 +399,17 @@ private:
|
||||
}
|
||||
|
||||
static void resolve_conflict(RawToken& last, const RawToken& current) {
|
||||
(void)current;
|
||||
if(last.kind == SymbolKind::Conflict) {
|
||||
return;
|
||||
}
|
||||
// Directive is a low-priority lexical kind; semantic tokens override it.
|
||||
if(last.kind == SymbolKind::Directive) {
|
||||
last = current;
|
||||
return;
|
||||
}
|
||||
if(current.kind == SymbolKind::Directive) {
|
||||
return;
|
||||
}
|
||||
last.kind = SymbolKind::Conflict;
|
||||
}
|
||||
|
||||
|
||||
@@ -131,33 +131,6 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if(auto module = unit.context().getCurrentNamedModule()) {
|
||||
// auto keyword = module->DefinitionLoc;
|
||||
// auto begin = TB.spelledTokenContaining(keyword);
|
||||
// // assert(begin->kind() == clang::tok::identifier && begin->text(SM) == "module" &&
|
||||
// // "Invalid module declaration");
|
||||
//
|
||||
// begin += 1;
|
||||
// auto end = TB.spelledTokens(unit.file_id(keyword)).end();
|
||||
//
|
||||
// for(auto iter = begin; iter != end; ++iter) {
|
||||
// if(iter->kind() == clang::tok::identifier) {
|
||||
// if(auto next = iter + 1; next != end && (next->kind() == clang::tok::period ||
|
||||
// next->kind() == clang::tok::colon)) {
|
||||
// iter += 1;
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// end = iter + 1;
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// std::unreachable();
|
||||
// }
|
||||
//
|
||||
// handleModuleOccurrence(keyword, llvm::ArrayRef<clang::syntax::Token>(begin, end));
|
||||
//}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -33,19 +33,19 @@ void CompileGraph::ensure_resolved(std::uint32_t path_id) {
|
||||
}
|
||||
}
|
||||
|
||||
et::task<bool> CompileGraph::compile_deps(std::uint32_t path_id) {
|
||||
kota::task<bool> CompileGraph::compile_deps(std::uint32_t path_id) {
|
||||
llvm::DenseSet<std::uint32_t> ancestors;
|
||||
co_return co_await compile_impl(path_id, ancestors, false);
|
||||
}
|
||||
|
||||
et::task<bool> CompileGraph::compile(std::uint32_t path_id) {
|
||||
kota::task<bool> CompileGraph::compile(std::uint32_t path_id) {
|
||||
llvm::DenseSet<std::uint32_t> ancestors;
|
||||
co_return co_await compile_impl(path_id, ancestors);
|
||||
}
|
||||
|
||||
et::task<bool> CompileGraph::compile_impl(std::uint32_t path_id,
|
||||
llvm::DenseSet<std::uint32_t> ancestors,
|
||||
bool dispatch_self) {
|
||||
kota::task<bool> CompileGraph::compile_impl(std::uint32_t path_id,
|
||||
llvm::DenseSet<std::uint32_t> ancestors,
|
||||
bool dispatch_self) {
|
||||
ensure_resolved(path_id);
|
||||
|
||||
// Cycle detection: if this unit is already in the compile chain, bail out.
|
||||
@@ -63,12 +63,12 @@ et::task<bool> CompileGraph::compile_impl(std::uint32_t path_id,
|
||||
co_return true;
|
||||
}
|
||||
|
||||
std::vector<et::task<bool>> dep_tasks;
|
||||
std::vector<kota::task<bool>> dep_tasks;
|
||||
dep_tasks.reserve(deps.size());
|
||||
for(auto dep_id: deps) {
|
||||
dep_tasks.push_back(compile_impl(dep_id, ancestors));
|
||||
}
|
||||
auto results = co_await et::when_all(std::move(dep_tasks));
|
||||
auto results = co_await kota::when_all(std::move(dep_tasks));
|
||||
for(auto ok: results) {
|
||||
if(!ok) {
|
||||
co_return false;
|
||||
@@ -96,7 +96,7 @@ et::task<bool> CompileGraph::compile_impl(std::uint32_t path_id,
|
||||
// Begin compilation. The finish lambda ensures compiling/completion state
|
||||
// is always cleaned up, regardless of how the function exits.
|
||||
it->second.compiling = true;
|
||||
it->second.completion = std::make_unique<et::event>();
|
||||
it->second.completion = std::make_unique<kota::event>();
|
||||
|
||||
auto finish = [&, path_id] {
|
||||
auto& u = units.find(path_id)->second;
|
||||
@@ -113,17 +113,17 @@ et::task<bool> CompileGraph::compile_impl(std::uint32_t path_id,
|
||||
// Deadlocks from cross-branch cycles (e.g. 1->{2,3}, 2->3, 3->2) are
|
||||
// prevented by has_wait_cycle() checking before completion.wait().
|
||||
if(!deps.empty()) {
|
||||
std::vector<et::task<bool, void, et::cancellation>> dep_tasks;
|
||||
std::vector<kota::task<bool, void, kota::cancellation>> dep_tasks;
|
||||
dep_tasks.reserve(deps.size());
|
||||
for(auto dep_id: deps) {
|
||||
dep_tasks.push_back(et::with_token(compile_impl(dep_id, ancestors), token));
|
||||
dep_tasks.push_back(kota::with_token(compile_impl(dep_id, ancestors), token));
|
||||
}
|
||||
|
||||
auto results = co_await et::when_all(std::move(dep_tasks));
|
||||
auto results = co_await kota::when_all(std::move(dep_tasks));
|
||||
|
||||
if(results.is_cancelled()) {
|
||||
finish();
|
||||
co_await et::cancel();
|
||||
co_await kota::cancel();
|
||||
}
|
||||
|
||||
for(auto ok: *results) {
|
||||
@@ -135,11 +135,11 @@ et::task<bool> CompileGraph::compile_impl(std::uint32_t path_id,
|
||||
}
|
||||
|
||||
// Dispatch the actual compilation, cancellable via the pre-captured token.
|
||||
auto result = co_await et::with_token(dispatch(path_id), token);
|
||||
auto result = co_await kota::with_token(dispatch(path_id), token);
|
||||
|
||||
if(!result.has_value()) {
|
||||
finish();
|
||||
co_await et::cancel();
|
||||
co_await kota::cancel();
|
||||
}
|
||||
|
||||
if(!*result) {
|
||||
@@ -199,7 +199,7 @@ llvm::SmallVector<std::uint32_t> CompileGraph::update(std::uint32_t path_id) {
|
||||
// Cancel in-flight compilation if running.
|
||||
if(unit.compiling) {
|
||||
unit.source->cancel();
|
||||
unit.source = std::make_unique<et::cancellation_source>();
|
||||
unit.source = std::make_unique<kota::cancellation_source>();
|
||||
}
|
||||
unit.dirty = true;
|
||||
unit.generation++;
|
||||
@@ -247,7 +247,7 @@ bool CompileGraph::has_wait_cycle(std::uint32_t target,
|
||||
void CompileGraph::cancel_all() {
|
||||
for(auto& [_, unit]: units) {
|
||||
unit.source->cancel();
|
||||
unit.source = std::make_unique<et::cancellation_source>();
|
||||
unit.source = std::make_unique<kota::cancellation_source>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,16 +4,13 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "eventide/async/async.h"
|
||||
|
||||
#include "kota/async/async.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace et = eventide;
|
||||
|
||||
struct CompileUnit {
|
||||
std::uint32_t path_id = 0;
|
||||
|
||||
@@ -33,14 +30,15 @@ struct CompileUnit {
|
||||
/// stale completions without ABA risk from raw-pointer comparison.
|
||||
std::uint64_t generation = 0;
|
||||
|
||||
std::unique_ptr<et::cancellation_source> source = std::make_unique<et::cancellation_source>();
|
||||
std::unique_ptr<et::event> completion;
|
||||
std::unique_ptr<kota::cancellation_source> source =
|
||||
std::make_unique<kota::cancellation_source>();
|
||||
std::unique_ptr<kota::event> completion;
|
||||
};
|
||||
|
||||
class CompileGraph {
|
||||
public:
|
||||
/// Performs the actual compilation (e.g. produce PCM file).
|
||||
using dispatch_fn = std::function<et::task<bool>(std::uint32_t path_id)>;
|
||||
using dispatch_fn = std::function<kota::task<bool>(std::uint32_t path_id)>;
|
||||
|
||||
/// Returns the dependency path_ids for a given path_id (called lazily on first compile).
|
||||
using resolve_fn = std::function<llvm::SmallVector<std::uint32_t>(std::uint32_t path_id)>;
|
||||
@@ -48,11 +46,11 @@ public:
|
||||
CompileGraph(dispatch_fn dispatch, resolve_fn resolve);
|
||||
|
||||
/// Compile a unit and all its transitive dependencies.
|
||||
et::task<bool> compile(std::uint32_t path_id);
|
||||
kota::task<bool> compile(std::uint32_t path_id);
|
||||
|
||||
/// Compile all transitive module dependencies of path_id, but NOT path_id itself.
|
||||
/// Used for non-module files (plain .cpp) that import modules.
|
||||
et::task<bool> compile_deps(std::uint32_t path_id);
|
||||
kota::task<bool> compile_deps(std::uint32_t path_id);
|
||||
|
||||
/// Mark path_id and all transitive dependents as dirty,
|
||||
/// cancelling any in-progress compilations.
|
||||
@@ -70,9 +68,9 @@ private:
|
||||
void ensure_resolved(std::uint32_t path_id);
|
||||
|
||||
/// Internal compile with ancestor tracking for cycle detection.
|
||||
et::task<bool> compile_impl(std::uint32_t path_id,
|
||||
llvm::DenseSet<std::uint32_t> ancestors,
|
||||
bool dispatch_self = true);
|
||||
kota::task<bool> compile_impl(std::uint32_t path_id,
|
||||
llvm::DenseSet<std::uint32_t> ancestors,
|
||||
bool dispatch_self = true);
|
||||
|
||||
/// Check if waiting on `target` would deadlock given our `ancestors` chain.
|
||||
/// Walks the dependency graph through compiling units to see if any dep
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
#include <string>
|
||||
|
||||
#include "command/search_config.h"
|
||||
#include "eventide/ipc/lsp/position.h"
|
||||
#include "eventide/ipc/lsp/uri.h"
|
||||
#include "eventide/serde/json/json.h"
|
||||
#include "index/tu_index.h"
|
||||
#include "server/protocol.h"
|
||||
#include "support/filesystem.h"
|
||||
@@ -15,6 +12,9 @@
|
||||
#include "syntax/include_resolver.h"
|
||||
#include "syntax/scan.h"
|
||||
|
||||
#include "kota/codec/json/json.h"
|
||||
#include "kota/ipc/lsp/position.h"
|
||||
#include "kota/ipc/lsp/uri.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
@@ -22,13 +22,13 @@
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace lsp = eventide::ipc::lsp;
|
||||
using serde_raw = et::serde::RawValue;
|
||||
namespace lsp = kota::ipc::lsp;
|
||||
using serde_raw = kota::codec::RawValue;
|
||||
|
||||
/// Detect whether the cursor is inside a preamble directive (include/import).
|
||||
|
||||
Compiler::Compiler(et::event_loop& loop,
|
||||
et::ipc::JsonPeer& peer,
|
||||
Compiler::Compiler(kota::event_loop& loop,
|
||||
kota::ipc::JsonPeer& peer,
|
||||
Workspace& workspace,
|
||||
WorkerPool& pool,
|
||||
llvm::DenseMap<std::uint32_t, Session>& sessions) :
|
||||
@@ -75,7 +75,7 @@ void Compiler::init_compile_graph() {
|
||||
};
|
||||
|
||||
// Dispatch: sends BuildPCM request to a stateless worker.
|
||||
auto dispatch = [this](std::uint32_t path_id) -> et::task<bool> {
|
||||
auto dispatch = [this](std::uint32_t path_id) -> kota::task<bool> {
|
||||
auto mod_it = workspace.path_to_module.find(path_id);
|
||||
if(mod_it == workspace.path_to_module.end())
|
||||
co_return false;
|
||||
@@ -393,10 +393,10 @@ std::string uri_to_path(const std::string& uri) {
|
||||
|
||||
void Compiler::publish_diagnostics(const std::string& uri,
|
||||
int version,
|
||||
const et::serde::RawValue& diagnostics_json) {
|
||||
const kota::codec::RawValue& diagnostics_json) {
|
||||
std::vector<protocol::Diagnostic> diagnostics;
|
||||
if(!diagnostics_json.empty()) {
|
||||
auto status = et::serde::json::from_json(diagnostics_json.data, diagnostics);
|
||||
auto status = kota::codec::json::from_json(diagnostics_json.data, diagnostics);
|
||||
if(!status) {
|
||||
LOG_WARN("Failed to deserialize diagnostics JSON for {}", uri);
|
||||
}
|
||||
@@ -415,9 +415,9 @@ void Compiler::clear_diagnostics(const std::string& uri) {
|
||||
peer.send_notification(params);
|
||||
}
|
||||
|
||||
et::task<bool> Compiler::ensure_pch(Session& session,
|
||||
const std::string& directory,
|
||||
const std::vector<std::string>& arguments) {
|
||||
kota::task<bool> Compiler::ensure_pch(Session& session,
|
||||
const std::string& directory,
|
||||
const std::vector<std::string>& arguments) {
|
||||
auto path_id = session.path_id;
|
||||
auto path = workspace.path_pool.resolve(path_id);
|
||||
auto& text = session.text;
|
||||
@@ -471,7 +471,7 @@ et::task<bool> Compiler::ensure_pch(Session& session,
|
||||
}
|
||||
|
||||
// Register in-flight build so concurrent requests wait on us.
|
||||
auto completion = std::make_shared<et::event>();
|
||||
auto completion = std::make_shared<kota::event>();
|
||||
workspace.pch_cache[path_id].building = completion;
|
||||
|
||||
// Build a new PCH via stateless worker.
|
||||
@@ -502,6 +502,7 @@ et::task<bool> Compiler::ensure_pch(Session& session,
|
||||
st.bound = bound;
|
||||
st.hash = preamble_hash;
|
||||
st.deps = capture_deps_snapshot(workspace.path_pool, result.value().deps);
|
||||
st.document_links_json = std::move(result.value().pch_links_json);
|
||||
st.building.reset();
|
||||
|
||||
session.pch_ref = Session::PCHRef{path_id, preamble_hash, bound};
|
||||
@@ -518,11 +519,11 @@ et::task<bool> Compiler::ensure_pch(Session& session,
|
||||
/// Compile module dependencies, build/reuse PCH, and fill PCM paths.
|
||||
/// Shared preparation step used by both ensure_compiled() (stateful path)
|
||||
/// and forward_stateless() (completion/signatureHelp path).
|
||||
et::task<bool> Compiler::ensure_deps(Session& session,
|
||||
const std::string& directory,
|
||||
const std::vector<std::string>& arguments,
|
||||
std::pair<std::string, uint32_t>& pch,
|
||||
std::unordered_map<std::string, std::string>& pcms) {
|
||||
kota::task<bool> Compiler::ensure_deps(Session& session,
|
||||
const std::string& directory,
|
||||
const std::vector<std::string>& arguments,
|
||||
std::pair<std::string, uint32_t>& pch,
|
||||
std::unordered_map<std::string, std::string>& pcms) {
|
||||
auto path_id = session.path_id;
|
||||
|
||||
// Compile C++20 module dependencies (PCMs).
|
||||
@@ -619,7 +620,7 @@ void Compiler::record_deps(Session& session, llvm::ArrayRef<std::string> deps) {
|
||||
/// task via loop.schedule(); subsequent ones wait on the shared event.
|
||||
/// The detached task cannot be cancelled by LSP $/cancelRequest, preventing
|
||||
/// the race where cancellation wakes all waiters and they all start compiles.
|
||||
et::task<bool> Compiler::ensure_compiled(Session& session) {
|
||||
kota::task<bool> Compiler::ensure_compiled(Session& session) {
|
||||
auto path_id = session.path_id;
|
||||
|
||||
LOG_DEBUG("ensure_compiled: path_id={} version={} gen={} ast_dirty={}",
|
||||
@@ -662,7 +663,7 @@ et::task<bool> Compiler::ensure_compiled(Session& session) {
|
||||
// from the sessions map after co_await (DenseMap may invalidate pointers).
|
||||
loop.schedule([](Compiler* self,
|
||||
std::uint32_t pid,
|
||||
std::shared_ptr<Session::PendingCompile> pc) -> et::task<> {
|
||||
std::shared_ptr<Session::PendingCompile> pc) -> kota::task<> {
|
||||
// Re-lookup session from the sessions map (pointer may have been
|
||||
// invalidated by DenseMap growth during co_await).
|
||||
auto find_session = [&]() -> Session* {
|
||||
@@ -893,7 +894,7 @@ Compiler::RawResult Compiler::handle_completion(const protocol::Position& positi
|
||||
item.kind = protocol::CompletionItemKind::File;
|
||||
items.push_back(std::move(item));
|
||||
}
|
||||
auto json = et::serde::json::to_json<et::ipc::lsp_config>(items);
|
||||
auto json = kota::codec::json::to_json<kota::ipc::lsp_config>(items);
|
||||
co_return serde_raw{json ? std::move(*json) : "[]"};
|
||||
}
|
||||
if(pctx.kind == CompletionContext::Import) {
|
||||
@@ -908,7 +909,7 @@ Compiler::RawResult Compiler::handle_completion(const protocol::Position& positi
|
||||
item.insert_text = name + ";";
|
||||
items.push_back(std::move(item));
|
||||
}
|
||||
auto json = et::serde::json::to_json<et::ipc::lsp_config>(items);
|
||||
auto json = kota::codec::json::to_json<kota::ipc::lsp_config>(items);
|
||||
co_return serde_raw{json ? std::move(*json) : "[]"};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
#include <vector>
|
||||
|
||||
#include "command/command.h"
|
||||
#include "eventide/async/async.h"
|
||||
#include "eventide/ipc/lsp/protocol.h"
|
||||
#include "eventide/ipc/peer.h"
|
||||
#include "eventide/serde/serde/raw_value.h"
|
||||
#include "server/session.h"
|
||||
#include "server/worker_pool.h"
|
||||
#include "server/workspace.h"
|
||||
#include "syntax/completion.h"
|
||||
|
||||
#include "kota/async/async.h"
|
||||
#include "kota/codec/raw_value.h"
|
||||
#include "kota/ipc/codec/json.h"
|
||||
#include "kota/ipc/lsp/protocol.h"
|
||||
#include "kota/ipc/peer.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
@@ -24,8 +25,7 @@
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace et = eventide;
|
||||
namespace protocol = et::ipc::protocol;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
|
||||
/// Convert a file:// URI to a local file path.
|
||||
std::string uri_to_path(const std::string& uri);
|
||||
@@ -49,8 +49,8 @@ std::string uri_to_path(const std::string& uri);
|
||||
/// - Background indexing scheduling — handled by Indexer
|
||||
class Compiler {
|
||||
public:
|
||||
Compiler(et::event_loop& loop,
|
||||
et::ipc::JsonPeer& peer,
|
||||
Compiler(kota::event_loop& loop,
|
||||
kota::ipc::JsonPeer& peer,
|
||||
Workspace& workspace,
|
||||
WorkerPool& pool,
|
||||
llvm::DenseMap<std::uint32_t, Session>& sessions);
|
||||
@@ -67,9 +67,9 @@ public:
|
||||
|
||||
/// Compile an open file's AST if dirty. On success, updates session's
|
||||
/// file_index, pch_ref, ast_deps, and publishes diagnostics.
|
||||
et::task<bool> ensure_compiled(Session& session);
|
||||
kota::task<bool> ensure_compiled(Session& session);
|
||||
|
||||
using RawResult = et::task<et::serde::RawValue, et::ipc::Error>;
|
||||
using RawResult = kota::task<kota::codec::RawValue, kota::ipc::Error>;
|
||||
|
||||
/// Forward a query to the stateful worker that holds this file's AST.
|
||||
/// Ensures compilation first. For position-sensitive queries (hover,
|
||||
@@ -97,20 +97,22 @@ public:
|
||||
std::function<void()> on_indexing_needed;
|
||||
|
||||
private:
|
||||
et::task<bool> ensure_deps(Session& session,
|
||||
const std::string& directory,
|
||||
const std::vector<std::string>& arguments,
|
||||
std::pair<std::string, uint32_t>& pch,
|
||||
std::unordered_map<std::string, std::string>& pcms);
|
||||
kota::task<bool> ensure_deps(Session& session,
|
||||
const std::string& directory,
|
||||
const std::vector<std::string>& arguments,
|
||||
std::pair<std::string, uint32_t>& pch,
|
||||
std::unordered_map<std::string, std::string>& pcms);
|
||||
|
||||
et::task<bool> ensure_pch(Session& session,
|
||||
const std::string& directory,
|
||||
const std::vector<std::string>& arguments);
|
||||
kota::task<bool> ensure_pch(Session& session,
|
||||
const std::string& directory,
|
||||
const std::vector<std::string>& arguments);
|
||||
|
||||
bool is_stale(const Session& session);
|
||||
void record_deps(Session& session, llvm::ArrayRef<std::string> deps);
|
||||
|
||||
void publish_diagnostics(const std::string& uri, int version, const et::serde::RawValue& diags);
|
||||
void publish_diagnostics(const std::string& uri,
|
||||
int version,
|
||||
const kota::codec::RawValue& diags);
|
||||
|
||||
std::optional<HeaderFileContext> resolve_header_context(std::uint32_t header_path_id,
|
||||
Session* session);
|
||||
@@ -122,8 +124,8 @@ private:
|
||||
Session* session);
|
||||
|
||||
private:
|
||||
et::event_loop& loop;
|
||||
et::ipc::JsonPeer& peer;
|
||||
kota::event_loop& loop;
|
||||
kota::ipc::JsonPeer& peer;
|
||||
Workspace& workspace;
|
||||
WorkerPool& pool;
|
||||
llvm::DenseMap<std::uint32_t, Session>& sessions;
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
#include "eventide/serde/toml.h"
|
||||
#include "support/filesystem.h"
|
||||
#include "support/logging.h"
|
||||
|
||||
#include "kota/codec/toml.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
/// Replace all occurrences of ${workspace} with the workspace root.
|
||||
@@ -59,7 +60,7 @@ std::optional<CliceConfig> CliceConfig::load(const std::string& path,
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto result = eventide::serde::toml::parse<CliceConfig>(*content);
|
||||
auto result = kota::codec::toml::parse<CliceConfig>(*content);
|
||||
if(!result) {
|
||||
LOG_WARN("Failed to parse config file {}", path);
|
||||
return std::nullopt;
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "eventide/ipc/lsp/position.h"
|
||||
#include "eventide/ipc/lsp/protocol.h"
|
||||
#include "eventide/ipc/lsp/uri.h"
|
||||
#include "index/tu_index.h"
|
||||
#include "server/compiler.h"
|
||||
#include "server/protocol.h"
|
||||
@@ -15,6 +12,9 @@
|
||||
#include "support/filesystem.h"
|
||||
#include "support/logging.h"
|
||||
|
||||
#include "kota/ipc/lsp/position.h"
|
||||
#include "kota/ipc/lsp/protocol.h"
|
||||
#include "kota/ipc/lsp/uri.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace lsp = eventide::ipc::lsp;
|
||||
namespace lsp = kota::ipc::lsp;
|
||||
|
||||
void Indexer::merge(const void* tu_index_data, std::size_t size) {
|
||||
auto tu_index = index::TUIndex::from(tu_index_data);
|
||||
@@ -630,13 +630,13 @@ void Indexer::schedule() {
|
||||
indexing_scheduled = true;
|
||||
|
||||
if(!index_idle_timer) {
|
||||
index_idle_timer = std::make_shared<et::timer>(et::timer::create(loop));
|
||||
index_idle_timer = std::make_shared<kota::timer>(kota::timer::create(loop));
|
||||
}
|
||||
index_idle_timer->start(std::chrono::milliseconds(workspace.config.idle_timeout_ms));
|
||||
loop.schedule(run_background_indexing());
|
||||
}
|
||||
|
||||
et::task<> Indexer::run_background_indexing() {
|
||||
kota::task<> Indexer::run_background_indexing() {
|
||||
if(index_idle_timer) {
|
||||
co_await index_idle_timer->wait();
|
||||
}
|
||||
|
||||
@@ -7,22 +7,21 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "eventide/async/async.h"
|
||||
#include "eventide/ipc/lsp/position.h"
|
||||
#include "eventide/ipc/lsp/protocol.h"
|
||||
#include "semantic/relation_kind.h"
|
||||
#include "semantic/symbol_kind.h"
|
||||
#include "server/workspace.h"
|
||||
|
||||
#include "kota/async/async.h"
|
||||
#include "kota/ipc/lsp/position.h"
|
||||
#include "kota/ipc/lsp/protocol.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace et = eventide;
|
||||
namespace protocol = et::ipc::protocol;
|
||||
namespace lsp = et::ipc::lsp;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
namespace lsp = kota::ipc::lsp;
|
||||
|
||||
struct Session;
|
||||
class Compiler;
|
||||
@@ -54,7 +53,7 @@ struct SymbolInfo {
|
||||
/// - Document lifecycle — handled by MasterServer
|
||||
class Indexer {
|
||||
public:
|
||||
Indexer(et::event_loop& loop,
|
||||
Indexer(kota::event_loop& loop,
|
||||
Workspace& workspace,
|
||||
llvm::DenseMap<std::uint32_t, Session>& sessions,
|
||||
WorkerPool& pool,
|
||||
@@ -165,7 +164,7 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
et::event_loop& loop;
|
||||
kota::event_loop& loop;
|
||||
Workspace& workspace;
|
||||
llvm::DenseMap<std::uint32_t, Session>& sessions;
|
||||
WorkerPool& pool;
|
||||
@@ -181,9 +180,9 @@ private:
|
||||
std::size_t index_queue_pos = 0;
|
||||
bool indexing_active = false;
|
||||
bool indexing_scheduled = false;
|
||||
std::shared_ptr<et::timer> index_idle_timer;
|
||||
std::shared_ptr<kota::timer> index_idle_timer;
|
||||
|
||||
et::task<> run_background_indexing();
|
||||
kota::task<> run_background_indexing();
|
||||
};
|
||||
|
||||
} // namespace clice
|
||||
|
||||
@@ -6,37 +6,39 @@
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
|
||||
#include "eventide/ipc/lsp/position.h"
|
||||
#include "eventide/ipc/lsp/protocol.h"
|
||||
#include "eventide/ipc/lsp/uri.h"
|
||||
#include "eventide/reflection/enum.h"
|
||||
#include "eventide/serde/json/json.h"
|
||||
#include "semantic/symbol_kind.h"
|
||||
#include "server/protocol.h"
|
||||
#include "support/filesystem.h"
|
||||
#include "support/logging.h"
|
||||
|
||||
#include "kota/codec/json/json.h"
|
||||
#include "kota/ipc/lsp/position.h"
|
||||
#include "kota/ipc/lsp/protocol.h"
|
||||
#include "kota/ipc/lsp/uri.h"
|
||||
#include "kota/meta/enum.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/Process.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace protocol = eventide::ipc::protocol;
|
||||
namespace lsp = eventide::ipc::lsp;
|
||||
namespace refl = eventide::refl;
|
||||
using et::ipc::RequestResult;
|
||||
using RequestContext = et::ipc::JsonPeer::RequestContext;
|
||||
using serde_raw = et::serde::RawValue;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
namespace lsp = kota::ipc::lsp;
|
||||
namespace refl = kota::meta;
|
||||
using kota::ipc::RequestResult;
|
||||
using RequestContext = kota::ipc::JsonPeer::RequestContext;
|
||||
using serde_raw = kota::codec::RawValue;
|
||||
|
||||
/// Serialize a value to a JSON RawValue using LSP config.
|
||||
template <typename T>
|
||||
static serde_raw to_raw(const T& value) {
|
||||
auto json = et::serde::json::to_json<et::ipc::lsp_config>(value);
|
||||
auto json = kota::codec::json::to_json<kota::ipc::lsp_config>(value);
|
||||
return serde_raw{json ? std::move(*json) : "null"};
|
||||
}
|
||||
|
||||
MasterServer::MasterServer(et::event_loop& loop, et::ipc::JsonPeer& peer, std::string self_path) :
|
||||
MasterServer::MasterServer(kota::event_loop& loop,
|
||||
kota::ipc::JsonPeer& peer,
|
||||
std::string self_path) :
|
||||
loop(loop), peer(peer), pool(loop), compiler(loop, peer, workspace, pool, sessions),
|
||||
indexer(loop,
|
||||
workspace,
|
||||
@@ -54,7 +56,7 @@ MasterServer::MasterServer(et::event_loop& loop, et::ipc::JsonPeer& peer, std::s
|
||||
|
||||
MasterServer::~MasterServer() = default;
|
||||
|
||||
et::task<> MasterServer::load_workspace() {
|
||||
kota::task<> MasterServer::load_workspace() {
|
||||
if(workspace_root.empty())
|
||||
co_return;
|
||||
|
||||
@@ -154,7 +156,7 @@ void MasterServer::register_handlers() {
|
||||
peer.on_request([this](RequestContext& ctx, const protocol::InitializeParams& params)
|
||||
-> RequestResult<protocol::InitializeParams> {
|
||||
if(lifecycle != ServerLifecycle::Uninitialized) {
|
||||
co_return et::outcome_error(protocol::Error{"Server already initialized"});
|
||||
co_return kota::outcome_error(protocol::Error{"Server already initialized"});
|
||||
}
|
||||
|
||||
auto& init = params.lsp__initialize_params;
|
||||
@@ -293,7 +295,7 @@ void MasterServer::register_handlers() {
|
||||
indexer.save(workspace.config.index_dir);
|
||||
workspace.save_cache();
|
||||
|
||||
loop.schedule([this]() -> et::task<> {
|
||||
loop.schedule([this]() -> kota::task<> {
|
||||
co_await pool.stop();
|
||||
loop.stop();
|
||||
}());
|
||||
@@ -478,15 +480,38 @@ void MasterServer::register_handlers() {
|
||||
co_return co_await compiler.forward_query(worker::QueryKind::DocumentSymbol, sit->second);
|
||||
});
|
||||
|
||||
peer.on_request(
|
||||
[this](RequestContext& ctx, const protocol::DocumentLinkParams& params) -> RawResult {
|
||||
auto path = uri_to_path(params.text_document.uri);
|
||||
auto path_id = workspace.path_pool.intern(path);
|
||||
auto sit = sessions.find(path_id);
|
||||
if(sit == sessions.end())
|
||||
co_return serde_raw{"null"};
|
||||
co_return co_await compiler.forward_query(worker::QueryKind::DocumentLink, sit->second);
|
||||
});
|
||||
peer.on_request([this](RequestContext& ctx,
|
||||
const protocol::DocumentLinkParams& params) -> RawResult {
|
||||
auto path = uri_to_path(params.text_document.uri);
|
||||
auto path_id = workspace.path_pool.intern(path);
|
||||
auto sit = sessions.find(path_id);
|
||||
if(sit == sessions.end())
|
||||
co_return serde_raw{"null"};
|
||||
auto& session = sit->second;
|
||||
auto result = co_await compiler.forward_query(worker::QueryKind::DocumentLink, session);
|
||||
if(!result.has_value())
|
||||
co_return serde_raw{"null"};
|
||||
// Merge document links from PCH if available.
|
||||
auto& links = result.value();
|
||||
// Re-lookup session after co_await since iterators may be invalidated.
|
||||
auto sit2 = sessions.find(path_id);
|
||||
if(sit2 != sessions.end() && sit2->second.pch_ref) {
|
||||
auto pch_it = workspace.pch_cache.find(sit2->second.pch_ref->path_id);
|
||||
if(pch_it != workspace.pch_cache.end() && !pch_it->second.document_links_json.empty()) {
|
||||
auto& pch_json = pch_it->second.document_links_json;
|
||||
// Merge two JSON arrays.
|
||||
if(!links.data.empty() && links.data != "null" && links.data.size() > 2) {
|
||||
// "[a,b]" + "[c,d]" -> "[a,b,c,d]"
|
||||
links.data.pop_back(); // remove trailing ']'
|
||||
links.data += ',';
|
||||
links.data.append(pch_json.begin() + 1, pch_json.end()); // skip '['
|
||||
} else {
|
||||
links.data = pch_json;
|
||||
}
|
||||
}
|
||||
}
|
||||
co_return std::move(links);
|
||||
});
|
||||
|
||||
peer.on_request(
|
||||
[this](RequestContext& ctx, const protocol::CodeActionParams& params) -> RawResult {
|
||||
|
||||
@@ -5,21 +5,19 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "eventide/async/async.h"
|
||||
#include "eventide/ipc/peer.h"
|
||||
#include "eventide/serde/serde/raw_value.h"
|
||||
#include "server/compiler.h"
|
||||
#include "server/indexer.h"
|
||||
#include "server/session.h"
|
||||
#include "server/worker_pool.h"
|
||||
#include "server/workspace.h"
|
||||
|
||||
#include "kota/async/async.h"
|
||||
#include "kota/codec/raw_value.h"
|
||||
#include "kota/ipc/peer.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace et = eventide;
|
||||
|
||||
enum class ServerLifecycle : std::uint8_t {
|
||||
Uninitialized,
|
||||
Initialized,
|
||||
@@ -44,14 +42,14 @@ enum class ServerLifecycle : std::uint8_t {
|
||||
/// point to disk files. The only path from Session to Workspace is didSave.
|
||||
class MasterServer {
|
||||
public:
|
||||
MasterServer(et::event_loop& loop, et::ipc::JsonPeer& peer, std::string self_path);
|
||||
MasterServer(kota::event_loop& loop, kota::ipc::JsonPeer& peer, std::string self_path);
|
||||
~MasterServer();
|
||||
|
||||
void register_handlers();
|
||||
|
||||
private:
|
||||
et::event_loop& loop;
|
||||
et::ipc::JsonPeer& peer;
|
||||
kota::event_loop& loop;
|
||||
kota::ipc::JsonPeer& peer;
|
||||
|
||||
/// Persistent project-wide state (config, CDB, path pool, dependency
|
||||
/// graphs, compilation caches, symbol index).
|
||||
@@ -74,9 +72,9 @@ private:
|
||||
std::string workspace_root;
|
||||
std::string session_log_dir;
|
||||
|
||||
et::task<> load_workspace();
|
||||
kota::task<> load_workspace();
|
||||
|
||||
using RawResult = et::task<et::serde::RawValue, et::ipc::Error>;
|
||||
using RawResult = kota::task<kota::codec::RawValue, kota::ipc::Error>;
|
||||
};
|
||||
|
||||
} // namespace clice
|
||||
|
||||
@@ -7,14 +7,15 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "eventide/ipc/lsp/protocol.h"
|
||||
#include "eventide/ipc/protocol.h"
|
||||
#include "eventide/serde/serde/raw_value.h"
|
||||
#include "syntax/token.h"
|
||||
|
||||
#include "kota/codec/raw_value.h"
|
||||
#include "kota/ipc/lsp/protocol.h"
|
||||
#include "kota/ipc/protocol.h"
|
||||
|
||||
namespace clice::worker {
|
||||
|
||||
namespace protocol = eventide::ipc::protocol;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
|
||||
/// Kind of AST query dispatched to a stateful worker.
|
||||
enum class QueryKind : uint8_t {
|
||||
@@ -51,7 +52,7 @@ struct CompileParams {
|
||||
struct CompileResult {
|
||||
int version;
|
||||
/// Diagnostics serialized as JSON (RawValue) to avoid bincode/serde annotation conflicts.
|
||||
eventide::serde::RawValue diagnostics;
|
||||
kota::codec::RawValue diagnostics;
|
||||
std::size_t memory_usage;
|
||||
std::vector<std::string> deps;
|
||||
/// Serialized TUIndex for the main file (interested_only=true).
|
||||
@@ -102,7 +103,8 @@ struct BuildResult {
|
||||
std::string output_path; ///< PCH or PCM path
|
||||
std::vector<std::string> deps;
|
||||
std::string tu_index_data;
|
||||
eventide::serde::RawValue result_json; ///< Completion/SignatureHelp result
|
||||
std::string pch_links_json; ///< Pre-serialized DocumentLink[] from PCH
|
||||
kota::codec::RawValue result_json; ///< Completion/SignatureHelp result
|
||||
};
|
||||
|
||||
struct DocumentUpdateParams {
|
||||
@@ -157,7 +159,7 @@ struct SwitchContextResult {
|
||||
|
||||
} // namespace clice::ext
|
||||
|
||||
namespace eventide::ipc::protocol {
|
||||
namespace kota::ipc::protocol {
|
||||
|
||||
template <>
|
||||
struct RequestTraits<clice::worker::CompileParams> {
|
||||
@@ -167,7 +169,7 @@ struct RequestTraits<clice::worker::CompileParams> {
|
||||
|
||||
template <>
|
||||
struct RequestTraits<clice::worker::QueryParams> {
|
||||
using Result = eventide::serde::RawValue;
|
||||
using Result = kota::codec::RawValue;
|
||||
constexpr inline static std::string_view method = "clice/worker/query";
|
||||
};
|
||||
|
||||
@@ -192,4 +194,4 @@ struct NotificationTraits<clice::worker::EvictedParams> {
|
||||
constexpr inline static std::string_view method = "clice/worker/evicted";
|
||||
};
|
||||
|
||||
} // namespace eventide::ipc::protocol
|
||||
} // namespace kota::ipc::protocol
|
||||
|
||||
@@ -5,15 +5,13 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "eventide/async/async.h"
|
||||
#include "server/workspace.h"
|
||||
|
||||
#include "kota/async/async.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace et = eventide;
|
||||
|
||||
/// An editing session for a single file opened in the editor.
|
||||
///
|
||||
/// Design principle: open files are never depended upon by other files.
|
||||
@@ -45,7 +43,7 @@ struct Session {
|
||||
/// Other queries wait on the event; the compilation task itself
|
||||
/// runs independently and cannot be cancelled by LSP $/cancelRequest.
|
||||
struct PendingCompile {
|
||||
et::event done;
|
||||
kota::event done;
|
||||
bool succeeded = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,23 +8,23 @@
|
||||
#include <vector>
|
||||
|
||||
#include "compile/compilation.h"
|
||||
#include "eventide/async/async.h"
|
||||
#include "eventide/ipc/peer.h"
|
||||
#include "eventide/ipc/transport.h"
|
||||
#include "feature/feature.h"
|
||||
#include "index/tu_index.h"
|
||||
#include "server/protocol.h"
|
||||
#include "server/worker_common.h"
|
||||
#include "support/logging.h"
|
||||
|
||||
#include "kota/async/async.h"
|
||||
#include "kota/ipc/codec/bincode.h"
|
||||
#include "kota/ipc/peer.h"
|
||||
#include "kota/ipc/transport.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace et = eventide;
|
||||
using et::ipc::RequestResult;
|
||||
using RequestContext = et::ipc::BincodePeer::RequestContext;
|
||||
using kota::ipc::RequestResult;
|
||||
using RequestContext = kota::ipc::BincodePeer::RequestContext;
|
||||
|
||||
struct DocumentEntry {
|
||||
int version = 0;
|
||||
@@ -35,7 +35,7 @@ struct DocumentEntry {
|
||||
|
||||
// Signaled when the first compilation completes (has_ast becomes true).
|
||||
// Feature handlers co_await this before accessing the AST.
|
||||
et::event ast_ready{false};
|
||||
kota::event ast_ready{false};
|
||||
|
||||
// Compilation context (from CompileParams)
|
||||
std::string directory;
|
||||
@@ -44,11 +44,11 @@ struct DocumentEntry {
|
||||
llvm::StringMap<std::string> pcms;
|
||||
|
||||
// Per-document serialization mutex
|
||||
et::mutex strand;
|
||||
kota::mutex strand;
|
||||
};
|
||||
|
||||
class StatefulWorker {
|
||||
et::ipc::BincodePeer& peer;
|
||||
kota::ipc::BincodePeer& peer;
|
||||
std::uint64_t memory_limit;
|
||||
|
||||
llvm::StringMap<std::shared_ptr<DocumentEntry>> documents;
|
||||
@@ -91,10 +91,10 @@ class StatefulWorker {
|
||||
/// Look up document, wait for AST, lock strand, run fn(doc) on thread pool, unlock.
|
||||
/// Returns "null" if document not found or AST not usable.
|
||||
template <typename F>
|
||||
et::task<et::serde::RawValue> with_ast(llvm::StringRef path, F&& fn) {
|
||||
kota::task<kota::codec::RawValue> with_ast(llvm::StringRef path, F&& fn) {
|
||||
auto it = documents.find(path);
|
||||
if(it == documents.end()) {
|
||||
co_return et::serde::RawValue{"null"};
|
||||
co_return kota::codec::RawValue{"null"};
|
||||
}
|
||||
|
||||
// Hold shared_ptr so Evict can't destroy the entry mid-request.
|
||||
@@ -104,9 +104,9 @@ class StatefulWorker {
|
||||
co_await doc->ast_ready.wait();
|
||||
co_await doc->strand.lock();
|
||||
|
||||
auto result = co_await et::queue([&]() -> et::serde::RawValue {
|
||||
auto result = co_await kota::queue([&]() -> kota::codec::RawValue {
|
||||
if(!doc->has_ast || (!doc->unit.completed() && !doc->unit.fatal_error()))
|
||||
return et::serde::RawValue{"null"};
|
||||
return kota::codec::RawValue{"null"};
|
||||
return fn(*doc);
|
||||
});
|
||||
|
||||
@@ -115,7 +115,7 @@ class StatefulWorker {
|
||||
}
|
||||
|
||||
public:
|
||||
StatefulWorker(et::ipc::BincodePeer& peer, std::uint64_t memory_limit) :
|
||||
StatefulWorker(kota::ipc::BincodePeer& peer, std::uint64_t memory_limit) :
|
||||
peer(peer), memory_limit(memory_limit) {}
|
||||
|
||||
void register_handlers();
|
||||
@@ -147,7 +147,7 @@ void StatefulWorker::register_handlers() {
|
||||
doc->pcms.try_emplace(name, pcm_path);
|
||||
}
|
||||
|
||||
auto compile_result = co_await et::queue([&]() -> worker::CompileResult {
|
||||
auto compile_result = co_await kota::queue([&]() -> worker::CompileResult {
|
||||
ScopedTimer timer;
|
||||
|
||||
CompilationParams cp;
|
||||
@@ -169,15 +169,15 @@ void StatefulWorker::register_handlers() {
|
||||
result.version = doc->version;
|
||||
if(doc->unit.completed() || doc->unit.fatal_error()) {
|
||||
auto diags = feature::diagnostics(doc->unit);
|
||||
auto json = et::serde::json::to_json<et::ipc::lsp_config>(diags);
|
||||
result.diagnostics = et::serde::RawValue{json ? std::move(*json) : "[]"};
|
||||
auto json = kota::codec::json::to_json<kota::ipc::lsp_config>(diags);
|
||||
result.diagnostics = kota::codec::RawValue{json ? std::move(*json) : "[]"};
|
||||
LOG_INFO("Compile done: path={}, {}ms, {} diags, fatal={}",
|
||||
params.path,
|
||||
timer.ms(),
|
||||
diags.size(),
|
||||
doc->unit.fatal_error());
|
||||
} else {
|
||||
result.diagnostics = et::serde::RawValue{"[]"};
|
||||
result.diagnostics = kota::codec::RawValue{"[]"};
|
||||
LOG_WARN("Compile incomplete: path={}, {}ms", params.path, timer.ms());
|
||||
}
|
||||
result.memory_usage = 0; // TODO: query actual memory
|
||||
@@ -201,7 +201,7 @@ void StatefulWorker::register_handlers() {
|
||||
|
||||
// === DocumentUpdate ===
|
||||
// Only mark the document dirty — do NOT update doc.text or doc.version
|
||||
// here. The et::queue compilation work may be reading doc.text on the
|
||||
// here. The kota::queue compilation work may be reading doc.text on the
|
||||
// thread pool concurrently, so writing it from the event loop would be
|
||||
// a data race. The next Compile request will bring the correct text
|
||||
// and update it inside the strand lock.
|
||||
@@ -238,11 +238,11 @@ void StatefulWorker::register_handlers() {
|
||||
case K::Hover:
|
||||
co_return co_await with_ast(params.path, [&](DocumentEntry& doc) {
|
||||
auto result = feature::hover(doc.unit, params.offset);
|
||||
return result ? to_raw(*result) : et::serde::RawValue{"null"};
|
||||
return result ? to_raw(*result) : kota::codec::RawValue{"null"};
|
||||
});
|
||||
case K::GoToDefinition:
|
||||
// TODO: Implement go-to-definition
|
||||
co_return et::serde::RawValue{"[]"};
|
||||
co_return kota::codec::RawValue{"[]"};
|
||||
case K::SemanticTokens:
|
||||
co_return co_await with_ast(params.path, [&](DocumentEntry& doc) {
|
||||
return to_raw(feature::semantic_tokens(doc.unit));
|
||||
@@ -268,9 +268,9 @@ void StatefulWorker::register_handlers() {
|
||||
});
|
||||
case K::CodeAction:
|
||||
// TODO: Implement code actions
|
||||
co_return et::serde::RawValue{"[]"};
|
||||
co_return kota::codec::RawValue{"[]"};
|
||||
}
|
||||
co_return et::serde::RawValue{"null"};
|
||||
co_return kota::codec::RawValue{"null"};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -284,15 +284,15 @@ int run_stateful_worker_mode(std::uint64_t memory_limit,
|
||||
|
||||
LOG_INFO("Starting stateful worker, memory_limit={}MB", memory_limit / (1024 * 1024));
|
||||
|
||||
et::event_loop loop;
|
||||
kota::event_loop loop;
|
||||
|
||||
auto transport_result = et::ipc::StreamTransport::open_stdio(loop);
|
||||
auto transport_result = kota::ipc::StreamTransport::open_stdio(loop);
|
||||
if(!transport_result) {
|
||||
LOG_ERROR("Failed to open stdio transport");
|
||||
return 1;
|
||||
}
|
||||
|
||||
et::ipc::BincodePeer peer(loop, std::move(*transport_result));
|
||||
kota::ipc::BincodePeer peer(loop, std::move(*transport_result));
|
||||
|
||||
StatefulWorker worker(peer, memory_limit);
|
||||
worker.register_handlers();
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#include "server/stateless_worker.h"
|
||||
|
||||
#include "compile/compilation.h"
|
||||
#include "eventide/async/async.h"
|
||||
#include "eventide/ipc/peer.h"
|
||||
#include "eventide/ipc/transport.h"
|
||||
#include "feature/feature.h"
|
||||
#include "index/tu_index.h"
|
||||
#include "server/protocol.h"
|
||||
#include "server/worker_common.h"
|
||||
#include "support/logging.h"
|
||||
|
||||
#include "kota/async/async.h"
|
||||
#include "kota/ipc/codec/bincode.h"
|
||||
#include "kota/ipc/peer.h"
|
||||
#include "kota/ipc/transport.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace et = eventide;
|
||||
using et::ipc::RequestResult;
|
||||
using RequestContext = et::ipc::BincodePeer::RequestContext;
|
||||
using kota::ipc::RequestResult;
|
||||
using RequestContext = kota::ipc::BincodePeer::RequestContext;
|
||||
|
||||
/// Extract error messages from compilation diagnostics.
|
||||
static std::string collect_errors(CompilationUnit& unit) {
|
||||
@@ -96,8 +96,13 @@ static worker::BuildResult handle_build_pch(const worker::BuildParams& params) {
|
||||
errors = collect_errors(unit);
|
||||
|
||||
std::string tu_index_data;
|
||||
if(success)
|
||||
std::string pch_links_json;
|
||||
if(success) {
|
||||
tu_index_data = serialize_tu_index(unit);
|
||||
auto links = feature::document_links(unit);
|
||||
auto raw = to_raw(links);
|
||||
pch_links_json = std::move(raw.data);
|
||||
}
|
||||
|
||||
// Destroy CompilationUnit to flush PCH to disk.
|
||||
unit = CompilationUnit(nullptr);
|
||||
@@ -110,6 +115,7 @@ static worker::BuildResult handle_build_pch(const worker::BuildParams& params) {
|
||||
result.output_path = std::move(final_path);
|
||||
result.deps = pch_info.deps;
|
||||
result.tu_index_data = std::move(tu_index_data);
|
||||
result.pch_links_json = std::move(pch_links_json);
|
||||
return result;
|
||||
} else {
|
||||
LOG_WARN("BuildPCH failed: file={}, {}ms, errors=[{}]", params.file, timer.ms(), errors);
|
||||
@@ -260,20 +266,20 @@ int run_stateless_worker_mode(const std::string& worker_name, const std::string&
|
||||
|
||||
LOG_INFO("Starting stateless worker");
|
||||
|
||||
et::event_loop loop;
|
||||
kota::event_loop loop;
|
||||
|
||||
auto transport_result = et::ipc::StreamTransport::open_stdio(loop);
|
||||
auto transport_result = kota::ipc::StreamTransport::open_stdio(loop);
|
||||
if(!transport_result) {
|
||||
LOG_ERROR("Failed to open stdio transport");
|
||||
return 1;
|
||||
}
|
||||
|
||||
et::ipc::BincodePeer peer(loop, std::move(*transport_result));
|
||||
kota::ipc::BincodePeer peer(loop, std::move(*transport_result));
|
||||
|
||||
peer.on_request([&](RequestContext& ctx,
|
||||
const worker::BuildParams& params) -> RequestResult<worker::BuildParams> {
|
||||
using K = worker::BuildKind;
|
||||
auto result = co_await et::queue([&]() -> worker::BuildResult {
|
||||
auto result = co_await kota::queue([&]() -> worker::BuildResult {
|
||||
switch(params.kind) {
|
||||
case K::BuildPCH: return handle_build_pch(params);
|
||||
case K::BuildPCM: return handle_build_pcm(params);
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
#include <vector>
|
||||
|
||||
#include "compile/compilation.h"
|
||||
#include "eventide/ipc/json_codec.h"
|
||||
#include "eventide/serde/json/serializer.h"
|
||||
#include "eventide/serde/serde/raw_value.h"
|
||||
|
||||
#include "kota/codec/json/serializer.h"
|
||||
#include "kota/codec/raw_value.h"
|
||||
#include "kota/ipc/codec/json.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
@@ -36,9 +37,9 @@ inline void fill_args(CompilationParams& cp,
|
||||
|
||||
/// Serialize a value to JSON RawValue using LSP config.
|
||||
template <typename T>
|
||||
inline eventide::serde::RawValue to_raw(const T& value) {
|
||||
auto json = eventide::serde::json::to_json<eventide::ipc::lsp_config>(value);
|
||||
return eventide::serde::RawValue{json ? std::move(*json) : "null"};
|
||||
inline kota::codec::RawValue to_raw(const T& value) {
|
||||
auto json = kota::codec::json::to_json<kota::ipc::lsp_config>(value);
|
||||
return kota::codec::RawValue{json ? std::move(*json) : "null"};
|
||||
}
|
||||
|
||||
} // namespace clice
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
#include <csignal>
|
||||
#include <string>
|
||||
|
||||
#include "eventide/ipc/transport.h"
|
||||
#include "support/logging.h"
|
||||
|
||||
#include "kota/ipc/transport.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace {
|
||||
@@ -13,7 +14,7 @@ namespace {
|
||||
/// Coroutine that drains a worker's stderr pipe.
|
||||
/// Workers write their own log files, so this only captures unexpected output
|
||||
/// (crash stacktraces, assertion failures, etc.) that bypasses spdlog.
|
||||
et::task<> drain_stderr(et::pipe stderr_pipe, std::string prefix) {
|
||||
kota::task<> drain_stderr(kota::pipe stderr_pipe, std::string prefix) {
|
||||
std::string buffer;
|
||||
while(true) {
|
||||
auto result = co_await stderr_pipe.read();
|
||||
@@ -54,7 +55,7 @@ bool WorkerPool::spawn_worker(const std::string& self_path,
|
||||
auto worker_index = workers.size();
|
||||
std::string worker_name = std::string(stateful ? "SF-" : "SL-") + std::to_string(worker_index);
|
||||
|
||||
et::process::options opts;
|
||||
kota::process::options opts;
|
||||
opts.file = self_path;
|
||||
if(stateful) {
|
||||
opts.args = {self_path,
|
||||
@@ -75,12 +76,12 @@ bool WorkerPool::spawn_worker(const std::string& self_path,
|
||||
}
|
||||
|
||||
opts.streams = {
|
||||
et::process::stdio::pipe(true, false), // stdin: child reads
|
||||
et::process::stdio::pipe(false, true), // stdout: child writes
|
||||
et::process::stdio::pipe(false, true), // stderr: child writes
|
||||
kota::process::stdio::pipe(true, false), // stdin: child reads
|
||||
kota::process::stdio::pipe(false, true), // stdout: child writes
|
||||
kota::process::stdio::pipe(false, true), // stderr: child writes
|
||||
};
|
||||
|
||||
auto result = et::process::spawn(opts, loop);
|
||||
auto result = kota::process::spawn(opts, loop);
|
||||
if(!result) {
|
||||
LOG_ERROR("Failed to spawn {} worker: {}",
|
||||
stateful ? "stateful" : "stateless",
|
||||
@@ -92,9 +93,9 @@ bool WorkerPool::spawn_worker(const std::string& self_path,
|
||||
|
||||
// StreamTransport: input = child's stdout (parent reads), output = child's stdin (parent
|
||||
// writes)
|
||||
auto transport = std::make_unique<et::ipc::StreamTransport>(std::move(spawn.stdout_pipe),
|
||||
std::move(spawn.stdin_pipe));
|
||||
auto peer = std::make_unique<et::ipc::BincodePeer>(loop, std::move(transport));
|
||||
auto transport = std::make_unique<kota::ipc::StreamTransport>(std::move(spawn.stdout_pipe),
|
||||
std::move(spawn.stdin_pipe));
|
||||
auto peer = std::make_unique<kota::ipc::BincodePeer>(loop, std::move(transport));
|
||||
|
||||
// Schedule stderr log collection
|
||||
std::string prefix = "[" + worker_name + "]";
|
||||
@@ -142,7 +143,7 @@ bool WorkerPool::start(const WorkerPoolOptions& options) {
|
||||
return true;
|
||||
}
|
||||
|
||||
et::task<> WorkerPool::stop() {
|
||||
kota::task<> WorkerPool::stop() {
|
||||
LOG_INFO("WorkerPool stopping...");
|
||||
|
||||
// Close output pipes to signal workers to exit gracefully
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "eventide/async/async.h"
|
||||
#include "eventide/ipc/peer.h"
|
||||
#include "server/protocol.h"
|
||||
|
||||
#include "kota/async/async.h"
|
||||
#include "kota/ipc/codec/bincode.h"
|
||||
#include "kota/ipc/peer.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace et = eventide;
|
||||
using et::ipc::RequestResult;
|
||||
using kota::ipc::RequestResult;
|
||||
|
||||
struct WorkerPoolOptions {
|
||||
std::string self_path;
|
||||
@@ -28,23 +28,24 @@ struct WorkerPoolOptions {
|
||||
|
||||
class WorkerPool {
|
||||
public:
|
||||
WorkerPool(et::event_loop& loop) : loop(loop) {}
|
||||
WorkerPool(kota::event_loop& loop) : loop(loop) {}
|
||||
|
||||
/// Spawn all worker processes. Returns false on failure.
|
||||
bool start(const WorkerPoolOptions& options);
|
||||
|
||||
/// Gracefully stop all workers.
|
||||
et::task<> stop();
|
||||
kota::task<> stop();
|
||||
|
||||
/// Send a request to a stateful worker with path_id affinity routing.
|
||||
template <typename Params>
|
||||
RequestResult<Params> send_stateful(std::uint32_t path_id,
|
||||
const Params& params,
|
||||
et::ipc::request_options opts = {});
|
||||
kota::ipc::request_options opts = {});
|
||||
|
||||
/// Send a request to a stateless worker with round-robin dispatch.
|
||||
template <typename Params>
|
||||
RequestResult<Params> send_stateless(const Params& params, et::ipc::request_options opts = {});
|
||||
RequestResult<Params> send_stateless(const Params& params,
|
||||
kota::ipc::request_options opts = {});
|
||||
|
||||
/// Send a notification to the stateful worker owning path_id (if any).
|
||||
template <typename Params>
|
||||
@@ -60,12 +61,12 @@ public:
|
||||
|
||||
private:
|
||||
struct WorkerProcess {
|
||||
et::process proc;
|
||||
std::unique_ptr<et::ipc::BincodePeer> peer;
|
||||
kota::process proc;
|
||||
std::unique_ptr<kota::ipc::BincodePeer> peer;
|
||||
std::size_t owned_documents = 0;
|
||||
};
|
||||
|
||||
et::event_loop& loop;
|
||||
kota::event_loop& loop;
|
||||
llvm::SmallVector<WorkerProcess> stateless_workers;
|
||||
llvm::SmallVector<WorkerProcess> stateful_workers;
|
||||
std::size_t next_stateless = 0;
|
||||
@@ -86,13 +87,13 @@ private:
|
||||
template <typename Params>
|
||||
RequestResult<Params> WorkerPool::send_stateful(std::uint32_t path_id,
|
||||
const Params& params,
|
||||
et::ipc::request_options opts) {
|
||||
kota::ipc::request_options opts) {
|
||||
if(stateful_workers.empty()) {
|
||||
co_return et::outcome_error(et::ipc::Error{"No stateful workers available"});
|
||||
co_return kota::outcome_error(kota::ipc::Error{"No stateful workers available"});
|
||||
}
|
||||
// No timeout: compile tasks run as detached tasks (loop.schedule) that
|
||||
// are immune to LSP $/cancelRequest. Adding a timeout here would use
|
||||
// eventide's with_token/when_any which has a spurious-cancellation bug
|
||||
// kotatsu's with_token/when_any which has a spurious-cancellation bug
|
||||
// that kills requests within milliseconds instead of the configured period.
|
||||
auto idx = assign_worker(path_id);
|
||||
co_return co_await stateful_workers[idx].peer->send_request(params, opts);
|
||||
@@ -100,9 +101,9 @@ RequestResult<Params> WorkerPool::send_stateful(std::uint32_t path_id,
|
||||
|
||||
template <typename Params>
|
||||
RequestResult<Params> WorkerPool::send_stateless(const Params& params,
|
||||
et::ipc::request_options opts) {
|
||||
kota::ipc::request_options opts) {
|
||||
if(stateless_workers.empty()) {
|
||||
co_return et::outcome_error(et::ipc::Error{"No stateless workers available"});
|
||||
co_return kota::outcome_error(kota::ipc::Error{"No stateless workers available"});
|
||||
}
|
||||
auto idx = next_stateless;
|
||||
next_stateless = (next_stateless + 1) % stateless_workers.size();
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
#include "eventide/ipc/lsp/position.h"
|
||||
#include "eventide/ipc/lsp/protocol.h"
|
||||
#include "eventide/serde/json/json.h"
|
||||
#include "support/filesystem.h"
|
||||
#include "support/logging.h"
|
||||
#include "syntax/scan.h"
|
||||
|
||||
#include "kota/codec/json/json.h"
|
||||
#include "kota/ipc/lsp/position.h"
|
||||
#include "kota/ipc/lsp/protocol.h"
|
||||
#include "llvm/Support/Chrono.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace lsp = eventide::ipc::lsp;
|
||||
namespace lsp = kota::ipc::lsp;
|
||||
|
||||
/// Find the tightest (innermost) occurrence containing `offset` via binary search.
|
||||
const static index::Occurrence* lookup_occurrence(const std::vector<index::Occurrence>& occs,
|
||||
@@ -194,7 +194,7 @@ void Workspace::load_cache() {
|
||||
}
|
||||
|
||||
CacheData data;
|
||||
auto status = eventide::serde::json::from_json(*content, data);
|
||||
auto status = kota::codec::json::from_json(*content, data);
|
||||
if(!status) {
|
||||
LOG_WARN("Failed to parse cache.json");
|
||||
return;
|
||||
@@ -300,7 +300,7 @@ void Workspace::save_cache() {
|
||||
data.pcm.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
auto json_str = eventide::serde::json::to_json(data);
|
||||
auto json_str = kota::codec::json::to_json(data);
|
||||
if(!json_str) {
|
||||
LOG_WARN("Failed to serialize cache.json");
|
||||
return;
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <utility>
|
||||
|
||||
#include "command/command.h"
|
||||
#include "eventide/ipc/lsp/position.h"
|
||||
#include "eventide/ipc/lsp/protocol.h"
|
||||
#include "index/merged_index.h"
|
||||
#include "index/project_index.h"
|
||||
#include "semantic/relation_kind.h"
|
||||
@@ -18,6 +16,8 @@
|
||||
#include "support/path_pool.h"
|
||||
#include "syntax/dependency_graph.h"
|
||||
|
||||
#include "kota/ipc/lsp/position.h"
|
||||
#include "kota/ipc/lsp/protocol.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
@@ -25,9 +25,8 @@
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace et = eventide;
|
||||
namespace protocol = et::ipc::protocol;
|
||||
namespace lsp = et::ipc::lsp;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
namespace lsp = kota::ipc::lsp;
|
||||
|
||||
/// Two-layer staleness snapshot for compilation artifacts (PCH, AST, etc.).
|
||||
///
|
||||
@@ -140,7 +139,8 @@ struct PCHState {
|
||||
std::uint32_t bound = 0;
|
||||
std::uint64_t hash = 0;
|
||||
DepsSnapshot deps;
|
||||
std::shared_ptr<eventide::event> building;
|
||||
std::string document_links_json; ///< Pre-serialized DocumentLink[] from PCH build
|
||||
std::shared_ptr<kota::event> building;
|
||||
};
|
||||
|
||||
/// Cached PCM state for a single C++20 module. Shared across all files that
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
|
||||
#include "eventide/common/meta.h"
|
||||
#include "eventide/common/ranges.h"
|
||||
#include "eventide/reflection/enum.h"
|
||||
#include "eventide/reflection/struct.h"
|
||||
|
||||
#include "kota/meta/enum.h"
|
||||
#include "kota/meta/struct.h"
|
||||
#include "kota/support/ranges.h"
|
||||
#include "kota/support/type_traits.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
@@ -86,7 +85,7 @@ struct std::formatter<std::error_code> : std::formatter<std::string_view> {
|
||||
}
|
||||
};
|
||||
|
||||
template <eventide::refl::enum_type E>
|
||||
template <kota::meta::enum_type E>
|
||||
struct std::formatter<E> : std::formatter<std::string> {
|
||||
using Base = std::formatter<std::string>;
|
||||
|
||||
@@ -97,7 +96,7 @@ struct std::formatter<E> : std::formatter<std::string> {
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const E& value, FormatContext& ctx) const {
|
||||
auto name = eventide::refl::enum_name(value);
|
||||
auto name = kota::meta::enum_name(value);
|
||||
if(name.empty()) {
|
||||
using U = std::underlying_type_t<E>;
|
||||
return Base::format(std::format("{}", static_cast<U>(value)), ctx);
|
||||
@@ -107,9 +106,8 @@ struct std::formatter<E> : std::formatter<std::string> {
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept clice_reflectable_class =
|
||||
eventide::refl::reflectable_class<T> && !eventide::sequence_range<T> &&
|
||||
!eventide::set_range<T> && !eventide::map_range<T>;
|
||||
concept clice_reflectable_class = kota::meta::reflectable_class<T> && !kota::sequence_range<T> &&
|
||||
!kota::set_range<T> && !kota::map_range<T>;
|
||||
|
||||
template <clice_reflectable_class T>
|
||||
struct std::formatter<T> : std::formatter<std::string> {
|
||||
@@ -138,7 +136,7 @@ std::string dump(const Object& object) {
|
||||
return std::format("\"{}\"", object);
|
||||
} else if constexpr(std::is_same_v<T, llvm::StringRef>) {
|
||||
return std::format("\"{}\"", object);
|
||||
} else if constexpr(eventide::map_range<T>) {
|
||||
} else if constexpr(kota::map_range<T>) {
|
||||
std::string result = "{";
|
||||
bool first = true;
|
||||
for(auto&& [key, value]: object) {
|
||||
@@ -150,8 +148,8 @@ std::string dump(const Object& object) {
|
||||
}
|
||||
result += "}";
|
||||
return result;
|
||||
} else if constexpr(eventide::set_range<T> || eventide::sequence_range<T>) {
|
||||
std::string result = eventide::set_range<T> ? "{" : "[";
|
||||
} else if constexpr(kota::set_range<T> || kota::sequence_range<T>) {
|
||||
std::string result = kota::set_range<T> ? "{" : "[";
|
||||
bool first = true;
|
||||
for(auto&& value: object) {
|
||||
if(!first) {
|
||||
@@ -160,10 +158,10 @@ std::string dump(const Object& object) {
|
||||
first = false;
|
||||
result += dump(value);
|
||||
}
|
||||
result += eventide::set_range<T> ? "}" : "]";
|
||||
result += kota::set_range<T> ? "}" : "]";
|
||||
return result;
|
||||
} else if constexpr(eventide::refl::enum_type<T>) {
|
||||
auto name = eventide::refl::enum_name(object);
|
||||
} else if constexpr(kota::meta::enum_type<T>) {
|
||||
auto name = kota::meta::enum_name(object);
|
||||
if(!name.empty()) {
|
||||
return std::format("\"{}\"", name);
|
||||
}
|
||||
@@ -172,7 +170,7 @@ std::string dump(const Object& object) {
|
||||
} else if constexpr(clice_reflectable_class<T>) {
|
||||
std::string result = "{";
|
||||
bool first = true;
|
||||
eventide::refl::for_each(object, [&](auto field) {
|
||||
kota::meta::for_each(object, [&](auto field) {
|
||||
if(!first) {
|
||||
result += ", ";
|
||||
}
|
||||
@@ -181,7 +179,7 @@ std::string dump(const Object& object) {
|
||||
});
|
||||
result += "}";
|
||||
return result;
|
||||
} else if constexpr(eventide::Formattable<T>) {
|
||||
} else if constexpr(kota::Formattable<T>) {
|
||||
return std::format("{}", object);
|
||||
} else {
|
||||
return "<unformattable>";
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
#include <chrono>
|
||||
|
||||
#include "command/toolchain.h"
|
||||
#include "eventide/async/async.h"
|
||||
#include "support/logging.h"
|
||||
#include "syntax/include_resolver.h"
|
||||
#include "syntax/scan.h"
|
||||
|
||||
#include "kota/async/async.h"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/ADT/StringSet.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
@@ -18,8 +18,6 @@
|
||||
|
||||
namespace clice {
|
||||
|
||||
namespace et = eventide;
|
||||
|
||||
// DependencyGraph implementation
|
||||
|
||||
void DependencyGraph::add_module(llvm::StringRef module_name, std::uint32_t path_id) {
|
||||
@@ -253,12 +251,12 @@ FileScanResult scan_file_worker(const char* path, std::uint32_t path_id, std::ui
|
||||
}
|
||||
|
||||
/// The async scan implementation that runs on a local event loop.
|
||||
et::task<> scan_impl(CompilationDatabase& cdb,
|
||||
PathPool& path_pool,
|
||||
DependencyGraph& graph,
|
||||
ScanReport& report,
|
||||
ScanCache* ext_cache,
|
||||
et::event_loop& loop) {
|
||||
kota::task<> scan_impl(CompilationDatabase& cdb,
|
||||
PathPool& path_pool,
|
||||
DependencyGraph& graph,
|
||||
ScanReport& report,
|
||||
ScanCache* ext_cache,
|
||||
kota::event_loop& loop) {
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
|
||||
// Reuse context groups and configs from cache when available (warm runs).
|
||||
@@ -316,10 +314,10 @@ et::task<> scan_impl(CompilationDatabase& cdb,
|
||||
if(!pending.empty()) {
|
||||
LOG_INFO("Warming toolchain cache: {} unique queries", pending.size());
|
||||
|
||||
std::vector<et::task<ToolchainResult, et::error>> tasks;
|
||||
std::vector<kota::task<ToolchainResult, kota::error>> tasks;
|
||||
tasks.reserve(pending.size());
|
||||
for(auto& query: pending) {
|
||||
tasks.push_back(et::queue(
|
||||
tasks.push_back(kota::queue(
|
||||
[q = std::move(query)]() -> ToolchainResult {
|
||||
ToolchainResult result;
|
||||
result.key = q.key;
|
||||
@@ -337,7 +335,7 @@ et::task<> scan_impl(CompilationDatabase& cdb,
|
||||
loop));
|
||||
}
|
||||
|
||||
auto outcome = co_await et::when_all(std::move(tasks));
|
||||
auto outcome = co_await kota::when_all(std::move(tasks));
|
||||
if(outcome.has_value()) {
|
||||
cdb.inject_results(*outcome);
|
||||
} else {
|
||||
@@ -390,7 +388,7 @@ et::task<> scan_impl(CompilationDatabase& cdb,
|
||||
llvm::StringSet<> entries;
|
||||
};
|
||||
|
||||
std::vector<et::task<DirEntry, et::error>> pending_dir_tasks;
|
||||
std::vector<kota::task<DirEntry, kota::error>> pending_dir_tasks;
|
||||
|
||||
if(dir_cache.dirs.empty()) {
|
||||
llvm::StringSet<> unique_dirs;
|
||||
@@ -412,7 +410,7 @@ et::task<> scan_impl(CompilationDatabase& cdb,
|
||||
pending_dir_tasks.reserve(unique_dirs.size());
|
||||
for(auto& entry: unique_dirs) {
|
||||
auto dir_path = entry.getKey().str();
|
||||
pending_dir_tasks.push_back(et::queue(
|
||||
pending_dir_tasks.push_back(kota::queue(
|
||||
[dir_path = std::move(dir_path)]() -> DirEntry {
|
||||
DirEntry result;
|
||||
result.dir_path = dir_path;
|
||||
@@ -463,7 +461,7 @@ et::task<> scan_impl(CompilationDatabase& cdb,
|
||||
// queued for scanning on the thread pool. When wave N+1 starts,
|
||||
// these tasks are already running (or finished), eliminating most
|
||||
// of the Phase 1 wait time for subsequent waves.
|
||||
std::vector<et::task<FileScanResult, et::error>> prefetch_tasks;
|
||||
std::vector<kota::task<FileScanResult, kota::error>> prefetch_tasks;
|
||||
|
||||
// Pre-resolved search configs: built once after dir cache is populated,
|
||||
// then reused for all waves. Eliminates StringMap lookups in Phase 2.
|
||||
@@ -500,7 +498,7 @@ et::task<> scan_impl(CompilationDatabase& cdb,
|
||||
|
||||
if(!prefetch_tasks.empty()) {
|
||||
// Waves 1+: await prefetched scan tasks from previous Phase 2.
|
||||
auto scan_outcome = co_await et::when_all(std::move(prefetch_tasks));
|
||||
auto scan_outcome = co_await kota::when_all(std::move(prefetch_tasks));
|
||||
prefetch_tasks.clear();
|
||||
if(scan_outcome.has_error()) {
|
||||
LOG_ERROR("Prefetch scan failed: {}", scan_outcome.error().message());
|
||||
@@ -514,7 +512,7 @@ et::task<> scan_impl(CompilationDatabase& cdb,
|
||||
}
|
||||
} else {
|
||||
// Wave 0 (or warm run with all cache hits): create scan tasks now.
|
||||
std::vector<et::task<FileScanResult, et::error>> scan_tasks;
|
||||
std::vector<kota::task<FileScanResult, kota::error>> scan_tasks;
|
||||
scan_tasks.reserve(current_wave.size());
|
||||
for(auto& entry: current_wave) {
|
||||
auto pid = entry.path_id;
|
||||
@@ -525,8 +523,8 @@ et::task<> scan_impl(CompilationDatabase& cdb,
|
||||
}
|
||||
auto path = path_pool.resolve(pid).data();
|
||||
scan_tasks.push_back(
|
||||
et::queue([path, pid, cid]() { return scan_file_worker(path, pid, cid); },
|
||||
loop));
|
||||
kota::queue([path, pid, cid]() { return scan_file_worker(path, pid, cid); },
|
||||
loop));
|
||||
}
|
||||
|
||||
// Optimization 1: await dir cache tasks concurrently with scan tasks.
|
||||
@@ -535,7 +533,7 @@ et::task<> scan_impl(CompilationDatabase& cdb,
|
||||
// max(dir_time, scan_time) instead of dir_time + scan_time.
|
||||
if(!pending_dir_tasks.empty()) {
|
||||
auto dir_t0 = std::chrono::steady_clock::now();
|
||||
auto dir_outcome = co_await et::when_all(std::move(pending_dir_tasks));
|
||||
auto dir_outcome = co_await kota::when_all(std::move(pending_dir_tasks));
|
||||
pending_dir_tasks.clear();
|
||||
if(dir_outcome.has_value()) {
|
||||
for(auto& entry: *dir_outcome) {
|
||||
@@ -549,7 +547,7 @@ et::task<> scan_impl(CompilationDatabase& cdb,
|
||||
}
|
||||
|
||||
if(!scan_tasks.empty()) {
|
||||
auto scan_outcome = co_await et::when_all(std::move(scan_tasks));
|
||||
auto scan_outcome = co_await kota::when_all(std::move(scan_tasks));
|
||||
if(scan_outcome.has_error()) {
|
||||
LOG_ERROR("Parallel scan failed: {}", scan_outcome.error().message());
|
||||
break;
|
||||
@@ -749,7 +747,7 @@ et::task<> scan_impl(CompilationDatabase& cdb,
|
||||
if(!ext_cache ||
|
||||
ext_cache->scan_results.find(inc_path_id) == ext_cache->scan_results.end()) {
|
||||
auto inc_path = path_pool.resolve(inc_path_id).data();
|
||||
prefetch_tasks.push_back(et::queue(
|
||||
prefetch_tasks.push_back(kota::queue(
|
||||
[inc_path, inc_path_id, cid = scan_result.config_id]() {
|
||||
return scan_file_worker(inc_path, inc_path_id, cid);
|
||||
},
|
||||
@@ -827,7 +825,7 @@ ScanReport scan_dependency_graph(CompilationDatabase& cdb,
|
||||
return report;
|
||||
}
|
||||
|
||||
et::event_loop loop;
|
||||
kota::event_loop loop;
|
||||
loop.schedule(scan_impl(cdb, path_pool, graph, report, cache, loop));
|
||||
loop.run();
|
||||
return report;
|
||||
|
||||
@@ -53,7 +53,8 @@ void Lexer::lex(Token& token) {
|
||||
}
|
||||
} else if(parse_pp_keyword) {
|
||||
parse_pp_keyword = false;
|
||||
parse_header_name = token.text(content) == "include";
|
||||
auto kw = token.text(content);
|
||||
parse_header_name = kw == "include" || kw == "include_next" || kw == "embed";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,4 +106,60 @@ Token Lexer::advance_until(TokenKind kind) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_directive_keyword(llvm::StringRef word) {
|
||||
return word == "include" || word == "include_next" || word == "import" || word == "embed" ||
|
||||
word == "__has_include" || word == "__has_include_next" || word == "__has_embed";
|
||||
}
|
||||
|
||||
std::optional<LocalSourceRange> find_directive_argument(llvm::StringRef content,
|
||||
std::uint32_t offset,
|
||||
const clang::LangOptions* lang_opts) {
|
||||
std::uint32_t line_start = 0;
|
||||
if(auto nl = content.rfind('\n', offset); nl != llvm::StringRef::npos)
|
||||
line_start = static_cast<std::uint32_t>(nl + 1);
|
||||
|
||||
auto line = content.substr(line_start);
|
||||
Lexer lexer(line, true, lang_opts);
|
||||
bool after_has_keyword = false;
|
||||
bool ready = false;
|
||||
|
||||
while(true) {
|
||||
auto tok = lexer.advance();
|
||||
if(tok.is_eof() || tok.is_eod())
|
||||
break;
|
||||
|
||||
auto abs_begin = line_start + tok.range.begin;
|
||||
auto abs_end = line_start + tok.range.end;
|
||||
|
||||
if(tok.is_identifier()) {
|
||||
auto text = tok.text(line);
|
||||
if(text == "__has_include" || text == "__has_include_next" || text == "__has_embed") {
|
||||
after_has_keyword = true;
|
||||
continue;
|
||||
}
|
||||
if(text == "include" || text == "include_next" || text == "embed") {
|
||||
ready = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(tok.kind == clang::tok::l_paren && after_has_keyword) {
|
||||
after_has_keyword = false;
|
||||
ready = true;
|
||||
lexer.set_header_name_mode();
|
||||
continue;
|
||||
}
|
||||
|
||||
if(abs_begin < offset || !ready)
|
||||
continue;
|
||||
|
||||
if(tok.is_header_name() || tok.kind == clang::tok::string_literal)
|
||||
return LocalSourceRange(abs_begin, abs_end);
|
||||
|
||||
if(tok.is_identifier())
|
||||
return LocalSourceRange(abs_begin, abs_end);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace clice
|
||||
|
||||
@@ -51,6 +51,15 @@ public:
|
||||
|
||||
Token advance_until(TokenKind kind);
|
||||
|
||||
/// Force the lexer into header-name mode so the next token is lexed
|
||||
/// via LexIncludeFilename (correctly handling both "..." and <...>).
|
||||
/// Use this before lexing filename arguments in contexts like
|
||||
/// __has_include() or __has_embed() where the lexer cannot detect
|
||||
/// the mode automatically.
|
||||
void set_header_name_mode() {
|
||||
parse_header_name = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool ignore_end_of_directive = true;
|
||||
bool parse_pp_keyword = false;
|
||||
@@ -64,4 +73,13 @@ private:
|
||||
std::unique_ptr<clang::Lexer> lexer;
|
||||
};
|
||||
|
||||
/// Find the range of the filename argument in a preprocessor directive line.
|
||||
/// `content` is the full source text, `offset` points at or before the directive keyword.
|
||||
/// Returns the range of the first filename-like token (header name, string literal,
|
||||
/// or macro identifier) found on the same line, or nullopt if none.
|
||||
std::optional<LocalSourceRange>
|
||||
find_directive_argument(llvm::StringRef content,
|
||||
std::uint32_t offset,
|
||||
const clang::LangOptions* lang_opts = nullptr);
|
||||
|
||||
} // namespace clice
|
||||
|
||||
@@ -231,6 +231,14 @@ def _generate_test_data_cdbs(data_dir: Path) -> None:
|
||||
if ic_main.exists():
|
||||
_write(ic_dir, [_entry(ic_dir, ic_main, ["-I."])])
|
||||
|
||||
# document_links
|
||||
dl_dir = data_dir / "document_links"
|
||||
dl_main = dl_dir / "main.cpp"
|
||||
if dl_main.exists():
|
||||
_write(
|
||||
dl_dir, [_entry(dl_dir, dl_main, [f"-I{dl_dir.as_posix()}", "-std=c++23"])]
|
||||
)
|
||||
|
||||
# pch_test
|
||||
pt_dir = data_dir / "pch_test"
|
||||
if pt_dir.exists():
|
||||
|
||||
1
tests/data/document_links/data.bin
Normal file
1
tests/data/document_links/data.bin
Normal file
@@ -0,0 +1 @@
|
||||
0123456789
|
||||
3
tests/data/document_links/header_a.h
Normal file
3
tests/data/document_links/header_a.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
int a = 1;
|
||||
3
tests/data/document_links/header_b.h
Normal file
3
tests/data/document_links/header_b.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
int b = 2;
|
||||
3
tests/data/document_links/header_c.h
Normal file
3
tests/data/document_links/header_c.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
int c = 3;
|
||||
20
tests/data/document_links/main.cpp
Normal file
20
tests/data/document_links/main.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "header_a.h"
|
||||
#include "header_b.h"
|
||||
int x = 1;
|
||||
#include "header_c.h"
|
||||
|
||||
const char data[] = {
|
||||
#embed "data.bin"
|
||||
};
|
||||
|
||||
#if __has_embed("data.bin")
|
||||
int has_embed_found = 1;
|
||||
#endif
|
||||
|
||||
#if __has_embed("no_such_file.bin")
|
||||
int has_embed_not_found = 1;
|
||||
#endif
|
||||
|
||||
int main() {
|
||||
return a + b + c;
|
||||
}
|
||||
103
tests/integration/features/test_document_links.py
Normal file
103
tests/integration/features/test_document_links.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.workspace("document_links")
|
||||
async def test_document_links_with_pch(client, workspace):
|
||||
uri, content = await client.open_and_wait(workspace / "main.cpp")
|
||||
links = await client.document_links(uri)
|
||||
|
||||
assert links is not None, "document_links returned None"
|
||||
|
||||
targets = sorted(Path(link.target).name for link in links)
|
||||
assert targets == [
|
||||
"data.bin",
|
||||
"data.bin",
|
||||
"header_a.h",
|
||||
"header_b.h",
|
||||
"header_c.h",
|
||||
], f"Unexpected targets: {targets}"
|
||||
|
||||
client.close(uri)
|
||||
|
||||
|
||||
@pytest.mark.workspace("document_links")
|
||||
async def test_document_links_pch_portion(client, workspace):
|
||||
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
||||
links = await client.document_links(uri)
|
||||
|
||||
pch_links = [link for link in links if link.range.start.line < 2]
|
||||
assert len(pch_links) == 2, (
|
||||
f"Expected 2 PCH links (lines 0-1), got {len(pch_links)}"
|
||||
)
|
||||
|
||||
pch_targets = sorted(Path(link.target).name for link in pch_links)
|
||||
assert pch_targets == ["header_a.h", "header_b.h"]
|
||||
|
||||
client.close(uri)
|
||||
|
||||
|
||||
@pytest.mark.workspace("document_links")
|
||||
async def test_document_links_main_portion(client, workspace):
|
||||
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
||||
links = await client.document_links(uri)
|
||||
|
||||
main_links = [link for link in links if link.range.start.line >= 2]
|
||||
assert len(main_links) == 3, (
|
||||
f"Expected 3 main-file links (lines 3, 6, 9), got {len(main_links)}"
|
||||
)
|
||||
|
||||
main_targets = sorted(Path(link.target).name for link in main_links)
|
||||
assert main_targets == ["data.bin", "data.bin", "header_c.h"]
|
||||
|
||||
client.close(uri)
|
||||
|
||||
|
||||
@pytest.mark.workspace("document_links")
|
||||
async def test_document_links_embed(client, workspace):
|
||||
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
||||
links = await client.document_links(uri)
|
||||
|
||||
embed_links = [
|
||||
link
|
||||
for link in links
|
||||
if Path(link.target).name == "data.bin" and link.range.start.line == 6
|
||||
]
|
||||
assert len(embed_links) == 1, (
|
||||
f"Expected 1 embed link at line 6, got {len(embed_links)}"
|
||||
)
|
||||
|
||||
client.close(uri)
|
||||
|
||||
|
||||
@pytest.mark.workspace("document_links")
|
||||
async def test_document_links_has_embed_exists(client, workspace):
|
||||
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
||||
links = await client.document_links(uri)
|
||||
|
||||
has_embed_links = [
|
||||
link
|
||||
for link in links
|
||||
if Path(link.target).name == "data.bin" and link.range.start.line == 9
|
||||
]
|
||||
assert len(has_embed_links) == 1, (
|
||||
f"Expected 1 has_embed link at line 9, got {len(has_embed_links)}"
|
||||
)
|
||||
|
||||
client.close(uri)
|
||||
|
||||
|
||||
@pytest.mark.workspace("document_links")
|
||||
async def test_document_links_has_embed_missing(client, workspace):
|
||||
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
||||
links = await client.document_links(uri)
|
||||
|
||||
missing_links = [
|
||||
link for link in links if Path(link.target).name == "no_such_file.bin"
|
||||
]
|
||||
assert len(missing_links) == 0, (
|
||||
f"Expected 0 links for non-existent file, got {len(missing_links)}"
|
||||
)
|
||||
|
||||
client.close(uri)
|
||||
@@ -9,7 +9,7 @@ namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace protocol = eventide::ipc::protocol;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
|
||||
TEST_SUITE(CodeCompletion) {
|
||||
|
||||
|
||||
@@ -9,15 +9,15 @@ namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace protocol = eventide::ipc::protocol;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
|
||||
TEST_SUITE(DocumentLink, Tester) {
|
||||
|
||||
std::vector<protocol::DocumentLink> links;
|
||||
|
||||
void run(llvm::StringRef source) {
|
||||
void run(llvm::StringRef source, llvm::StringRef standard = "-std=c++17") {
|
||||
add_files("main.cpp", source);
|
||||
ASSERT_TRUE(compile());
|
||||
ASSERT_TRUE(compile(standard));
|
||||
links = feature::document_links(*unit, feature::PositionEncoding::UTF8);
|
||||
}
|
||||
|
||||
@@ -89,6 +89,53 @@ TEST_CASE(HasInclude) {
|
||||
EXPECT_LINK(1, "1", TestVFS::path("test.h"));
|
||||
}
|
||||
|
||||
TEST_CASE(MacroInclude) {
|
||||
run(R"cpp(
|
||||
#[test.h]
|
||||
|
||||
#[main.cpp]
|
||||
#define HEADER "test.h"
|
||||
#include @0[HEADER$]
|
||||
)cpp");
|
||||
|
||||
ASSERT_EQ(links.size(), 1U);
|
||||
EXPECT_LINK(0, "0", TestVFS::path("test.h"));
|
||||
}
|
||||
|
||||
TEST_CASE(Embed) {
|
||||
run(R"cpp(
|
||||
#[bytes.bin]
|
||||
0123456789
|
||||
|
||||
#[main.cpp]
|
||||
const char e[] = {
|
||||
#embed @0["bytes.bin"$]
|
||||
};
|
||||
)cpp",
|
||||
"-std=c++23");
|
||||
|
||||
ASSERT_EQ(links.size(), 1U);
|
||||
EXPECT_LINK(0, "0", TestVFS::path("bytes.bin"));
|
||||
}
|
||||
|
||||
TEST_CASE(HasEmbed) {
|
||||
run(R"cpp(
|
||||
#[data.bin]
|
||||
ABCDE
|
||||
|
||||
#[main.cpp]
|
||||
#if __has_embed(@0["data.bin"$])
|
||||
#endif
|
||||
|
||||
#if __has_embed("non_existent.bin")
|
||||
#endif
|
||||
)cpp",
|
||||
"-std=c++23");
|
||||
|
||||
ASSERT_EQ(links.size(), 1U);
|
||||
EXPECT_LINK(0, "0", TestVFS::path("data.bin"));
|
||||
}
|
||||
|
||||
}; // TEST_SUITE(DocumentLink)
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace protocol = eventide::ipc::protocol;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
|
||||
TEST_SUITE(DocumentSymbol, Tester) {
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace protocol = eventide::ipc::protocol;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
|
||||
TEST_SUITE(FoldingRange, Tester) {
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace protocol = eventide::ipc::protocol;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
|
||||
TEST_SUITE(Hover, Tester) {
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace protocol = eventide::ipc::protocol;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
|
||||
TEST_SUITE(InlayHint, Tester) {
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace protocol = eventide::ipc::protocol;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
|
||||
struct DecodedToken {
|
||||
LocalSourceRange range;
|
||||
@@ -423,6 +423,122 @@ cd*/
|
||||
ASSERT_EQ(comments[1].length, 4);
|
||||
}
|
||||
|
||||
TEST_CASE(ModuleDeclaration) {
|
||||
add_main("main.cpp", R"cpp(
|
||||
export @kw[module] @mod[foo];
|
||||
)cpp");
|
||||
ASSERT_TRUE(compile("-std=c++20"));
|
||||
tokens = feature::semantic_tokens(*unit, feature::PositionEncoding::UTF8);
|
||||
decoded = decode_utf8_tokens(unit->interested_content(), tokens);
|
||||
|
||||
EXPECT_TOKEN("kw", SymbolKind::Keyword);
|
||||
EXPECT_TOKEN("mod", SymbolKind::Module);
|
||||
}
|
||||
|
||||
TEST_CASE(ModuleDeclarationDotted) {
|
||||
add_main("main.cpp", R"cpp(
|
||||
export @kw[module] @m0[foo].@m1[bar];
|
||||
)cpp");
|
||||
ASSERT_TRUE(compile("-std=c++20"));
|
||||
tokens = feature::semantic_tokens(*unit, feature::PositionEncoding::UTF8);
|
||||
decoded = decode_utf8_tokens(unit->interested_content(), tokens);
|
||||
|
||||
EXPECT_TOKEN("kw", SymbolKind::Keyword);
|
||||
EXPECT_TOKEN("m0", SymbolKind::Module);
|
||||
EXPECT_TOKEN("m1", SymbolKind::Module);
|
||||
}
|
||||
|
||||
TEST_CASE(ModuleImport) {
|
||||
add_files("main.cpp", R"(
|
||||
#[mod.cppm]
|
||||
export module foo;
|
||||
export int x = 42;
|
||||
|
||||
#[main.cpp]
|
||||
@kw[import] @mod[foo];
|
||||
int y = x;
|
||||
)");
|
||||
ASSERT_TRUE(compile_with_modules());
|
||||
tokens = feature::semantic_tokens(*unit, feature::PositionEncoding::UTF8);
|
||||
decoded = decode_utf8_tokens(unit->interested_content(), tokens);
|
||||
|
||||
EXPECT_TOKEN("kw", SymbolKind::Keyword);
|
||||
EXPECT_TOKEN("mod", SymbolKind::Module);
|
||||
}
|
||||
|
||||
TEST_CASE(ModulePartition) {
|
||||
add_main("main.cpp", R"cpp(
|
||||
export module @m0[foo]:@m1[bar];
|
||||
)cpp");
|
||||
ASSERT_TRUE(compile("-std=c++20"));
|
||||
tokens = feature::semantic_tokens(*unit, feature::PositionEncoding::UTF8);
|
||||
decoded = decode_utf8_tokens(unit->interested_content(), tokens);
|
||||
|
||||
EXPECT_TOKEN("m0", SymbolKind::Module);
|
||||
EXPECT_TOKEN("m1", SymbolKind::Module);
|
||||
}
|
||||
|
||||
TEST_CASE(ModuleReexport) {
|
||||
add_files("main.cppm", R"(
|
||||
#[mod.cppm]
|
||||
export module foo;
|
||||
export int x = 42;
|
||||
|
||||
#[main.cppm]
|
||||
export module bar;
|
||||
export @kw[import] @mod[foo];
|
||||
)");
|
||||
ASSERT_TRUE(compile_with_modules());
|
||||
tokens = feature::semantic_tokens(*unit, feature::PositionEncoding::UTF8);
|
||||
decoded = decode_utf8_tokens(unit->interested_content(), tokens);
|
||||
|
||||
EXPECT_TOKEN("kw", SymbolKind::Keyword);
|
||||
EXPECT_TOKEN("mod", SymbolKind::Module);
|
||||
}
|
||||
|
||||
TEST_CASE(GlobalModuleFragment) {
|
||||
add_main("main.cpp", R"cpp(
|
||||
module;
|
||||
export module @mod[foo];
|
||||
)cpp");
|
||||
ASSERT_TRUE(compile("-std=c++20"));
|
||||
tokens = feature::semantic_tokens(*unit, feature::PositionEncoding::UTF8);
|
||||
decoded = decode_utf8_tokens(unit->interested_content(), tokens);
|
||||
|
||||
EXPECT_TOKEN("mod", SymbolKind::Module);
|
||||
}
|
||||
|
||||
TEST_CASE(PrivateModuleFragment) {
|
||||
add_main("main.cpp", R"cpp(
|
||||
export module @mod[foo];
|
||||
module :private;
|
||||
int x = 1;
|
||||
)cpp");
|
||||
ASSERT_TRUE(compile("-std=c++20"));
|
||||
tokens = feature::semantic_tokens(*unit, feature::PositionEncoding::UTF8);
|
||||
decoded = decode_utf8_tokens(unit->interested_content(), tokens);
|
||||
|
||||
EXPECT_TOKEN("mod", SymbolKind::Module);
|
||||
}
|
||||
|
||||
TEST_CASE(ModuleKeywordAsIdentifier) {
|
||||
run_utf8(R"cpp(
|
||||
void f() {
|
||||
struct @s0[module] {};
|
||||
@s1[module] @v0[m];
|
||||
int @v1[import] = 1;
|
||||
int @v2[module] = 2;
|
||||
}
|
||||
)cpp");
|
||||
|
||||
auto definition = modifier_mask({SymbolModifiers::Definition});
|
||||
EXPECT_TOKEN("s0", SymbolKind::Struct, definition);
|
||||
EXPECT_TOKEN("s1", SymbolKind::Struct);
|
||||
EXPECT_TOKEN("v0", SymbolKind::Variable, definition);
|
||||
EXPECT_TOKEN("v1", SymbolKind::Variable, definition);
|
||||
EXPECT_TOKEN("v2", SymbolKind::Variable, definition);
|
||||
}
|
||||
|
||||
}; // TEST_SUITE(SemanticTokens)
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace protocol = eventide::ipc::protocol;
|
||||
namespace protocol = kota::ipc::protocol;
|
||||
|
||||
TEST_SUITE(SignatureHelp, Tester) {
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
namespace clice::testing {
|
||||
namespace {
|
||||
|
||||
namespace et = eventide;
|
||||
|
||||
/// Build a dispatch_fn that compiles PCMs in-process (no workers).
|
||||
/// Clang requires ALL transitive PCM deps (not just direct imports)
|
||||
/// in PrebuiltModuleFiles, so we pass every available PCM.
|
||||
@@ -20,7 +18,7 @@ CompileGraph::dispatch_fn make_dispatch(CompilationDatabase& cdb,
|
||||
PathPool& pool,
|
||||
DependencyGraph& graph,
|
||||
llvm::DenseMap<std::uint32_t, std::string>& pcm_paths) {
|
||||
return [&](std::uint32_t path_id) -> et::task<bool> {
|
||||
return [&](std::uint32_t path_id) -> kota::task<bool> {
|
||||
auto file_path = pool.resolve(path_id);
|
||||
auto results = cdb.lookup(file_path, {.query_toolchain = true, .suppress_logging = true});
|
||||
if(results.empty()) {
|
||||
@@ -123,8 +121,8 @@ TEST_CASE(SingleModuleNoDeps) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_a]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_a]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid_a).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -157,8 +155,8 @@ TEST_CASE(ChainedModules) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_a, pid_b]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_a, pid_b]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid_b).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -202,8 +200,8 @@ TEST_CASE(DiamondModules) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_top]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_top]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid_top).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -238,8 +236,8 @@ TEST_CASE(DottedModuleName) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_app]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_app]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid_app).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -280,8 +278,8 @@ TEST_CASE(ReExport) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_user]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_user]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid_user).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -324,8 +322,8 @@ TEST_CASE(ExportBlock) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -360,8 +358,8 @@ TEST_CASE(GlobalModuleFragment) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -396,8 +394,8 @@ TEST_CASE(PrivateModuleFragment) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -436,8 +434,8 @@ TEST_CASE(PartitionInterface) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_m]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_m]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid_m).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -476,8 +474,8 @@ TEST_CASE(MultiplePartitions) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_lib]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_lib]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid_lib).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -519,8 +517,8 @@ TEST_CASE(PartitionChain) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_sys]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_sys]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid_sys).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -561,8 +559,8 @@ TEST_CASE(ExportNamespace) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -600,8 +598,8 @@ TEST_CASE(GMFWithImport) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -651,8 +649,8 @@ TEST_CASE(DeepChain) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -686,8 +684,8 @@ TEST_CASE(IndependentModules) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_x, pid_y]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_x, pid_y]() -> kota::task<> {
|
||||
auto r1 = co_await cg.compile(pid_x).catch_cancel();
|
||||
EXPECT_TRUE(r1.has_value() && *r1);
|
||||
auto r2 = co_await cg.compile(pid_y).catch_cancel();
|
||||
@@ -728,8 +726,8 @@ TEST_CASE(TemplateExport) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -775,8 +773,8 @@ TEST_CASE(ClassExportAndInheritance) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -813,8 +811,8 @@ TEST_CASE(RecompileAfterUpdate) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_leaf, pid_mid]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_leaf, pid_mid]() -> kota::task<> {
|
||||
// First compile.
|
||||
auto r1 = co_await cg.compile(pid_mid).catch_cancel();
|
||||
EXPECT_TRUE(r1.has_value() && *r1);
|
||||
@@ -864,8 +862,8 @@ TEST_CASE(PartitionWithGMF) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -905,8 +903,8 @@ TEST_CASE(PartitionWithExternalImport) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_app]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_app]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid_app).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -958,8 +956,8 @@ TEST_CASE(DiamondUpdateCascade) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_base, pid_left, pid_right, pid_top]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_base, pid_left, pid_right, pid_top]() -> kota::task<> {
|
||||
// Initial compile.
|
||||
auto r1 = co_await cg.compile(pid_top).catch_cancel();
|
||||
EXPECT_TRUE(r1.has_value() && *r1);
|
||||
@@ -1046,8 +1044,8 @@ TEST_CASE(ReResolveAfterUpdate) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
std::move(counting_resolver));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, &resolve_count, pid_mid]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, &resolve_count, pid_mid]() -> kota::task<> {
|
||||
// First compile: resolve_fn called once for Mid.
|
||||
auto r1 = co_await cg.compile(pid_mid).catch_cancel();
|
||||
EXPECT_TRUE(r1.has_value() && *r1);
|
||||
@@ -1092,8 +1090,8 @@ TEST_CASE(CompileFailurePropagation) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_bad]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_bad]() -> kota::task<> {
|
||||
auto result = co_await cg.compile(pid_bad).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
// Compilation should fail due to undefined symbol.
|
||||
@@ -1133,8 +1131,8 @@ TEST_CASE(ModuleImplementationUnit) {
|
||||
CompileGraph cg(make_dispatch(env.cdb, env.pool, env.graph, env.pcm_paths),
|
||||
make_resolver(env.cdb, env.pool, env.graph));
|
||||
|
||||
et::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_iface]() -> et::task<> {
|
||||
kota::event_loop loop;
|
||||
auto test = [this, &cg, &env, pid_iface]() -> kota::task<> {
|
||||
// Build the interface PCM via CompileGraph.
|
||||
auto r1 = co_await cg.compile(pid_iface).catch_cancel();
|
||||
EXPECT_TRUE(r1.has_value() && *r1);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
namespace clice::testing {
|
||||
namespace {
|
||||
|
||||
namespace et = eventide;
|
||||
namespace ranges = std::ranges;
|
||||
|
||||
/// A resolve_fn that always returns no dependencies.
|
||||
@@ -29,27 +28,27 @@ CompileGraph::resolve_fn
|
||||
}
|
||||
|
||||
CompileGraph::dispatch_fn instant_dispatch() {
|
||||
return [](std::uint32_t) -> et::task<bool> {
|
||||
return [](std::uint32_t) -> kota::task<bool> {
|
||||
co_return true;
|
||||
};
|
||||
}
|
||||
|
||||
CompileGraph::dispatch_fn tracking_dispatch(std::vector<std::uint32_t>& compiled) {
|
||||
return [&compiled](std::uint32_t path_id) -> et::task<bool> {
|
||||
return [&compiled](std::uint32_t path_id) -> kota::task<bool> {
|
||||
compiled.push_back(path_id);
|
||||
co_return true;
|
||||
};
|
||||
}
|
||||
|
||||
CompileGraph::dispatch_fn failing_dispatch() {
|
||||
return [](std::uint32_t) -> et::task<bool> {
|
||||
return [](std::uint32_t) -> kota::task<bool> {
|
||||
co_return false;
|
||||
};
|
||||
}
|
||||
|
||||
/// Dispatch that fails only for specific path_ids.
|
||||
CompileGraph::dispatch_fn selective_dispatch(llvm::DenseSet<std::uint32_t> fail_ids) {
|
||||
return [fail_ids = std::move(fail_ids)](std::uint32_t path_id) -> et::task<bool> {
|
||||
return [fail_ids = std::move(fail_ids)](std::uint32_t path_id) -> kota::task<bool> {
|
||||
co_return !fail_ids.contains(path_id);
|
||||
};
|
||||
}
|
||||
@@ -61,7 +60,7 @@ std::optional<CompileGraph> graph;
|
||||
|
||||
template <typename F>
|
||||
void execute(F&& fn) {
|
||||
et::event_loop loop;
|
||||
kota::event_loop loop;
|
||||
auto t = fn();
|
||||
loop.schedule(t);
|
||||
loop.run();
|
||||
@@ -70,7 +69,7 @@ void execute(F&& fn) {
|
||||
TEST_CASE(CompileNoDeps) {
|
||||
graph.emplace(tracking_dispatch(compiled), no_deps());
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -87,7 +86,7 @@ TEST_CASE(CompileWithDependency) {
|
||||
{1, {2}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -109,7 +108,7 @@ TEST_CASE(CompileChain) {
|
||||
{2, {3}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -132,7 +131,7 @@ TEST_CASE(DiamondDependency) {
|
||||
{3, {4} }
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -152,7 +151,7 @@ TEST_CASE(UpdateInvalidates) {
|
||||
{1, {2}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_FALSE(graph->is_dirty(2));
|
||||
EXPECT_FALSE(graph->is_dirty(1));
|
||||
@@ -172,7 +171,7 @@ TEST_CASE(UpdateCascade) {
|
||||
{2, {3}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_FALSE(graph->is_dirty(2));
|
||||
EXPECT_FALSE(graph->is_dirty(3));
|
||||
@@ -192,7 +191,7 @@ TEST_CASE(CompileAfterUpdate) {
|
||||
{1, {2}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_EQ(compiled.size(), 2u);
|
||||
|
||||
@@ -210,7 +209,7 @@ TEST_CASE(DispatchFailure) {
|
||||
{1, {2}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_FALSE(*result);
|
||||
@@ -228,7 +227,7 @@ TEST_CASE(CancelAll) {
|
||||
TEST_CASE(SecondCompileSkips) {
|
||||
graph.emplace(tracking_dispatch(compiled), no_deps());
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_EQ(compiled.size(), 1u);
|
||||
// Second compile should skip (already clean).
|
||||
@@ -245,7 +244,7 @@ TEST_CASE(CascadeThroughAlreadyDirty) {
|
||||
{2, {3}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
co_await graph->compile(1).catch_cancel();
|
||||
|
||||
// Update node 2: marks 2 and 1 dirty.
|
||||
@@ -270,7 +269,7 @@ TEST_CASE(CircularDependencyDetection) {
|
||||
{2, {1}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile(1).catch_cancel();
|
||||
// Should return false (cycle detected), not deadlock.
|
||||
EXPECT_TRUE(result.has_value());
|
||||
@@ -289,7 +288,7 @@ TEST_CASE(CrossBranchCycleDetection) {
|
||||
{3, {2} }
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile(1).catch_cancel();
|
||||
// Should return false (cycle detected), not deadlock.
|
||||
EXPECT_TRUE(result.has_value());
|
||||
@@ -312,7 +311,7 @@ TEST_CASE(UpdateResetsResolved) {
|
||||
|
||||
graph.emplace(tracking_dispatch(compiled), std::move(resolver));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
// First compile: resolves 1 -> {2}.
|
||||
co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_EQ(resolve_count, 1);
|
||||
@@ -344,7 +343,7 @@ TEST_CASE(UpdateCleansBackEdges) {
|
||||
|
||||
graph.emplace(tracking_dispatch(compiled), std::move(resolver));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
// First compile: 1 -> {2}.
|
||||
co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_FALSE(graph->is_dirty(1));
|
||||
@@ -373,7 +372,7 @@ TEST_CASE(DiamondUpdateCascade) {
|
||||
{3, {4} }
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_FALSE(graph->is_dirty(1));
|
||||
EXPECT_FALSE(graph->is_dirty(4));
|
||||
@@ -402,7 +401,7 @@ TEST_CASE(UpdateReturnsAllDirtied) {
|
||||
{2, {3}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
co_await graph->compile(1).catch_cancel();
|
||||
|
||||
auto dirtied = graph->update(3);
|
||||
@@ -417,7 +416,7 @@ TEST_CASE(UpdateReturnsAllDirtied) {
|
||||
TEST_CASE(HasUnitAndIsCompiling) {
|
||||
graph.emplace(instant_dispatch(), no_deps());
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
EXPECT_FALSE(graph->has_unit(1));
|
||||
EXPECT_FALSE(graph->is_compiling(1));
|
||||
|
||||
@@ -434,7 +433,7 @@ TEST_CASE(FailureLeavesDepsDirty) {
|
||||
{1, {2}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_FALSE(*result);
|
||||
@@ -451,7 +450,7 @@ TEST_CASE(SelfLoop) {
|
||||
{1, {1}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile(1).catch_cancel();
|
||||
// Should detect cycle and return false, not deadlock.
|
||||
EXPECT_TRUE(result.has_value());
|
||||
@@ -465,7 +464,7 @@ TEST_CASE(CancelAllAndRecompile) {
|
||||
{1, {2}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_EQ(compiled.size(), 2u);
|
||||
EXPECT_FALSE(graph->is_dirty(1));
|
||||
@@ -488,10 +487,10 @@ TEST_CASE(CancelAllAndRecompile) {
|
||||
}
|
||||
|
||||
TEST_CASE(UpdateDuringCompile) {
|
||||
et::event_loop loop;
|
||||
et::event gate;
|
||||
kota::event_loop loop;
|
||||
kota::event gate;
|
||||
|
||||
auto gated_dispatch = [&gate](std::uint32_t) -> et::task<bool> {
|
||||
auto gated_dispatch = [&gate](std::uint32_t) -> kota::task<bool> {
|
||||
co_await gate.wait();
|
||||
co_return true;
|
||||
};
|
||||
@@ -502,14 +501,14 @@ TEST_CASE(UpdateDuringCompile) {
|
||||
bool was_cancelled = false;
|
||||
|
||||
// Coroutine 1: compile(1), will suspend inside dispatch waiting on gate.
|
||||
auto compiler = [&]() -> et::task<> {
|
||||
auto compiler = [&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile(1).catch_cancel();
|
||||
compile_done = true;
|
||||
was_cancelled = !result.has_value();
|
||||
};
|
||||
|
||||
// Coroutine 2: update(1) while dispatch is in flight, then unblock gate.
|
||||
auto updater = [&]() -> et::task<> {
|
||||
auto updater = [&]() -> kota::task<> {
|
||||
graph->update(1);
|
||||
gate.set();
|
||||
co_return;
|
||||
@@ -534,7 +533,7 @@ TEST_CASE(WhenAllPartialFailure) {
|
||||
}),
|
||||
static_resolver({{1, {2, 3}}}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile(1).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_FALSE(*result);
|
||||
@@ -566,7 +565,7 @@ TEST_CASE(EmptyGraphNoCompile) {
|
||||
TEST_CASE(CompileDepsNoDeps) {
|
||||
graph.emplace(tracking_dispatch(compiled), no_deps());
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile_deps(1).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -582,7 +581,7 @@ TEST_CASE(CompileDepsWithDependency) {
|
||||
{1, {2}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile_deps(1).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -602,7 +601,7 @@ TEST_CASE(CompileDepsChain) {
|
||||
{2, {3}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile_deps(1).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -623,7 +622,7 @@ TEST_CASE(CompileDepsDiamond) {
|
||||
{3, {4} }
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile_deps(1).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -640,7 +639,7 @@ TEST_CASE(CompileDepsDiamond) {
|
||||
|
||||
TEST_CASE(CompileDepsFailure) {
|
||||
// 1 -> 2. Dispatch fails for unit 2.
|
||||
auto fail_and_track = [&](std::uint32_t path_id) -> et::task<bool> {
|
||||
auto fail_and_track = [&](std::uint32_t path_id) -> kota::task<bool> {
|
||||
compiled.push_back(path_id);
|
||||
co_return false;
|
||||
};
|
||||
@@ -650,7 +649,7 @@ TEST_CASE(CompileDepsFailure) {
|
||||
{1, {2}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile_deps(1).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_FALSE(*result);
|
||||
@@ -666,7 +665,7 @@ TEST_CASE(CompileDepsPlainCpp) {
|
||||
{10, {20}}
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto result = co_await graph->compile_deps(10).catch_cancel();
|
||||
EXPECT_TRUE(result.has_value());
|
||||
EXPECT_TRUE(*result);
|
||||
@@ -688,11 +687,11 @@ TEST_CASE(CompileDepsConcurrentDedup) {
|
||||
{2, {3, 5}},
|
||||
}));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
// Launch both compile_deps concurrently.
|
||||
auto t1 = graph->compile_deps(1);
|
||||
auto t2 = graph->compile_deps(2);
|
||||
auto results = co_await et::when_all(std::move(t1), std::move(t2));
|
||||
auto results = co_await kota::when_all(std::move(t1), std::move(t2));
|
||||
|
||||
auto [r1, r2] = results;
|
||||
EXPECT_TRUE(r1);
|
||||
@@ -722,10 +721,10 @@ TEST_CASE(CompileDepsResolveOnce) {
|
||||
|
||||
graph.emplace(tracking_dispatch(compiled), std::move(resolve));
|
||||
|
||||
execute([&]() -> et::task<> {
|
||||
execute([&]() -> kota::task<> {
|
||||
auto t1 = graph->compile_deps(1);
|
||||
auto t2 = graph->compile_deps(2);
|
||||
auto results = co_await et::when_all(std::move(t1), std::move(t2));
|
||||
auto results = co_await kota::when_all(std::move(t1), std::move(t2));
|
||||
|
||||
auto [r1, r2] = results;
|
||||
EXPECT_TRUE(r1);
|
||||
|
||||
@@ -9,8 +9,6 @@ namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace et = eventide;
|
||||
|
||||
// ============================================================================
|
||||
// End-to-end module compilation through real workers:
|
||||
// 1. Stateless worker builds PCM for module interface
|
||||
@@ -38,7 +36,7 @@ TEST_CASE(BuildPCMThenCompileWithImport) {
|
||||
std::string pcm_path;
|
||||
bool phase1_done = false;
|
||||
|
||||
sl.run([&]() -> et::task<> {
|
||||
sl.run([&]() -> kota::task<> {
|
||||
worker::BuildParams params;
|
||||
params.kind = worker::BuildKind::BuildPCM;
|
||||
params.file = iface;
|
||||
@@ -71,7 +69,7 @@ TEST_CASE(BuildPCMThenCompileWithImport) {
|
||||
|
||||
bool phase2_done = false;
|
||||
|
||||
sf.run([&]() -> et::task<> {
|
||||
sf.run([&]() -> kota::task<> {
|
||||
worker::CompileParams params;
|
||||
params.path = consumer;
|
||||
params.version = 1;
|
||||
@@ -123,7 +121,7 @@ TEST_CASE(BuildPCMChainThenCompile) {
|
||||
std::string pcm_a, pcm_b;
|
||||
bool pcm_done = false;
|
||||
|
||||
sl.run([&]() -> et::task<> {
|
||||
sl.run([&]() -> kota::task<> {
|
||||
// Build PCM for A first.
|
||||
{
|
||||
worker::BuildParams params;
|
||||
@@ -179,7 +177,7 @@ TEST_CASE(BuildPCMChainThenCompile) {
|
||||
|
||||
bool compile_done = false;
|
||||
|
||||
sf.run([&]() -> et::task<> {
|
||||
sf.run([&]() -> kota::task<> {
|
||||
worker::CompileParams params;
|
||||
params.path = consumer;
|
||||
params.version = 1;
|
||||
@@ -227,7 +225,7 @@ TEST_CASE(ModuleImplementationUnitWithWorker) {
|
||||
std::string pcm_path;
|
||||
bool pcm_done = false;
|
||||
|
||||
sl.run([&]() -> et::task<> {
|
||||
sl.run([&]() -> kota::task<> {
|
||||
worker::BuildParams params;
|
||||
params.kind = worker::BuildKind::BuildPCM;
|
||||
params.file = iface;
|
||||
@@ -257,7 +255,7 @@ TEST_CASE(ModuleImplementationUnitWithWorker) {
|
||||
|
||||
bool compile_done = false;
|
||||
|
||||
sf.run([&]() -> et::task<> {
|
||||
sf.run([&]() -> kota::task<> {
|
||||
worker::CompileParams params;
|
||||
params.path = impl;
|
||||
params.version = 1;
|
||||
|
||||
@@ -10,8 +10,6 @@ namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace et = eventide;
|
||||
|
||||
// ============================================================================
|
||||
// End-to-end PCH compilation through real workers:
|
||||
// 1. Stateless worker builds PCH for preamble headers
|
||||
@@ -39,7 +37,7 @@ TEST_CASE(BuildPCHThenCompile) {
|
||||
std::string pch_path;
|
||||
bool phase1_done = false;
|
||||
|
||||
sl.run([&]() -> et::task<> {
|
||||
sl.run([&]() -> kota::task<> {
|
||||
worker::BuildParams params;
|
||||
params.kind = worker::BuildKind::BuildPCH;
|
||||
params.file = main_file;
|
||||
@@ -79,7 +77,7 @@ TEST_CASE(BuildPCHThenCompile) {
|
||||
|
||||
auto preamble_bound = compute_preamble_bound(main_text);
|
||||
|
||||
sf.run([&]() -> et::task<> {
|
||||
sf.run([&]() -> kota::task<> {
|
||||
worker::CompileParams params;
|
||||
params.path = main_file;
|
||||
params.version = 1;
|
||||
@@ -123,7 +121,7 @@ TEST_CASE(CompileWithoutPCHStillWorks) {
|
||||
|
||||
bool compile_done = false;
|
||||
|
||||
sf.run([&]() -> et::task<> {
|
||||
sf.run([&]() -> kota::task<> {
|
||||
worker::CompileParams params;
|
||||
params.path = main_file;
|
||||
params.version = 1;
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
#include <vector>
|
||||
|
||||
#include "test/test.h"
|
||||
#include "eventide/serde/serde/raw_value.h"
|
||||
#include "server/protocol.h"
|
||||
#include "server/worker_test_helpers.h"
|
||||
|
||||
#include "kota/codec/raw_value.h"
|
||||
|
||||
namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace et = eventide;
|
||||
|
||||
TEST_SUITE(StatefulWorker) {
|
||||
|
||||
TEST_CASE(SpawnAndExit) {
|
||||
@@ -33,7 +32,7 @@ TEST_CASE(CompileRequest) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
worker::CompileParams params;
|
||||
params.path = src;
|
||||
params.version = 1;
|
||||
@@ -59,7 +58,7 @@ TEST_CASE(HoverWithoutCompile) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
// Hover on a file that hasn't been compiled should return null.
|
||||
worker::QueryParams params;
|
||||
params.kind = worker::QueryKind::Hover;
|
||||
@@ -88,7 +87,7 @@ TEST_CASE(CompileThenHover) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
// First compile
|
||||
worker::CompileParams cp;
|
||||
cp.path = src;
|
||||
@@ -129,7 +128,7 @@ TEST_CASE(DocumentUpdate) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
// Compile first
|
||||
worker::CompileParams cp;
|
||||
cp.path = src;
|
||||
@@ -170,7 +169,7 @@ TEST_CASE(CodeActionReturnsEmpty) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
worker::QueryParams params;
|
||||
params.kind = worker::QueryKind::CodeAction;
|
||||
params.path = "/tmp/test.cpp";
|
||||
@@ -192,7 +191,7 @@ TEST_CASE(GoToDefinitionReturnsEmpty) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
worker::QueryParams params;
|
||||
params.kind = worker::QueryKind::GoToDefinition;
|
||||
params.path = "/tmp/test.cpp";
|
||||
@@ -215,7 +214,7 @@ TEST_CASE(SemanticTokensWithoutCompile) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
worker::QueryParams params;
|
||||
params.kind = worker::QueryKind::SemanticTokens;
|
||||
params.path = "/tmp/nonexistent.cpp";
|
||||
@@ -236,7 +235,7 @@ TEST_CASE(FoldingRangeWithoutCompile) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
worker::QueryParams params;
|
||||
params.kind = worker::QueryKind::FoldingRange;
|
||||
params.path = "/tmp/nonexistent.cpp";
|
||||
@@ -257,7 +256,7 @@ TEST_CASE(DocumentSymbolWithoutCompile) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
worker::QueryParams params;
|
||||
params.kind = worker::QueryKind::DocumentSymbol;
|
||||
params.path = "/tmp/nonexistent.cpp";
|
||||
@@ -278,7 +277,7 @@ TEST_CASE(DocumentLinkWithoutCompile) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
worker::QueryParams params;
|
||||
params.kind = worker::QueryKind::DocumentLink;
|
||||
params.path = "/tmp/nonexistent.cpp";
|
||||
@@ -299,7 +298,7 @@ TEST_CASE(InlayHintsWithoutCompile) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
worker::QueryParams params;
|
||||
params.kind = worker::QueryKind::InlayHints;
|
||||
params.path = "/tmp/nonexistent.cpp";
|
||||
@@ -330,7 +329,7 @@ TEST_CASE(MultipleSequentialRequests) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
// Compile first so feature requests return real data.
|
||||
worker::CompileParams cp;
|
||||
cp.path = src;
|
||||
@@ -402,7 +401,7 @@ TEST_CASE(MultipleDocuments) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
// Compile 3 different documents.
|
||||
for(int i = 0; i < 3; i++) {
|
||||
worker::CompileParams cp;
|
||||
@@ -440,7 +439,7 @@ TEST_CASE(EvictNotification) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
// Send an evict notification — worker should remove the document without crashing.
|
||||
worker::EvictParams ep;
|
||||
ep.path = "/tmp/evict_test.cpp";
|
||||
@@ -474,7 +473,7 @@ TEST_CASE(SpawnWithMemoryLimit) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
// Compile first.
|
||||
worker::CompileParams cp;
|
||||
cp.path = src;
|
||||
|
||||
@@ -2,17 +2,16 @@
|
||||
#include <vector>
|
||||
|
||||
#include "test/test.h"
|
||||
#include "eventide/serde/bincode/bincode.h"
|
||||
#include "eventide/serde/serde/raw_value.h"
|
||||
#include "server/protocol.h"
|
||||
#include "server/worker_test_helpers.h"
|
||||
|
||||
#include "kota/codec/bincode/bincode.h"
|
||||
#include "kota/codec/raw_value.h"
|
||||
|
||||
namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace et = eventide;
|
||||
|
||||
// ============================================================================
|
||||
// Bincode Serialization Tests
|
||||
// ============================================================================
|
||||
@@ -20,7 +19,7 @@ namespace et = eventide;
|
||||
TEST_SUITE(BincodeRoundTrip) {
|
||||
|
||||
TEST_CASE(CompileParamsRoundTrip) {
|
||||
namespace bincode = eventide::serde::bincode;
|
||||
namespace bincode = kota::codec::bincode;
|
||||
|
||||
worker::CompileParams params;
|
||||
params.path = "/tmp/test.cpp";
|
||||
@@ -47,7 +46,7 @@ TEST_CASE(CompileParamsRoundTrip) {
|
||||
}
|
||||
|
||||
TEST_CASE(CompileResultRoundTrip) {
|
||||
namespace bincode = eventide::serde::bincode;
|
||||
namespace bincode = kota::codec::bincode;
|
||||
|
||||
worker::CompileResult result;
|
||||
result.version = 1;
|
||||
@@ -92,7 +91,7 @@ TEST_CASE(BuildPCHRequest) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
worker::BuildParams params;
|
||||
params.kind = worker::BuildKind::BuildPCH;
|
||||
params.file = hdr;
|
||||
@@ -127,7 +126,7 @@ TEST_CASE(IndexRequest) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
worker::BuildParams params;
|
||||
params.kind = worker::BuildKind::Index;
|
||||
params.file = src;
|
||||
@@ -162,7 +161,7 @@ TEST_CASE(BuildPCMRequest) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
worker::BuildParams params;
|
||||
params.kind = worker::BuildKind::BuildPCM;
|
||||
params.file = src;
|
||||
@@ -196,7 +195,7 @@ TEST_CASE(CompletionRequest) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
worker::BuildParams params;
|
||||
params.kind = worker::BuildKind::Completion;
|
||||
params.file = src;
|
||||
@@ -226,7 +225,7 @@ TEST_CASE(SignatureHelpRequest) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
worker::BuildParams params;
|
||||
params.kind = worker::BuildKind::SignatureHelp;
|
||||
params.file = src;
|
||||
@@ -261,7 +260,7 @@ TEST_CASE(MultipleStatelessRequests) {
|
||||
|
||||
bool test_done = false;
|
||||
|
||||
w.run([&]() -> et::task<> {
|
||||
w.run([&]() -> kota::task<> {
|
||||
// Send multiple index requests to test stateless worker handles them sequentially.
|
||||
for(int i = 0; i < 3; i++) {
|
||||
worker::BuildParams params;
|
||||
|
||||
@@ -11,12 +11,14 @@
|
||||
#include "test/temp_dir.h"
|
||||
#include "command/argument_parser.h"
|
||||
#include "command/command.h"
|
||||
#include "eventide/async/async.h"
|
||||
#include "eventide/ipc/peer.h"
|
||||
#include "eventide/ipc/transport.h"
|
||||
#include "server/protocol.h"
|
||||
#include "support/filesystem.h"
|
||||
|
||||
#include "kota/async/async.h"
|
||||
#include "kota/ipc/codec/bincode.h"
|
||||
#include "kota/ipc/peer.h"
|
||||
#include "kota/ipc/transport.h"
|
||||
|
||||
namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
@@ -32,8 +34,6 @@ struct SigpipeGuard {
|
||||
|
||||
static SigpipeGuard sigpipe_guard;
|
||||
|
||||
namespace et = eventide;
|
||||
|
||||
/// Resolve path to the clice binary for spawning workers.
|
||||
inline std::string clice_binary() {
|
||||
auto res_dir = resource_dir();
|
||||
@@ -59,10 +59,10 @@ inline std::vector<std::string> make_args(const std::string& file_path,
|
||||
|
||||
/// Helper: spawn a worker process and return a BincodePeer connected to it.
|
||||
struct WorkerHandle {
|
||||
et::event_loop loop;
|
||||
et::process proc{};
|
||||
std::unique_ptr<et::ipc::StreamTransport> transport;
|
||||
std::unique_ptr<et::ipc::BincodePeer> peer;
|
||||
kota::event_loop loop;
|
||||
kota::process proc{};
|
||||
std::unique_ptr<kota::ipc::StreamTransport> transport;
|
||||
std::unique_ptr<kota::ipc::BincodePeer> peer;
|
||||
int stderr_fd = -1;
|
||||
|
||||
bool spawn(const std::string& mode, std::uint64_t memory_limit = 0) {
|
||||
@@ -74,7 +74,7 @@ struct WorkerHandle {
|
||||
stderr_fd = ::open(stderr_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
#endif
|
||||
|
||||
et::process::options opts;
|
||||
kota::process::options opts;
|
||||
opts.file = binary;
|
||||
opts.args = {binary, "--mode", mode};
|
||||
if(memory_limit > 0) {
|
||||
@@ -82,12 +82,13 @@ struct WorkerHandle {
|
||||
opts.args.push_back(std::to_string(memory_limit));
|
||||
}
|
||||
opts.streams = {
|
||||
et::process::stdio::pipe(true, false), // stdin: child reads
|
||||
et::process::stdio::pipe(false, true), // stdout: child writes
|
||||
stderr_fd >= 0 ? et::process::stdio::from_fd(stderr_fd) : et::process::stdio::ignore(),
|
||||
kota::process::stdio::pipe(true, false), // stdin: child reads
|
||||
kota::process::stdio::pipe(false, true), // stdout: child writes
|
||||
stderr_fd >= 0 ? kota::process::stdio::from_fd(stderr_fd)
|
||||
: kota::process::stdio::ignore(),
|
||||
};
|
||||
|
||||
auto result = et::process::spawn(opts, loop);
|
||||
auto result = kota::process::spawn(opts, loop);
|
||||
if(!result) {
|
||||
#ifndef _WIN32
|
||||
if(stderr_fd >= 0)
|
||||
@@ -97,9 +98,9 @@ struct WorkerHandle {
|
||||
}
|
||||
|
||||
auto& spawn = *result;
|
||||
transport = std::make_unique<et::ipc::StreamTransport>(std::move(spawn.stdout_pipe),
|
||||
std::move(spawn.stdin_pipe));
|
||||
peer = std::make_unique<et::ipc::BincodePeer>(loop, std::move(transport));
|
||||
transport = std::make_unique<kota::ipc::StreamTransport>(std::move(spawn.stdout_pipe),
|
||||
std::move(spawn.stdin_pipe));
|
||||
peer = std::make_unique<kota::ipc::BincodePeer>(loop, std::move(transport));
|
||||
proc = std::move(spawn.proc);
|
||||
#ifndef _WIN32
|
||||
if(stderr_fd >= 0)
|
||||
|
||||
@@ -83,5 +83,87 @@ int x = 1;
|
||||
}
|
||||
|
||||
}; // TEST_SUITE(SourceText)
|
||||
|
||||
TEST_SUITE(DirectiveArgument) {
|
||||
|
||||
void EXPECT_RANGE(llvm::StringRef content, std::uint32_t offset, llvm::StringRef expected) {
|
||||
auto result = find_directive_argument(content, offset);
|
||||
ASSERT_TRUE(result.has_value());
|
||||
ASSERT_EQ(content.substr(result->begin, result->length()), expected);
|
||||
}
|
||||
|
||||
void EXPECT_NONE(llvm::StringRef content, std::uint32_t offset) {
|
||||
auto result = find_directive_argument(content, offset);
|
||||
ASSERT_FALSE(result.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE(IncludeQuoted) {
|
||||
llvm::StringRef src = R"(#include "foo.h")";
|
||||
EXPECT_RANGE(src, 0, R"("foo.h")");
|
||||
}
|
||||
|
||||
TEST_CASE(IncludeAngled) {
|
||||
llvm::StringRef src = "#include <iostream>";
|
||||
EXPECT_RANGE(src, 0, "<iostream>");
|
||||
}
|
||||
|
||||
TEST_CASE(IncludeMacro) {
|
||||
llvm::StringRef src = "#include HEADER";
|
||||
EXPECT_RANGE(src, 0, "HEADER");
|
||||
}
|
||||
|
||||
TEST_CASE(HasIncludeQuoted) {
|
||||
llvm::StringRef src = R"(#if __has_include("foo.h"))";
|
||||
// offset at __has_include
|
||||
auto pos = src.find("__has_include");
|
||||
EXPECT_RANGE(src, static_cast<std::uint32_t>(pos), R"("foo.h")");
|
||||
}
|
||||
|
||||
TEST_CASE(HasIncludeAngled) {
|
||||
llvm::StringRef src = "#if __has_include(<vector>)";
|
||||
auto pos = src.find("__has_include");
|
||||
EXPECT_RANGE(src, static_cast<std::uint32_t>(pos), "<vector>");
|
||||
}
|
||||
|
||||
TEST_CASE(EmbedQuoted) {
|
||||
llvm::StringRef src = R"(#embed "data.bin")";
|
||||
EXPECT_RANGE(src, 0, R"("data.bin")");
|
||||
}
|
||||
|
||||
TEST_CASE(HasEmbedQuoted) {
|
||||
llvm::StringRef src = R"(#if __has_embed("data.bin"))";
|
||||
auto pos = src.find("__has_embed");
|
||||
EXPECT_RANGE(src, static_cast<std::uint32_t>(pos), R"("data.bin")");
|
||||
}
|
||||
|
||||
TEST_CASE(MultilineOffset) {
|
||||
llvm::StringRef src = "#include \"a.h\"\n#include \"b.h\"";
|
||||
// offset pointing into the second line
|
||||
auto pos = src.find("#include \"b.h\"");
|
||||
EXPECT_RANGE(src, static_cast<std::uint32_t>(pos), R"("b.h")");
|
||||
}
|
||||
|
||||
TEST_CASE(EmptyDirective) {
|
||||
llvm::StringRef src = "#include \n";
|
||||
EXPECT_NONE(src, 0);
|
||||
}
|
||||
|
||||
TEST_CASE(HasIncludeFromLineStart) {
|
||||
llvm::StringRef src = "#if __has_include(<vector>)";
|
||||
EXPECT_RANGE(src, 0, "<vector>");
|
||||
}
|
||||
|
||||
TEST_CASE(HasEmbedFromLineStart) {
|
||||
llvm::StringRef src = R"(#if __has_embed("data.bin"))";
|
||||
EXPECT_RANGE(src, 0, R"("data.bin")");
|
||||
}
|
||||
|
||||
TEST_CASE(IncludeNext) {
|
||||
llvm::StringRef src = "#include_next <stdlib.h>";
|
||||
EXPECT_RANGE(src, 0, "<stdlib.h>");
|
||||
}
|
||||
|
||||
}; // TEST_SUITE(DirectiveArgument)
|
||||
|
||||
} // namespace
|
||||
} // namespace clice::testing
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "test/platform.h"
|
||||
#include "eventide/zest/macro.h"
|
||||
#include "support/format.h"
|
||||
|
||||
#include "kota/zest/macro.h"
|
||||
|
||||
@@ -7,6 +7,44 @@
|
||||
|
||||
namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<std::string> base_cc1_args(llvm::StringRef standard) {
|
||||
return {
|
||||
"clang",
|
||||
"-cc1",
|
||||
"-triple",
|
||||
LLVM_DEFAULT_TARGET_TRIPLE,
|
||||
standard.str(),
|
||||
"-ffreestanding",
|
||||
"-undef",
|
||||
"-fms-extensions",
|
||||
"-fsyntax-only",
|
||||
"-x",
|
||||
"c++",
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Tester::~Tester() {
|
||||
for(auto& path: pcm_paths) {
|
||||
fs::remove(path);
|
||||
}
|
||||
}
|
||||
|
||||
bool Tester::try_compile() {
|
||||
auto built = clice::compile(params);
|
||||
if(!built.completed()) {
|
||||
for(auto& diag: built.diagnostics()) {
|
||||
LOG_ERROR("{}", diag.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
unit.emplace(std::move(built));
|
||||
return true;
|
||||
}
|
||||
|
||||
void Tester::prepare(llvm::StringRef standard) {
|
||||
params = CompilationParams();
|
||||
unit.reset();
|
||||
@@ -16,19 +54,7 @@ void Tester::prepare(llvm::StringRef standard) {
|
||||
vfs->add(file, source.content);
|
||||
}
|
||||
|
||||
owned_args.clear();
|
||||
// Use -cc1 mode directly to bypass the slow driver subprocess.
|
||||
owned_args.push_back("clang");
|
||||
owned_args.push_back("-cc1");
|
||||
owned_args.push_back("-triple");
|
||||
owned_args.push_back(LLVM_DEFAULT_TARGET_TRIPLE);
|
||||
owned_args.push_back(standard.str());
|
||||
owned_args.push_back("-ffreestanding");
|
||||
owned_args.push_back("-undef");
|
||||
owned_args.push_back("-fms-extensions");
|
||||
owned_args.push_back("-fsyntax-only");
|
||||
owned_args.push_back("-x");
|
||||
owned_args.push_back("c++");
|
||||
owned_args = base_cc1_args(standard);
|
||||
owned_args.push_back(TestVFS::path(src_path));
|
||||
|
||||
params.arguments.clear();
|
||||
@@ -42,17 +68,7 @@ void Tester::prepare(llvm::StringRef standard) {
|
||||
|
||||
bool Tester::compile(llvm::StringRef standard) {
|
||||
prepare(standard);
|
||||
|
||||
auto built = clice::compile(params);
|
||||
if(!built.completed()) {
|
||||
for(auto& diag: built.diagnostics()) {
|
||||
LOG_ERROR("{}", diag.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
unit.emplace(std::move(built));
|
||||
return true;
|
||||
return try_compile();
|
||||
}
|
||||
|
||||
bool Tester::compile_with_pch(llvm::StringRef standard) {
|
||||
@@ -64,7 +80,6 @@ bool Tester::compile_with_pch(llvm::StringRef standard) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use an overlay VFS so the PCH temp file on real disk is accessible.
|
||||
auto overlay =
|
||||
llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(llvm::vfs::getRealFileSystem());
|
||||
overlay->pushOverlay(vfs);
|
||||
@@ -96,16 +111,123 @@ bool Tester::compile_with_pch(llvm::StringRef standard) {
|
||||
params.pch = {info.path, static_cast<std::uint32_t>(info.preamble.size())};
|
||||
params.buffers.clear();
|
||||
|
||||
auto built = clice::compile(params);
|
||||
if(!built.completed()) {
|
||||
for(auto& diag: built.diagnostics()) {
|
||||
LOG_ERROR("{}", diag.message);
|
||||
return try_compile();
|
||||
}
|
||||
|
||||
bool Tester::compile_with_modules(llvm::StringRef standard) {
|
||||
std::vector<ModuleFile> all_modules = module_files;
|
||||
for(auto& [file, source]: sources.all_files) {
|
||||
if(file == src_path) {
|
||||
continue;
|
||||
}
|
||||
auto result = scan(source.content);
|
||||
if(!result.module_name.empty() || result.need_preprocess) {
|
||||
all_modules.push_back({file.str(), source.content});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
unit.emplace(std::move(built));
|
||||
return true;
|
||||
if(all_modules.empty()) {
|
||||
return compile(standard);
|
||||
}
|
||||
|
||||
vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
|
||||
for(auto& [file, source]: sources.all_files) {
|
||||
vfs->add(file, source.content);
|
||||
}
|
||||
for(auto& mod: module_files) {
|
||||
vfs->add(mod.filename, mod.content);
|
||||
}
|
||||
|
||||
struct ScannedModule {
|
||||
std::string filename;
|
||||
std::string content;
|
||||
std::string module_name;
|
||||
std::vector<std::string> deps;
|
||||
};
|
||||
|
||||
auto scan_args_base = base_cc1_args(standard);
|
||||
|
||||
std::vector<ScannedModule> modules;
|
||||
for(auto& mod: all_modules) {
|
||||
auto args = scan_args_base;
|
||||
args.push_back(TestVFS::path(mod.filename));
|
||||
|
||||
std::vector<const char*> argv;
|
||||
for(auto& arg: args) {
|
||||
argv.push_back(arg.c_str());
|
||||
}
|
||||
|
||||
auto result = scan_precise(argv, TestVFS::root(), {}, nullptr, vfs);
|
||||
modules.push_back(
|
||||
{mod.filename, mod.content, result.module_name, std::move(result.modules)});
|
||||
}
|
||||
|
||||
llvm::StringMap<std::size_t> name_to_index;
|
||||
for(std::size_t i = 0; i < modules.size(); ++i) {
|
||||
name_to_index[modules[i].module_name] = i;
|
||||
}
|
||||
|
||||
std::vector<std::size_t> order;
|
||||
std::vector<int> state(modules.size(), 0);
|
||||
|
||||
auto topo_visit = [&](this auto& self, std::size_t i) -> bool {
|
||||
if(state[i] == 2)
|
||||
return true;
|
||||
if(state[i] == 1) {
|
||||
LOG_ERROR("Circular module dependency involving {}", modules[i].module_name);
|
||||
return false;
|
||||
}
|
||||
state[i] = 1;
|
||||
for(auto& dep: modules[i].deps) {
|
||||
auto it = name_to_index.find(dep);
|
||||
if(it != name_to_index.end()) {
|
||||
if(!self(it->second))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
state[i] = 2;
|
||||
order.push_back(i);
|
||||
return true;
|
||||
};
|
||||
|
||||
for(std::size_t i = 0; i < modules.size(); ++i) {
|
||||
if(!topo_visit(i))
|
||||
return false;
|
||||
}
|
||||
|
||||
auto overlay =
|
||||
llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(llvm::vfs::getRealFileSystem());
|
||||
overlay->pushOverlay(vfs);
|
||||
|
||||
llvm::StringMap<std::string> built_pcms;
|
||||
for(auto idx: order) {
|
||||
auto& mod = modules[idx];
|
||||
|
||||
auto pcm_path = fs::createTemporaryFile("clice", "pcm");
|
||||
if(!pcm_path) {
|
||||
LOG_ERROR("{}", pcm_path.error().message());
|
||||
return false;
|
||||
}
|
||||
pcm_paths.push_back(*pcm_path);
|
||||
|
||||
Tester builder;
|
||||
builder.add_main(mod.filename, mod.content);
|
||||
builder.prepare(standard);
|
||||
builder.params.kind = CompilationKind::ModuleInterface;
|
||||
builder.params.output_file = *pcm_path;
|
||||
builder.params.vfs = overlay;
|
||||
builder.params.pcms = built_pcms;
|
||||
|
||||
if(!builder.try_compile())
|
||||
return false;
|
||||
|
||||
built_pcms.try_emplace(mod.module_name, *pcm_path);
|
||||
}
|
||||
|
||||
prepare(standard);
|
||||
params.vfs = overlay;
|
||||
params.pcms = std::move(built_pcms);
|
||||
return try_compile();
|
||||
}
|
||||
|
||||
std::uint32_t Tester::point(llvm::StringRef name, llvm::StringRef file) {
|
||||
@@ -166,13 +288,11 @@ void Tester::prepare_driver(llvm::StringRef standard) {
|
||||
|
||||
params.kind = CompilationKind::Content;
|
||||
|
||||
// Use overlay VFS: real FS (for system headers) + InMemoryFS (for test files).
|
||||
auto overlay =
|
||||
llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(llvm::vfs::getRealFileSystem());
|
||||
overlay->pushOverlay(vfs);
|
||||
params.vfs = overlay;
|
||||
|
||||
// Remap test files so clang sees our in-memory content.
|
||||
for(auto& [file, source]: sources.all_files) {
|
||||
if(file == src_path) {
|
||||
params.add_remapped_file(file, source.content);
|
||||
@@ -185,36 +305,11 @@ void Tester::prepare_driver(llvm::StringRef standard) {
|
||||
|
||||
bool Tester::compile_driver(llvm::StringRef standard) {
|
||||
prepare_driver(standard);
|
||||
|
||||
auto built = clice::compile(params);
|
||||
if(!built.completed()) {
|
||||
for(auto& diag: built.diagnostics()) {
|
||||
LOG_ERROR("{}", diag.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
unit.emplace(std::move(built));
|
||||
return true;
|
||||
return try_compile();
|
||||
}
|
||||
|
||||
bool Tester::compile_driver_with_pch(llvm::StringRef standard) {
|
||||
params = CompilationParams();
|
||||
unit.reset();
|
||||
vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
|
||||
for(auto& [file, source]: sources.all_files) {
|
||||
vfs->add(file, source.content);
|
||||
}
|
||||
|
||||
auto command = std::format("clang++ {} {} -fms-extensions", standard, src_path);
|
||||
database.add_command("fake", src_path, command);
|
||||
|
||||
CommandOptions options;
|
||||
options.query_toolchain = true;
|
||||
options.suppress_logging = true;
|
||||
auto commands = database.lookup(src_path, options);
|
||||
assert(!commands.empty() && "lookup failed after add_command");
|
||||
params.arguments = commands.front().to_argv();
|
||||
prepare_driver(standard);
|
||||
|
||||
auto pch_path = fs::createTemporaryFile("clice", "pch");
|
||||
if(!pch_path) {
|
||||
@@ -222,16 +317,12 @@ bool Tester::compile_driver_with_pch(llvm::StringRef standard) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use overlay VFS: real FS (for system headers + PCH temp) + InMemoryFS.
|
||||
auto overlay =
|
||||
llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(llvm::vfs::getRealFileSystem());
|
||||
overlay->pushOverlay(vfs);
|
||||
params.vfs = overlay;
|
||||
|
||||
// Phase 1: Build PCH from the preamble portion.
|
||||
params.kind = CompilationKind::Preamble;
|
||||
params.output_file = *pch_path;
|
||||
|
||||
// Clear buffers from prepare_driver() so we can re-add with preamble bound.
|
||||
params.buffers.clear();
|
||||
for(auto& [file, source]: sources.all_files) {
|
||||
if(file == src_path) {
|
||||
auto bound = compute_preamble_bound(source.content);
|
||||
@@ -259,25 +350,7 @@ bool Tester::compile_driver_with_pch(llvm::StringRef standard) {
|
||||
params.pch = {info.path, static_cast<std::uint32_t>(info.preamble.size())};
|
||||
params.buffers.clear();
|
||||
|
||||
for(auto& [file, source]: sources.all_files) {
|
||||
if(file == src_path) {
|
||||
params.add_remapped_file(file, source.content);
|
||||
} else {
|
||||
std::string path = path::is_absolute(file) ? file.str() : path::join(".", file);
|
||||
params.add_remapped_file(path, source.content);
|
||||
}
|
||||
}
|
||||
|
||||
auto built = clice::compile(params);
|
||||
if(!built.completed()) {
|
||||
for(auto& diag: built.diagnostics()) {
|
||||
LOG_ERROR("{}", diag.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
unit.emplace(std::move(built));
|
||||
return true;
|
||||
return try_compile();
|
||||
}
|
||||
|
||||
void Tester::clear() {
|
||||
@@ -288,6 +361,11 @@ void Tester::clear() {
|
||||
src_path.clear();
|
||||
owned_args.clear();
|
||||
vfs.reset();
|
||||
module_files.clear();
|
||||
for(auto& path: pcm_paths) {
|
||||
fs::remove(path);
|
||||
}
|
||||
pcm_paths.clear();
|
||||
}
|
||||
|
||||
} // namespace clice::testing
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "test/annotation.h"
|
||||
#include "test/test.h"
|
||||
@@ -25,6 +26,16 @@ struct Tester {
|
||||
/// The VFS used for compilation.
|
||||
llvm::IntrusiveRefCntPtr<TestVFS> vfs;
|
||||
|
||||
struct ModuleFile {
|
||||
std::string filename;
|
||||
std::string content;
|
||||
};
|
||||
|
||||
std::vector<ModuleFile> module_files;
|
||||
std::vector<std::string> pcm_paths;
|
||||
|
||||
~Tester();
|
||||
|
||||
void add_main(llvm::StringRef file, llvm::StringRef content) {
|
||||
src_path = file.str();
|
||||
sources.add_source(file, content);
|
||||
@@ -39,6 +50,10 @@ struct Tester {
|
||||
sources.add_sources(content);
|
||||
}
|
||||
|
||||
void add_module(llvm::StringRef filename, llvm::StringRef content) {
|
||||
module_files.push_back({filename.str(), content.str()});
|
||||
}
|
||||
|
||||
/// Fast VFS-only path: uses -cc1 directly, no system headers.
|
||||
void prepare(llvm::StringRef standard = "-std=c++20");
|
||||
|
||||
@@ -46,6 +61,8 @@ struct Tester {
|
||||
|
||||
bool compile_with_pch(llvm::StringRef standard = "-std=c++20");
|
||||
|
||||
bool compile_with_modules(llvm::StringRef standard = "-std=c++20");
|
||||
|
||||
/// Driver path: uses CompilationDatabase + toolchain cache, has system headers.
|
||||
void prepare_driver(llvm::StringRef standard = "-std=c++20");
|
||||
|
||||
@@ -53,6 +70,8 @@ struct Tester {
|
||||
|
||||
bool compile_driver_with_pch(llvm::StringRef standard = "-std=c++20");
|
||||
|
||||
bool try_compile();
|
||||
|
||||
std::uint32_t operator[](llvm::StringRef file, llvm::StringRef pos) {
|
||||
return sources.all_files.lookup(file).offsets.lookup(pos);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "eventide/deco/deco.h"
|
||||
#include "eventide/zest/zest.h"
|
||||
#include "support/logging.h"
|
||||
|
||||
#include "kota/deco/deco.h"
|
||||
#include "kota/zest/zest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using deco::decl::KVStyle;
|
||||
using kota::deco::decl::KVStyle;
|
||||
|
||||
struct TestOptions {
|
||||
DecoKV(style = KVStyle::JoinedOrSeparate,
|
||||
@@ -32,8 +33,8 @@ struct TestOptions {
|
||||
} // namespace
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
auto args = deco::util::argvify(argc, argv);
|
||||
auto parsed = deco::cli::parse<TestOptions>(args);
|
||||
auto args = kota::deco::util::argvify(argc, argv);
|
||||
auto parsed = kota::deco::cli::parse<TestOptions>(args);
|
||||
|
||||
std::string_view filter = {};
|
||||
if(parsed.has_value() && parsed->options.test_filter.has_value()) {
|
||||
@@ -57,5 +58,5 @@ int main(int argc, const char** argv) {
|
||||
|
||||
clice::logging::stderr_logger("test", clice::logging::options);
|
||||
|
||||
return eventide::zest::Runner::instance().run_tests(filter);
|
||||
return kota::zest::Runner::instance().run_tests(filter);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user