//===-- HeuristicResolverTests.cpp --------------------------*- C++ -*-----===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/Sema/HeuristicResolver.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Tooling.h" #include "gmock/gmock-matchers.h" #include "gtest/gtest.h" using namespace clang::ast_matchers; using testing::ElementsAre; namespace clang { namespace { // Helper for matching a sequence of elements with a variadic list of matchers. // Usage: `ElementsAre(matchAdapter(Vs, MatchFunction)...)`, where `Vs...` is // a variadic list of matchers. // For each `V` in `Vs`, this will match the corresponding element `E` if // `MatchFunction(V, E)` is true. MATCHER_P2(matchAdapter, MatcherForElement, MatchFunction, "matchAdapter") { return MatchFunction(MatcherForElement, arg); } template using ResolveFnT = std::function( const HeuristicResolver *, const InputNode *)>; // Test heuristic resolution on `Code` using the resolution procedure // `ResolveFn`, which takes a `HeuristicResolver` and an input AST node of type // `InputNode` and returns a `std::vector`. // `InputMatcher` should be an AST matcher that matches a single node to pass as // input to `ResolveFn`, bound to the ID "input". `OutputMatchers` should be AST // matchers that each match a single node, bound to the ID "output". template void expectResolution(llvm::StringRef Code, ResolveFnT ResolveFn, const InputMatcher &IM, const OutputMatchers &...OMS) { auto TU = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++20"}); auto &Ctx = TU->getASTContext(); auto InputMatches = match(IM, Ctx); ASSERT_EQ(1u, InputMatches.size()); const auto *Input = InputMatches[0].template getNodeAs("input"); ASSERT_TRUE(Input); auto OutputNodeMatches = [&](auto &OutputMatcher, auto &Actual) { auto OutputMatches = match(OutputMatcher, Ctx); if (OutputMatches.size() != 1u) return false; const auto *ExpectedOutput = OutputMatches[0].template getNodeAs("output"); if (!ExpectedOutput) return false; return ExpectedOutput == Actual; }; HeuristicResolver H(Ctx); auto Results = ResolveFn(&H, Input); EXPECT_THAT(Results, ElementsAre(matchAdapter(OMS, OutputNodeMatches)...)); } // Wrapper for the above that accepts a HeuristicResolver member function // pointer directly. template void expectResolution(llvm::StringRef Code, std::vector ( HeuristicResolver::*ResolveFn)(const InputNode *) const, const InputMatcher &IM, const OutputMatchers &...OMS) { expectResolution(Code, ResolveFnT(std::mem_fn(ResolveFn)), IM, OMS...); } TEST(HeuristicResolver, MemberExpr) { std::string Code = R"cpp( template struct S { void bar() {} }; template void foo(S arg) { arg.bar(); } )cpp"; // Test resolution of "bar" in "arg.bar()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("bar")).bind("input"), cxxMethodDecl(hasName("bar")).bind("output")); } TEST(HeuristicResolver, MemberExpr_Overloads) { std::string Code = R"cpp( template struct S { void bar(int); void bar(float); }; template void foo(S arg, U u) { arg.bar(u); } )cpp"; // Test resolution of "bar" in "arg.bar(u)". Both overloads should be found. expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("bar")).bind("input"), cxxMethodDecl(hasName("bar"), hasParameter(0, hasType(asString("int")))) .bind("output"), cxxMethodDecl(hasName("bar"), hasParameter(0, hasType(asString("float")))) .bind("output")); } TEST(HeuristicResolver, MemberExpr_SmartPointer) { std::string Code = R"cpp( template struct S { void foo() {} }; template struct unique_ptr { T* operator->(); }; template void test(unique_ptr>& v) { v->foo(); } )cpp"; // Test resolution of "foo" in "v->foo()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"), cxxMethodDecl(hasName("foo")).bind("output")); } TEST(HeuristicResolver, MemberExpr_SmartPointer_Qualified) { std::string Code = R"cpp( template struct Waldo { void find(); void find() const; }; template struct unique_ptr { T* operator->(); }; template void test(unique_ptr>& w) { w->find(); } )cpp"; expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("find")).bind("input"), cxxMethodDecl(hasName("find"), isConst()).bind("output")); } TEST(HeuristicResolver, MemberExpr_AutoTypeDeduction1) { std::string Code = R"cpp( template struct A { int waldo; }; template void foo(A a) { auto copy = a; copy.waldo; } )cpp"; expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("waldo")).bind("input"), fieldDecl(hasName("waldo")).bind("output")); } TEST(HeuristicResolver, MemberExpr_AutoTypeDeduction2) { std::string Code = R"cpp( struct B { int waldo; }; template struct A { B b; }; template void foo(A a) { auto b = a.b; b.waldo; } )cpp"; expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("waldo")).bind("input"), fieldDecl(hasName("waldo")).bind("output")); } TEST(HeuristicResolver, MemberExpr_Chained) { std::string Code = R"cpp( struct A { void foo() {} }; template struct B { A func(int); void bar() { func(1).foo(); } }; )cpp"; // Test resolution of "foo" in "func(1).foo()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"), cxxMethodDecl(hasName("foo")).bind("output")); } TEST(HeuristicResolver, MemberExpr_TemplateArgs) { std::string Code = R"cpp( struct Foo { static Foo k(int); template T convert(); }; template void test() { Foo::k(T()).template convert(); } )cpp"; // Test resolution of "convert" in "Foo::k(T()).template convert()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("convert")).bind("input"), functionTemplateDecl(hasName("convert")).bind("output")); } TEST(HeuristicResolver, MemberExpr_TypeAlias) { std::string Code = R"cpp( template struct Waldo { void find(); }; template using Wally = Waldo; template void foo(Wally w) { w.find(); } )cpp"; // Test resolution of "find" in "w.find()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("find")).bind("input"), cxxMethodDecl(hasName("find")).bind("output")); } TEST(HeuristicResolver, MemberExpr_BaseClass_TypeAlias) { std::string Code = R"cpp( template struct Waldo { void find(); }; template using Wally = Waldo; template struct S : Wally { void foo() { this->find(); } }; )cpp"; // Test resolution of "find" in "this->find()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("find")).bind("input"), cxxMethodDecl(hasName("find")).bind("output")); } TEST(HeuristicResolver, MemberExpr_Metafunction) { std::string Code = R"cpp( template struct Waldo { void find(); }; template struct MetaWaldo { using Type = Waldo; }; template void foo(typename MetaWaldo::Type w) { w.find(); } )cpp"; // Test resolution of "find" in "w.find()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("find")).bind("input"), cxxMethodDecl(hasName("find")).bind("output")); } TEST(HeuristicResolver, MemberExpr_Metafunction_Enumerator) { std::string Code = R"cpp( enum class State { Hidden }; template struct Meta { using Type = State; }; template void foo(typename Meta::Type t) { t.Hidden; } )cpp"; // Test resolution of "Hidden" in "t.Hidden". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("Hidden")).bind("input"), enumConstantDecl(hasName("Hidden")).bind("output")); } TEST(HeuristicResolver, MemberExpr_DeducedNonTypeTemplateParameter) { std::string Code = R"cpp( template struct Waldo { const int found = N; }; template int foo() { return W.found; } )cpp"; // Test resolution of "found" in "W.found". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("found")).bind("input"), fieldDecl(hasName("found")).bind("output")); } TEST(HeuristicResolver, DeclRefExpr_StaticMethod) { std::string Code = R"cpp( template struct S { static void bar() {} }; template void foo() { S::bar(); } )cpp"; // Test resolution of "bar" in "S::bar()". expectResolution( Code, &HeuristicResolver::resolveDeclRefExpr, dependentScopeDeclRefExpr(hasDependentName("bar")).bind("input"), cxxMethodDecl(hasName("bar")).bind("output")); } TEST(HeuristicResolver, DeclRefExpr_StaticOverloads) { std::string Code = R"cpp( template struct S { static void bar(int); static void bar(float); }; template void foo(U u) { S::bar(u); } )cpp"; // Test resolution of "bar" in "S::bar(u)". Both overloads should be found. expectResolution( Code, &HeuristicResolver::resolveDeclRefExpr, dependentScopeDeclRefExpr(hasDependentName("bar")).bind("input"), cxxMethodDecl(hasName("bar"), hasParameter(0, hasType(asString("int")))) .bind("output"), cxxMethodDecl(hasName("bar"), hasParameter(0, hasType(asString("float")))) .bind("output")); } TEST(HeuristicResolver, DeclRefExpr_Enumerator) { std::string Code = R"cpp( template struct Foo { enum class E { A, B }; E e = E::A; }; )cpp"; // Test resolution of "A" in "E::A". expectResolution( Code, &HeuristicResolver::resolveDeclRefExpr, dependentScopeDeclRefExpr(hasDependentName("A")).bind("input"), enumConstantDecl(hasName("A")).bind("output")); } TEST(HeuristicResolver, DeclRefExpr_RespectScope) { std::string Code = R"cpp( template struct PointerIntPair { void *getPointer() const { return Info::getPointer(); } }; )cpp"; // Test resolution of "getPointer" in "Info::getPointer()". // Here, we are testing that we do not incorrectly get the enclosing // getPointer() function as a result. expectResolution( Code, &HeuristicResolver::resolveDeclRefExpr, dependentScopeDeclRefExpr(hasDependentName("getPointer")).bind("input")); } TEST(HeuristicResolver, DeclRefExpr_Nested) { std::string Code = R"cpp( struct S { static int Waldo; }; template struct Meta { using Type = S; }; template void foo() { Meta::Type::Waldo; } )cpp"; // Test resolution of "Waldo" in "Meta::Type::Waldo". expectResolution( Code, &HeuristicResolver::resolveDeclRefExpr, dependentScopeDeclRefExpr(hasDependentName("Waldo")).bind("input"), varDecl(hasName("Waldo")).bind("output")); } TEST(HeuristicResolver, DependentNameType) { std::string Code = R"cpp( template struct A { struct B {}; }; template void foo(typename A::B); )cpp"; // Tests resolution of "B" in "A::B". expectResolution( Code, &HeuristicResolver::resolveDependentNameType, functionDecl(hasParameter(0, hasType(dependentNameType().bind("input")))), classTemplateDecl( has(cxxRecordDecl(has(cxxRecordDecl(hasName("B")).bind("output")))))); } TEST(HeuristicResolver, DependentNameType_Nested) { std::string Code = R"cpp( template struct A { struct B { struct C {}; }; }; template void foo(typename A::B::C); )cpp"; // Tests resolution of "C" in "A::B::C". expectResolution( Code, &HeuristicResolver::resolveDependentNameType, functionDecl(hasParameter(0, hasType(dependentNameType().bind("input")))), classTemplateDecl(has(cxxRecordDecl(has( cxxRecordDecl(has(cxxRecordDecl(hasName("C")).bind("output")))))))); } TEST(HeuristicResolver, DependentNameType_Recursion) { std::string Code = R"cpp( template struct Waldo { using Type = typename Waldo::Type::Next; }; )cpp"; // Test resolution of "Next" in "typename Waldo::Type::Next". // Here, we are testing that we do not get into an infinite recursion. expectResolution(Code, &HeuristicResolver::resolveDependentNameType, typeAliasDecl(hasType(dependentNameType().bind("input")))); } TEST(HeuristicResolver, DependentNameType_MutualRecursion) { std::string Code = R"cpp( template struct Odd; template struct Even { using Type = typename Odd::Type::Next; }; template struct Odd { using Type = typename Even::Type::Next; }; )cpp"; // Test resolution of "Next" in "typename Even::Type::Next". // Similar to the above but we have two mutually recursive templates. expectResolution( Code, &HeuristicResolver::resolveDependentNameType, classTemplateDecl(hasName("Odd"), has(cxxRecordDecl(has(typeAliasDecl( hasType(dependentNameType().bind("input")))))))); } TEST(HeuristicResolver, NestedNameSpecifier) { // Test resolution of "B" in "A::B::C". // Unlike the "C", the "B" does not get its own DependentNameTypeLoc node, // so the resolution uses the NestedNameSpecifier as input. std::string Code = R"cpp( template struct A { struct B { struct C {}; }; }; template void foo(typename A::B::C); )cpp"; // Adapt the call to resolveNestedNameSpecifierToType() to the interface // expected by expectResolution() (returning a vector of decls). ResolveFnT ResolveFn = [](const HeuristicResolver *H, const NestedNameSpecifier *NNS) -> std::vector { return {H->resolveNestedNameSpecifierToType(NNS)->getAsCXXRecordDecl()}; }; expectResolution(Code, ResolveFn, nestedNameSpecifier(hasPrefix(specifiesType(hasDeclaration( classTemplateDecl(hasName("A")))))) .bind("input"), classTemplateDecl(has(cxxRecordDecl( has(cxxRecordDecl(hasName("B")).bind("output")))))); } TEST(HeuristicResolver, TemplateSpecializationType) { std::string Code = R"cpp( template struct A { template struct B {}; }; template void foo(typename A::template B); )cpp"; // Test resolution of "B" in "A::template B". expectResolution(Code, &HeuristicResolver::resolveTemplateSpecializationType, functionDecl(hasParameter(0, hasType(type().bind("input")))), classTemplateDecl(has(cxxRecordDecl( has(classTemplateDecl(hasName("B")).bind("output")))))); } TEST(HeuristicResolver, DependentCall_NonMember) { std::string Code = R"cpp( template void nonmember(T); template void bar(T t) { nonmember(t); } )cpp"; // Test resolution of "nonmember" in "nonmember(t)". expectResolution(Code, &HeuristicResolver::resolveCalleeOfCallExpr, callExpr(callee(unresolvedLookupExpr(hasAnyDeclaration( functionTemplateDecl(hasName("nonmember")))))) .bind("input"), functionTemplateDecl(hasName("nonmember")).bind("output")); } TEST(HeuristicResolver, DependentCall_Member) { std::string Code = R"cpp( template struct A { void member(T); }; template void bar(A a, T t) { a.member(t); } )cpp"; // Test resolution of "member" in "a.member(t)". expectResolution( Code, &HeuristicResolver::resolveCalleeOfCallExpr, callExpr(callee(cxxDependentScopeMemberExpr(hasMemberName("member")))) .bind("input"), cxxMethodDecl(hasName("member")).bind("output")); } TEST(HeuristicResolver, DependentCall_StaticMember) { std::string Code = R"cpp( template struct A { static void static_member(T); }; template void bar(T t) { A::static_member(t); } )cpp"; // Test resolution of "static_member" in "A::static_member(t)". expectResolution(Code, &HeuristicResolver::resolveCalleeOfCallExpr, callExpr(callee(dependentScopeDeclRefExpr( hasDependentName("static_member")))) .bind("input"), cxxMethodDecl(hasName("static_member")).bind("output")); } TEST(HeuristicResolver, DependentCall_Overload) { std::string Code = R"cpp( void overload(int); void overload(double); template void bar(T t) { overload(t); } )cpp"; // Test resolution of "overload" in "overload(t)". Both overload should be // found. expectResolution(Code, &HeuristicResolver::resolveCalleeOfCallExpr, callExpr(callee(unresolvedLookupExpr(hasAnyDeclaration( functionDecl(hasName("overload")))))) .bind("input"), functionDecl(hasName("overload"), hasParameter(0, hasType(asString("double")))) .bind("output"), functionDecl(hasName("overload"), hasParameter(0, hasType(asString("int")))) .bind("output")); } TEST(HeuristicResolver, UsingValueDecl) { std::string Code = R"cpp( template struct Base { void waldo(); }; template struct Derived : Base { using Base::waldo; }; )cpp"; // Test resolution of "waldo" in "Base::waldo". expectResolution(Code, &HeuristicResolver::resolveUsingValueDecl, unresolvedUsingValueDecl(hasName("waldo")).bind("input"), cxxMethodDecl(hasName("waldo")).bind("output")); } } // namespace } // namespace clang