#include "test/test.h" #include "test/tester.h" #include "clang/AST/RecursiveASTVisitor.h" namespace clice::testing { namespace { struct InputFinder : clang::RecursiveASTVisitor { CompilationUnitRef unit; clang::QualType input; clang::QualType expect; using Base = clang::RecursiveASTVisitor; InputFinder(CompilationUnitRef unit) : unit(unit) {} bool TraverseDecl(clang::Decl* decl) { if(decl && (llvm::isa(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 struct A { using type = T; }; template struct test { using input = typename A::type; using expect = X; }; )code"); } TEST_CASE(SingleLevel) { run(R"code( template struct type_list {}; template struct A { using type = type_list; }; template struct test { using input = typename A::type; using expect = type_list; }; )code"); } TEST_CASE(SingleLevelNotDependent) { run(R"code( template struct A { using type = int; }; template struct test { using input = typename A::type; using expect = int; }; )code"); } TEST_CASE(MultiLevel) { run(R"code( template struct type_list {}; template struct A { using type = type_list; }; template struct B { using type = typename A::type; }; template struct C { using type = typename B::type; }; template struct test { using input = typename C::type; using expect = type_list; }; )code"); } TEST_CASE(MultiLevelNotDependent) { run(R"code( template struct A { using type = int; }; template struct B { using type = typename A::type; }; template struct C { using type = typename B::type; }; template struct test { using input = typename C::type; using expect = int; }; )code"); } TEST_CASE(ArgumentDependent) { run(R"code( template struct type_list {}; template struct A { using type = T1; }; template struct B { using type = type_list; }; template struct test { using input = typename B::type>::type; using expect = type_list; }; )code"); } TEST_CASE(AliasArgument) { run(R"code( template struct type_list {}; template struct A { using type = T1; }; template struct B { using base = A; using type = type_list; }; template struct test { using input = typename B::type; using expect = type_list; }; )code"); } TEST_CASE(AliasDependent) { run(R"code( template struct type_list {}; template struct A { using type = type_list; }; template struct B { using base = A; using type = typename base::type; }; template struct test { using input = typename B::type; using expect = type_list; }; )code"); } TEST_CASE(AliasTemplate) { run(R"code( template struct type_list {}; template struct A { using type = type_list; }; template struct B { template using type = typename A::type; }; template struct test { using input = typename B::template type; using expect = type_list; }; )code"); } TEST_CASE(BaseDependent) { run(R"code( template struct type_list {}; template struct A { using type = type_list; }; template struct B : A {}; template struct test { using input = typename B::type; using expect = type_list; }; )code"); } TEST_CASE(MultiNested) { run(R"code( template struct type_list {}; template struct A { using self = A; using type = type_list; }; template struct test { using input = typename A::self::self::self::self::self::type; using expect = type_list; }; )code"); } TEST_CASE(OuterDependentMemberClass) { run(R"code( template struct type_list {}; template struct A { template struct B { template struct C { using type = type_list; }; }; }; template struct test { using input = typename A::template B::template C::type; using expect = type_list; }; )code"); } TEST_CASE(InnerDependentMemberClass) { run(R"code( template struct type_list {}; template struct test { template struct B { using type = type_list; }; using input = typename B<1, T>::type; using expect = type_list; }; )code"); } TEST_CASE(InnerPartialMember) { run(R"code( template struct type_list {}; template struct test {}; template struct test { template struct A { using type = type_list; }; using input = typename A<1, T>::type; using expect = type_list; }; )code"); } TEST_CASE(PartialSpecialization) { run(R"code( template struct type_list {}; template struct A {}; template struct B {}; template typename HKT> struct B> { using type = type_list; }; template struct test { using input = typename B>::type; using expect = type_list; }; )code"); } TEST_CASE(PartialDefaultArgument) { run(R"code( template struct X {}; template struct X { using type = T; }; template struct test { using input = typename X::type; using expect = T; }; )code"); } TEST_CASE(DefaultArgument) { run(R"code( template struct type_list {}; template struct A { using type = type_list; }; template > struct B { using type = typename U2::type; }; template struct test { using input = typename B::type; using expect = type_list; }; )code"); } TEST_CASE(PackExpansion) { run(R"code( template struct type_list {}; template struct X { using type = type_list; }; template struct test { using input = typename X::type; using expect = type_list; }; )code"); } TEST_CASE(BasePackExpansion) { run(R"code( template struct type_list {}; template struct X { using type = type_list; }; template struct Y : X {}; template struct test { using input = typename Y::type; using expect = type_list; }; )code"); } // --- Robustness tests for edge cases found during stress testing --- TEST_CASE(RecursiveBaseClass) { // Regression test: callback_traits inherits callback_traits, // 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 struct callback_traits : callback_traits {}; template struct callback_traits { using result_type = R; }; template struct test { using input = typename callback_traits::result_type; using expect = typename callback_traits::result_type; }; )code"); } TEST_CASE(PointerType) { run(R"code( template struct A { using type = T*; }; template struct test { using input = typename A::type; using expect = X*; }; )code"); } TEST_CASE(ReferenceType) { run(R"code( template struct A { using type = T&; }; template struct test { using input = typename A::type; using expect = X&; }; )code"); } TEST_CASE(ConstQualified) { run(R"code( template struct A { using type = const T; }; template struct test { using input = typename A::type; using expect = const X; }; )code"); } // TODO: Outer is non-dependent, TransformNestedNameSpecifierLoc // doesn't trigger our heuristic lookup for non-dependent qualifiers. // TEST_CASE(NestedClassTemplate) { ... } TEST_CASE(MultipleInheritance) { run(R"code( template struct type_list {}; template struct Base1 { using type1 = type_list; }; template struct Base2 { using type2 = T; }; template struct Derived : Base1, Base2 {}; template struct test { using input = typename Derived::type1; using expect = type_list; }; )code"); } TEST_CASE(SecondBaseInheritance) { run(R"code( template struct Base1 { using type1 = int; }; template struct Base2 { using type2 = T; }; template struct Derived : Base1, Base2 {}; template struct test { using input = typename Derived::type2; using expect = X; }; )code"); } TEST_CASE(TypedefChain) { // Deep typedef chain that SubstituteOnly must expand run(R"code( template struct A { using step1 = T; using step2 = step1; using step3 = step2; using type = step3; }; template struct test { using input = typename A::type; using expect = X; }; )code"); } TEST_CASE(DependentBaseTypedef) { // Base class type depends on template parameter through alias run(R"code( template struct type_list {}; template struct Base { using value_type = T; }; template struct Derived { using base = Base; using type = typename base::value_type; }; template struct test { using input = typename Derived::type; using expect = X; }; )code"); } TEST_CASE(CRTPPattern) { // Common CRTP pattern run(R"code( template struct Base { using derived_type = Derived; }; template struct Impl : Base> { using type = T; }; template struct test { using input = typename Impl::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 using identity = T; template struct A { using type = identity; }; template struct test { using input = typename A::type; using expect = X; }; )code"); } TEST_CASE(ConditionalType) { // Partial specialization as conditional run(R"code( template struct conditional { using type = T; }; template struct conditional { using type = F; }; template struct test { using input = typename conditional::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 struct remove_reference { using type = T; }; template struct remove_reference { using type = T; }; template struct remove_reference { using type = T; }; template struct test { using input = typename remove_reference::type; using expect = X; }; )code"); } TEST_CASE(RvalueRefRemoval) { run(R"code( template struct remove_reference { using type = T; }; template struct remove_reference { using type = T; }; template struct remove_reference { using type = T; }; template struct test { using input = typename remove_reference::type; using expect = X; }; )code"); } TEST_CASE(AddPointer) { run(R"code( template struct add_pointer { using type = T*; }; template struct add_pointer { using type = T*; }; template struct test { using input = typename add_pointer::type; using expect = X*; }; )code"); } // TODO: enable_if requires NTTP partial specialization matching. // TEST_CASE(EnableIfLike) { ... } TEST_CASE(NestedLookup) { // Two levels of dependent lookup: A::B::type run(R"code( template struct type_list {}; template struct A { template struct B { using type = type_list; }; }; template struct test { using input = typename A::template B::type; using expect = type_list; }; )code"); } TEST_CASE(IndirectBaseClass) { // Member found through two levels of inheritance run(R"code( template struct GrandBase { using type = T; }; template struct Middle : GrandBase {}; template struct Top : Middle {}; template struct test { using input = typename Top::type; using expect = X; }; )code"); } TEST_CASE(SelfReferentialAlias) { // Type alias that refers back to the same class (like iterator::self) run(R"code( template struct Wrapper { using self = Wrapper; using type = T; }; template struct test { using input = typename Wrapper::self::self::type; using expect = X; }; )code"); } TEST_CASE(VoidSpecialization) { run(R"code( template struct A { using type = T; }; template <> struct A { using type = int; }; template struct test { using input = typename A::type; using expect = X; }; )code"); } TEST_CASE(DependentSizedArray) { run(R"code( template struct A { using type = T; using pointer = type*; }; template struct test { using input = typename A::pointer; using expect = X*; }; )code"); } TEST_CASE(MultiplePacks) { // Two separate pack parameters run(R"code( template struct type_list {}; template struct A { using type = type_list; }; template struct test { using input = typename A::type; using expect = type_list; }; )code"); } TEST_CASE(StandardMap) { add_main("main.cpp", R"code( #include template struct test { using input = typename std::map::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 template struct test { using input = typename std::basic_string::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 template struct test { using input = typename std::vector::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