## Summary - **Restructure `src/server/` into subdirectories** (`service/`, `compiler/`, `worker/`, `workspace/`, `protocol/`) to separate concerns: transport/session management, compilation, worker orchestration, and persistent workspace state. - **Decouple MasterServer from transport**: MasterServer no longer holds a `JsonPeer&` reference or registers handlers itself. New `LSPClient` and `AgentClient` classes own their peer references and register protocol handlers, accessing MasterServer internals via `friend class`. - **Add agentic protocol**: A TCP-based side channel (`agentic/compileCommand`) that lets external tools (AI agents, build systems) query compile commands from a running clice server. Includes a CLI client mode (`--mode agentic --port N --path FILE`), server-side listener when `--port` is specified in pipe mode, and integration tests for happy path, fallback, concurrency, and connection-refused. - **Replace fire-and-forget `loop.schedule()` with `kota::task_group`**: Compiler compile tasks, Indexer background indexing + resource monitor, WorkerPool worker monitors, and socket accept loops now use structured concurrency. This eliminates manual `alive_count_`/generation counters and ensures all spawned tasks are joined on shutdown. - **Fix flaky integration test**: `CliceClient.initialize()` now always sets `cache_dir` to a workspace-local `.clice/` directory, preventing stale PCH artifacts from the global `~/.cache/clice/` from polluting test runs. ## Details **Compiler peer lifetime**: `Compiler` and `Indexer` previously took `JsonPeer&` in their constructors, coupling them to a single connection. They now store a `JsonPeer*` set via `set_peer()`, with null checks before sending diagnostics/progress. This supports the multi-connection model where agentic clients don't need diagnostics. **Socket mode single-LSP enforcement**: `accept_connections()` takes a `register_lsp` flag; when true, only the first connection gets an `LSPClient`. All connections get an `AgentClient`. This prevents multiple LSP sessions from racing on shared server state. **Structured shutdown**: `Compiler::stop()` cancels in-flight compile tasks and joins them. `WorkerPool::stop()` signals workers and joins the monitor task group. `Indexer` uses a `cancellation_source` to stop its resource monitor when a background indexing run completes. **Pin kotatsu**: Changed from `GIT_TAG main` + `GIT_SHALLOW TRUE` to an exact commit hash for reproducible builds. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
953 lines
22 KiB
C++
953 lines
22 KiB
C++
#include "test/test.h"
|
|
#include "test/tester.h"
|
|
|
|
#include "clang/AST/RecursiveASTVisitor.h"
|
|
|
|
namespace clice::testing {
|
|
|
|
namespace {
|
|
|
|
struct InputFinder : clang::RecursiveASTVisitor<InputFinder> {
|
|
CompilationUnitRef unit;
|
|
clang::QualType input;
|
|
clang::QualType expect;
|
|
|
|
using Base = clang::RecursiveASTVisitor<InputFinder>;
|
|
|
|
InputFinder(CompilationUnitRef unit) : unit(unit) {}
|
|
|
|
bool TraverseDecl(clang::Decl* decl) {
|
|
if(decl && (llvm::isa<clang::TranslationUnitDecl>(decl) ||
|
|
unit.file_id(decl->getLocation()) == unit.interested_file())) {
|
|
Base::TraverseDecl(decl);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VisitTypedefNameDecl(const clang::TypedefNameDecl* decl) {
|
|
if(decl->getName() == "input") {
|
|
input = decl->getUnderlyingType();
|
|
}
|
|
|
|
if(decl->getName() == "expect") {
|
|
expect = decl->getUnderlyingType();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
TEST_SUITE(TemplateResolver, Tester) {
|
|
|
|
void run(llvm::StringRef code) {
|
|
add_main("main.cpp", code);
|
|
ASSERT_TRUE(compile());
|
|
|
|
InputFinder finder(*unit);
|
|
finder.TraverseAST(unit->context());
|
|
|
|
auto input = unit->resolver().resolve(finder.input);
|
|
auto target = finder.expect;
|
|
ASSERT_FALSE(input.isNull() || target.isNull());
|
|
EXPECT_EQ(input.getCanonicalType(), target.getCanonicalType());
|
|
}
|
|
|
|
TEST_CASE(TypeParameterType) {
|
|
run(R"code(
|
|
template <typename T>
|
|
struct A {
|
|
using type = T;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename A<X>::type;
|
|
using expect = X;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(SingleLevel) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T>
|
|
struct A {
|
|
using type = type_list<T>;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename A<X>::type;
|
|
using expect = type_list<X>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(SingleLevelNotDependent) {
|
|
run(R"code(
|
|
template <typename T>
|
|
struct A {
|
|
using type = int;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename A<X>::type;
|
|
using expect = int;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(MultiLevel) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T1>
|
|
struct A {
|
|
using type = type_list<T1>;
|
|
};
|
|
|
|
template <typename T2>
|
|
struct B {
|
|
using type = typename A<T2>::type;
|
|
};
|
|
|
|
template <typename T3>
|
|
struct C {
|
|
using type = typename B<T3>::type;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename C<X>::type;
|
|
using expect = type_list<X>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(MultiLevelNotDependent) {
|
|
run(R"code(
|
|
template <typename T1>
|
|
struct A {
|
|
using type = int;
|
|
};
|
|
|
|
template <typename T2>
|
|
struct B {
|
|
using type = typename A<T2>::type;
|
|
};
|
|
|
|
template <typename T3>
|
|
struct C {
|
|
using type = typename B<T3>::type;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename C<X>::type;
|
|
using expect = int;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(ArgumentDependent) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T1>
|
|
struct A {
|
|
using type = T1;
|
|
};
|
|
|
|
template <typename T2>
|
|
struct B {
|
|
using type = type_list<T2>;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename B<typename A<X>::type>::type;
|
|
using expect = type_list<X>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(AliasArgument) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T1>
|
|
struct A {
|
|
using type = T1;
|
|
};
|
|
|
|
template <typename T2>
|
|
struct B {
|
|
using base = A<T2>;
|
|
using type = type_list<typename base::type>;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename B<X>::type;
|
|
using expect = type_list<X>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(AliasDependent) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T1>
|
|
struct A {
|
|
using type = type_list<T1>;
|
|
};
|
|
|
|
template <typename T2>
|
|
struct B {
|
|
using base = A<T2>;
|
|
using type = typename base::type;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename B<X>::type;
|
|
using expect = type_list<X>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(AliasTemplate) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T1, typename U1>
|
|
struct A {
|
|
using type = type_list<T1, U1>;
|
|
};
|
|
|
|
template <typename T2>
|
|
struct B {
|
|
template <typename U2>
|
|
using type = typename A<T2, U2>::type;
|
|
};
|
|
|
|
template <typename X, typename Y>
|
|
struct test {
|
|
using input = typename B<X>::template type<Y>;
|
|
using expect = type_list<X, Y>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(BaseDependent) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T1>
|
|
struct A {
|
|
using type = type_list<T1>;
|
|
};
|
|
|
|
template <typename U2>
|
|
struct B : A<U2> {};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename B<X>::type;
|
|
using expect = type_list<X>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(MultiNested) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T1>
|
|
struct A {
|
|
using self = A<T1>;
|
|
using type = type_list<T1>;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename A<X>::self::self::self::self::self::type;
|
|
using expect = type_list<X>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(OuterDependentMemberClass) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T1>
|
|
struct A {
|
|
template <typename T2>
|
|
struct B {
|
|
template <typename T3>
|
|
struct C {
|
|
using type = type_list<T1, T2, T3>;
|
|
};
|
|
};
|
|
};
|
|
|
|
template <typename X, typename Y, typename Z>
|
|
struct test {
|
|
using input = typename A<X>::template B<Y>::template C<Z>::type;
|
|
using expect = type_list<X, Y, Z>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(InnerDependentMemberClass) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T>
|
|
struct test {
|
|
template <int N, typename U>
|
|
struct B {
|
|
using type = type_list<U, T>;
|
|
};
|
|
|
|
using input = typename B<1, T>::type;
|
|
using expect = type_list<T, T>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(InnerPartialMember) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T, typename U>
|
|
struct test {};
|
|
|
|
template <typename T>
|
|
struct test<T, T> {
|
|
template <int N, typename U>
|
|
struct A {
|
|
using type = type_list<U, T>;
|
|
};
|
|
|
|
using input = typename A<1, T>::type;
|
|
using expect = type_list<T, T>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(PartialSpecialization) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T1>
|
|
struct A {};
|
|
|
|
template <typename U2>
|
|
struct B {};
|
|
|
|
template <typename U2, template <typename...> typename HKT>
|
|
struct B<HKT<U2>> {
|
|
using type = type_list<U2>;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename B<A<X>>::type;
|
|
using expect = type_list<X>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(PartialDefaultArgument) {
|
|
run(R"code(
|
|
template <typename T, typename U = T>
|
|
struct X {};
|
|
|
|
template <typename T>
|
|
struct X<T, T> {
|
|
using type = T;
|
|
};
|
|
|
|
template <typename T>
|
|
struct test {
|
|
using input = typename X<T>::type;
|
|
using expect = T;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(DefaultArgument) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T1>
|
|
struct A {
|
|
using type = type_list<T1>;
|
|
};
|
|
|
|
template <typename U1, typename U2 = A<U1>>
|
|
struct B {
|
|
using type = typename U2::type;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename B<X>::type;
|
|
using expect = type_list<X>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(PackExpansion) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename U, typename... Us>
|
|
struct X {
|
|
using type = type_list<Us...>;
|
|
};
|
|
|
|
template <typename... Ts>
|
|
struct test {
|
|
using input = typename X<int, Ts...>::type;
|
|
using expect = type_list<Ts...>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(BasePackExpansion) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename U, typename... Us>
|
|
struct X {
|
|
using type = type_list<Us...>;
|
|
};
|
|
|
|
template <typename... Us>
|
|
struct Y : X<int, Us...> {};
|
|
|
|
template <typename... Ts>
|
|
struct test {
|
|
using input = typename Y<Ts...>::type;
|
|
using expect = type_list<Ts...>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(RecursiveBaseClass) {
|
|
// Regression test: callback_traits<F> inherits callback_traits<decltype(&F::operator())>,
|
|
// creating infinite recursion through lookupInBases. CTD cycle detection must bail out.
|
|
// We set input = expect because the resolver cannot fully resolve this pattern;
|
|
// the test verifies it doesn't crash or hang.
|
|
run(R"code(
|
|
template <typename F>
|
|
struct callback_traits : callback_traits<decltype(&F::operator())> {};
|
|
|
|
template <typename R, typename C, typename... Args>
|
|
struct callback_traits<R (C::*)(Args...) const> {
|
|
using result_type = R;
|
|
};
|
|
|
|
template <typename F>
|
|
struct test {
|
|
using input = typename callback_traits<F>::result_type;
|
|
using expect = typename callback_traits<F>::result_type;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(PointerType) {
|
|
run(R"code(
|
|
template <typename T>
|
|
struct A {
|
|
using type = T*;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename A<X>::type;
|
|
using expect = X*;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(ReferenceType) {
|
|
run(R"code(
|
|
template <typename T>
|
|
struct A {
|
|
using type = T&;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename A<X>::type;
|
|
using expect = X&;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(ConstQualified) {
|
|
run(R"code(
|
|
template <typename T>
|
|
struct A {
|
|
using type = const T;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename A<X>::type;
|
|
using expect = const X;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
// TODO: Outer<int> is non-dependent, TransformNestedNameSpecifierLoc
|
|
// doesn't trigger our heuristic lookup for non-dependent qualifiers.
|
|
// TEST_CASE(NestedClassTemplate) { ... }
|
|
|
|
TEST_CASE(MultipleInheritance) {
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T>
|
|
struct Base1 {
|
|
using type1 = type_list<T>;
|
|
};
|
|
|
|
template <typename T>
|
|
struct Base2 {
|
|
using type2 = T;
|
|
};
|
|
|
|
template <typename T>
|
|
struct Derived : Base1<T>, Base2<T> {};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename Derived<X>::type1;
|
|
using expect = type_list<X>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(SecondBaseInheritance) {
|
|
run(R"code(
|
|
template <typename T>
|
|
struct Base1 {
|
|
using type1 = int;
|
|
};
|
|
|
|
template <typename T>
|
|
struct Base2 {
|
|
using type2 = T;
|
|
};
|
|
|
|
template <typename T>
|
|
struct Derived : Base1<T>, Base2<T> {};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename Derived<X>::type2;
|
|
using expect = X;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(TypedefChain) {
|
|
// Deep typedef chain that SubstituteOnly must expand
|
|
run(R"code(
|
|
template <typename T>
|
|
struct A {
|
|
using step1 = T;
|
|
using step2 = step1;
|
|
using step3 = step2;
|
|
using type = step3;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename A<X>::type;
|
|
using expect = X;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(DependentBaseTypedef) {
|
|
// Base class type depends on template parameter through alias
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T>
|
|
struct Base {
|
|
using value_type = T;
|
|
};
|
|
|
|
template <typename T>
|
|
struct Derived {
|
|
using base = Base<T>;
|
|
using type = typename base::value_type;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename Derived<X>::type;
|
|
using expect = X;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(CRTPPattern) {
|
|
// Common CRTP pattern
|
|
run(R"code(
|
|
template <typename Derived>
|
|
struct Base {
|
|
using derived_type = Derived;
|
|
};
|
|
|
|
template <typename T>
|
|
struct Impl : Base<Impl<T>> {
|
|
using type = T;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename Impl<X>::type;
|
|
using expect = X;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
// TODO: NTTP partial specialization matching not yet supported.
|
|
// checkTemplateArguments only fills default TemplateTypeParmDecl args.
|
|
// TEST_CASE(NonTypeTemplateParam) { ... }
|
|
|
|
TEST_CASE(IdentityAlias) {
|
|
// Alias template that forwards type unchanged
|
|
run(R"code(
|
|
template <typename T>
|
|
using identity = T;
|
|
|
|
template <typename T>
|
|
struct A {
|
|
using type = identity<T>;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename A<X>::type;
|
|
using expect = X;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(ConditionalType) {
|
|
// Partial specialization as conditional
|
|
run(R"code(
|
|
template <bool B, typename T, typename F>
|
|
struct conditional {
|
|
using type = T;
|
|
};
|
|
|
|
template <typename T, typename F>
|
|
struct conditional<false, T, F> {
|
|
using type = F;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename conditional<true, X, int>::type;
|
|
using expect = X;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
// TODO: Same as NonTypeTemplateParam — partial specialization on `false`
|
|
// requires NTTP matching which is not yet supported.
|
|
// TEST_CASE(ConditionalTypeFalse) { ... }
|
|
|
|
// TODO: Template template parameter deduction not yet supported.
|
|
// TEST_CASE(TemplateTemplateParam) { ... }
|
|
|
|
TEST_CASE(DependentReturnType) {
|
|
// Resolve through a struct that wraps a function return type pattern
|
|
run(R"code(
|
|
template <typename T>
|
|
struct remove_reference {
|
|
using type = T;
|
|
};
|
|
|
|
template <typename T>
|
|
struct remove_reference<T&> {
|
|
using type = T;
|
|
};
|
|
|
|
template <typename T>
|
|
struct remove_reference<T&&> {
|
|
using type = T;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename remove_reference<X&>::type;
|
|
using expect = X;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(RvalueRefRemoval) {
|
|
run(R"code(
|
|
template <typename T>
|
|
struct remove_reference {
|
|
using type = T;
|
|
};
|
|
|
|
template <typename T>
|
|
struct remove_reference<T&> {
|
|
using type = T;
|
|
};
|
|
|
|
template <typename T>
|
|
struct remove_reference<T&&> {
|
|
using type = T;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename remove_reference<X&&>::type;
|
|
using expect = X;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(AddPointer) {
|
|
run(R"code(
|
|
template <typename T>
|
|
struct add_pointer {
|
|
using type = T*;
|
|
};
|
|
|
|
template <typename T>
|
|
struct add_pointer<T&> {
|
|
using type = T*;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename add_pointer<X&>::type;
|
|
using expect = X*;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
// TODO: enable_if<true, X> requires NTTP partial specialization matching.
|
|
// TEST_CASE(EnableIfLike) { ... }
|
|
|
|
TEST_CASE(NestedLookup) {
|
|
// Two levels of dependent lookup: A<T>::B<T>::type
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T>
|
|
struct A {
|
|
template <typename U>
|
|
struct B {
|
|
using type = type_list<T, U>;
|
|
};
|
|
};
|
|
|
|
template <typename X, typename Y>
|
|
struct test {
|
|
using input = typename A<X>::template B<Y>::type;
|
|
using expect = type_list<X, Y>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(IndirectBaseClass) {
|
|
// Member found through two levels of inheritance
|
|
run(R"code(
|
|
template <typename T>
|
|
struct GrandBase {
|
|
using type = T;
|
|
};
|
|
|
|
template <typename T>
|
|
struct Middle : GrandBase<T> {};
|
|
|
|
template <typename T>
|
|
struct Top : Middle<T> {};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename Top<X>::type;
|
|
using expect = X;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(SelfReferentialAlias) {
|
|
// Type alias that refers back to the same class (like iterator::self)
|
|
run(R"code(
|
|
template <typename T>
|
|
struct Wrapper {
|
|
using self = Wrapper<T>;
|
|
using type = T;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename Wrapper<X>::self::self::type;
|
|
using expect = X;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(VoidSpecialization) {
|
|
run(R"code(
|
|
template <typename T>
|
|
struct A {
|
|
using type = T;
|
|
};
|
|
|
|
template <>
|
|
struct A<void> {
|
|
using type = int;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename A<X>::type;
|
|
using expect = X;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(DependentSizedArray) {
|
|
run(R"code(
|
|
template <typename T>
|
|
struct A {
|
|
using type = T;
|
|
using pointer = type*;
|
|
};
|
|
|
|
template <typename X>
|
|
struct test {
|
|
using input = typename A<X>::pointer;
|
|
using expect = X*;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(MultiplePacks) {
|
|
// Two separate pack parameters
|
|
run(R"code(
|
|
template <typename... Ts>
|
|
struct type_list {};
|
|
|
|
template <typename T, typename... Us>
|
|
struct A {
|
|
using type = type_list<T, Us...>;
|
|
};
|
|
|
|
template <typename X, typename... Ys>
|
|
struct test {
|
|
using input = typename A<X, Ys...>::type;
|
|
using expect = type_list<X, Ys...>;
|
|
};
|
|
)code");
|
|
}
|
|
|
|
TEST_CASE(StandardMap) {
|
|
add_main("main.cpp", R"code(
|
|
#include <map>
|
|
|
|
template <typename K, typename V>
|
|
struct test {
|
|
using input = typename std::map<K, V>::mapped_type;
|
|
using expect = V;
|
|
};
|
|
)code");
|
|
ASSERT_TRUE(compile_driver());
|
|
|
|
InputFinder finder(*unit);
|
|
finder.TraverseAST(unit->context());
|
|
|
|
auto input = unit->resolver().resolve(finder.input);
|
|
auto target = finder.expect;
|
|
ASSERT_FALSE(input.isNull() || target.isNull());
|
|
EXPECT_EQ(input.getCanonicalType(), target.getCanonicalType());
|
|
}
|
|
|
|
TEST_CASE(StandardString) {
|
|
add_main("main.cpp", R"code(
|
|
#include <string>
|
|
|
|
template <typename T>
|
|
struct test {
|
|
using input = typename std::basic_string<T>::value_type;
|
|
using expect = T;
|
|
};
|
|
)code");
|
|
ASSERT_TRUE(compile_driver());
|
|
|
|
InputFinder finder(*unit);
|
|
finder.TraverseAST(unit->context());
|
|
|
|
auto input = unit->resolver().resolve(finder.input);
|
|
auto target = finder.expect;
|
|
ASSERT_FALSE(input.isNull() || target.isNull());
|
|
EXPECT_EQ(input.getCanonicalType(), target.getCanonicalType());
|
|
}
|
|
|
|
TEST_CASE(Standard) {
|
|
add_main("main.cpp", R"code(
|
|
#include <vector>
|
|
|
|
template <typename T>
|
|
struct test {
|
|
using input = typename std::vector<T>::reference;
|
|
using expect = T&;
|
|
};
|
|
)code");
|
|
ASSERT_TRUE(compile_driver());
|
|
|
|
InputFinder finder(*unit);
|
|
finder.TraverseAST(unit->context());
|
|
|
|
auto input = unit->resolver().resolve(finder.input);
|
|
auto target = finder.expect;
|
|
ASSERT_FALSE(input.isNull() || target.isNull());
|
|
EXPECT_EQ(input.getCanonicalType(), target.getCanonicalType());
|
|
};
|
|
|
|
}; // TEST_SUITE(TemplateResolver)
|
|
|
|
} // namespace
|
|
|
|
} // namespace clice::testing
|