//===-- FindTargetTests.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 "FindTarget.h" #include "Selection.h" #include "TestTU.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclTemplate.h" #include "clang/Basic/SourceLocation.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Testing/Annotations/Annotations.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include namespace clang { namespace clangd { namespace { // A referenced Decl together with its DeclRelationSet, for assertions. // // There's no great way to assert on the "content" of a Decl in the general case // that's both expressive and unambiguous (e.g. clearly distinguishes between // templated decls and their specializations). // // We use the result of pretty-printing the decl, with the {body} truncated. struct PrintedDecl { PrintedDecl(const char *Name, DeclRelationSet Relations = {}) : Name(Name), Relations(Relations) {} PrintedDecl(const NamedDecl *D, DeclRelationSet Relations = {}) : Relations(Relations) { std::string S; llvm::raw_string_ostream OS(S); D->print(OS); llvm::StringRef FirstLine = llvm::StringRef(OS.str()).take_until([](char C) { return C == '\n'; }); FirstLine = FirstLine.rtrim(" {"); Name = std::string(FirstLine.rtrim(" {")); } std::string Name; DeclRelationSet Relations; }; bool operator==(const PrintedDecl &L, const PrintedDecl &R) { return std::tie(L.Name, L.Relations) == std::tie(R.Name, R.Relations); } llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const PrintedDecl &D) { return OS << D.Name << " Rel=" << D.Relations; } // The test cases in for targetDecl() take the form // - a piece of code (Code = "...") // - Code should have a single AST node marked as a [[range]] // - an EXPECT_DECLS() assertion that verify the type of node selected, and // all the decls that targetDecl() considers it to reference // Despite the name, these cases actually test allTargetDecls() for brevity. class TargetDeclTest : public ::testing::Test { protected: using Rel = DeclRelation; std::string Code; std::vector Flags; // Asserts that `Code` has a marked selection of a node `NodeType`, // and returns allTargetDecls() as PrintedDecl structs. // Use via EXPECT_DECLS(). std::vector assertNodeAndPrintDecls(const char *NodeType) { llvm::Annotations A(Code); auto TU = TestTU::withCode(A.code()); TU.ExtraArgs = Flags; auto AST = TU.build(); llvm::Annotations::Range R = A.range(); auto Selection = SelectionTree::createRight( AST.getASTContext(), AST.getTokens(), R.Begin, R.End); const SelectionTree::Node *N = Selection.commonAncestor(); if (!N) { ADD_FAILURE() << "No node selected!\n" << Code; return {}; } EXPECT_EQ(N->kind(), NodeType) << Selection; std::vector ActualDecls; for (const auto &Entry : allTargetDecls(N->ASTNode, AST.getHeuristicResolver())) ActualDecls.emplace_back(Entry.first, Entry.second); return ActualDecls; } }; // This is a macro to preserve line numbers in assertion failures. // It takes the expected decls as varargs to work around comma-in-macro issues. #define EXPECT_DECLS(NodeType, ...) \ EXPECT_THAT(assertNodeAndPrintDecls(NodeType), \ ::testing::UnorderedElementsAreArray( \ std::vector({__VA_ARGS__}))) \ << Code using ExpectedDecls = std::vector; TEST_F(TargetDeclTest, Exprs) { Code = R"cpp( int f(); int x = [[f]](); )cpp"; EXPECT_DECLS("DeclRefExpr", "int f()"); Code = R"cpp( struct S { S operator+(S) const; }; auto X = S() [[+]] S(); )cpp"; EXPECT_DECLS("DeclRefExpr", "S operator+(S) const"); Code = R"cpp( int foo(); int s = foo[[()]]; )cpp"; EXPECT_DECLS("CallExpr", "int foo()"); Code = R"cpp( struct X { void operator()(int n); }; void test() { X x; x[[(123)]]; } )cpp"; EXPECT_DECLS("CXXOperatorCallExpr", "void operator()(int n)"); Code = R"cpp( void test() { goto [[label]]; label: return; } )cpp"; EXPECT_DECLS("GotoStmt", "label:"); Code = R"cpp( void test() { [[label]]: return; } )cpp"; EXPECT_DECLS("LabelStmt", "label:"); } TEST_F(TargetDeclTest, RecoveryForC) { Flags = {"-xc", "-Xclang", "-frecovery-ast"}; Code = R"cpp( // error-ok: testing behavior on broken code // int f(); int f(int); int x = [[f]](); )cpp"; EXPECT_DECLS("DeclRefExpr", "int f(int)"); } TEST_F(TargetDeclTest, Recovery) { Code = R"cpp( // error-ok: testing behavior on broken code int f(); int f(int, int); int x = [[f]](42); )cpp"; EXPECT_DECLS("UnresolvedLookupExpr", "int f()", "int f(int, int)"); } TEST_F(TargetDeclTest, RecoveryType) { Code = R"cpp( // error-ok: testing behavior on broken code struct S { int member; }; S overloaded(int); void foo() { // No overload matches, but we have recovery-expr with the correct type. overloaded().[[member]]; } )cpp"; EXPECT_DECLS("MemberExpr", "int member"); } TEST_F(TargetDeclTest, UsingDecl) { Code = R"cpp( namespace foo { int f(int); int f(char); } using foo::f; int x = [[f]](42); )cpp"; // f(char) is not referenced! EXPECT_DECLS("DeclRefExpr", {"using foo::f", Rel::Alias}, {"int f(int)"}); Code = R"cpp( namespace foo { int f(int); int f(char); } [[using foo::f]]; )cpp"; // All overloads are referenced. EXPECT_DECLS("UsingDecl", {"using foo::f", Rel::Alias}, {"int f(int)"}, {"int f(char)"}); Code = R"cpp( struct X { int foo(); }; struct Y : X { using X::foo; }; int x = Y().[[foo]](); )cpp"; EXPECT_DECLS("MemberExpr", {"using X::foo", Rel::Alias}, {"int foo()"}); Code = R"cpp( template struct Base { void waldo() {} }; template struct Derived : Base { using Base::[[waldo]]; }; )cpp"; EXPECT_DECLS("UnresolvedUsingValueDecl", {"using Base::waldo", Rel::Alias}, {"void waldo()"}); Code = R"cpp( namespace ns { template class S {}; } using ns::S; template using A = [[S]]; )cpp"; EXPECT_DECLS("TemplateSpecializationTypeLoc", {"using ns::S", Rel::Alias}, {"template class S"}, {"class S", Rel::TemplatePattern}); Code = R"cpp( namespace ns { template class S {}; } using ns::S; template