================ @@ -0,0 +1,540 @@ +//===-- 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 "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 clang::clangd::HeuristicResolver; +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 <typename InputNode> +using ResolveFnT = std::function<std::vector<const NamedDecl *>( + 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<const NamedDecl *>`. +// `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 <typename InputNode, typename InputMatcher, typename... OutputMatchers> +void expectResolution(llvm::StringRef Code, ResolveFnT<InputNode> 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<InputNode>("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<NamedDecl>("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 <typename InputNode, typename InputMatcher, typename... OutputMatchers> +void expectResolution(llvm::StringRef Code, + std::vector<const NamedDecl *> ( + HeuristicResolver::*ResolveFn)(const InputNode *) + const, + const InputMatcher &IM, const OutputMatchers &...OMS) { + expectResolution(Code, ResolveFnT<InputNode>(std::mem_fn(ResolveFn)), IM, + OMS...); +} + +TEST(HeuristicResolver, MemberExpr) { + std::string Code = R"cpp( + template <typename T> + struct S { + void bar() {} + }; + + template <typename T> + void foo(S<T> 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 <typename T> + struct S { + void bar(int); + void bar(float); + }; + + template <typename T, typename U> + void foo(S<T> 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 <typename> struct S { void foo() {} }; + template <typename T> struct unique_ptr { + T* operator->(); + }; + template <typename T> + void test(unique_ptr<S<T>>& 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_Chained) { + std::string Code = R"cpp( + struct A { void foo() {} }; + template <typename T> + 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 <typename T> T convert(); + }; + template <typename T> + void test() { + Foo::k(T()).template convert<T>(); + } + )cpp"; + // Test resolution of "convert" in "Foo::k(T()).template convert<T>()". + expectResolution( + Code, &HeuristicResolver::resolveMemberExpr, + cxxDependentScopeMemberExpr(hasMemberName("convert")).bind("input"), + functionTemplateDecl(hasName("convert")).bind("output")); +} + +TEST(HeuristicResolver, MemberExpr_TypeAlias) { + std::string Code = R"cpp( + template <typename T> + struct Waldo { + void find(); + }; + template <typename T> + using Wally = Waldo<T>; + template <typename T> + void foo(Wally<T> 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 <typename T> + struct Waldo { + void find(); + }; + template <typename T> + using Wally = Waldo<T>; + template <typename T> + struct S : Wally<T> { + 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 <typename T> + struct Waldo { + void find(); + }; + template <typename T> + struct MetaWaldo { + using Type = Waldo<T>; + }; + template <typename T> + void foo(typename MetaWaldo<T>::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_DeducedNonTypeTemplateParameter) { + std::string Code = R"cpp( + template <int N> + struct Waldo { + const int found = N; + }; + template <Waldo W> + 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 <typename T> + struct S { + static void bar() {} + }; + + template <typename T> + void foo() { + S<T>::bar(); + } + )cpp"; + // Test resolution of "bar" in "S<T>::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 <typename T> + struct S { + static void bar(int); + static void bar(float); + }; + + template <typename T, typename U> + void foo(U u) { + S<T>::bar(u); + } + )cpp"; + // Test resolution of "bar" in "S<T>::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 <typename T> + 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 <typename Info> + 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, DependentNameType) { + std::string Code = R"cpp( + template <typename> + struct A { + struct B {}; + }; + template <typename T> + void foo(typename A<T>::B); + )cpp"; + // Tests resolution of "B" in "A<T>::B". + expectResolution(Code, &HeuristicResolver::resolveDependentNameType, + functionDecl(hasParameter(0, hasType(type().bind("input")))), ---------------- HighCommander4 wrote:
Thanks for the suggestion! I made this change here and in other `DependentNameType` test cases. https://github.com/llvm/llvm-project/pull/121313 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits