| //===-- 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/Support/Annotations.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include <initializer_list> |
| |
| 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<std::string> Flags; |
| |
| // Asserts that `Code` has a marked selection of a node `NodeType`, |
| // and returns allTargetDecls() as PrintedDecl structs. |
| // Use via EXPECT_DECLS(). |
| std::vector<PrintedDecl> 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<PrintedDecl> 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<PrintedDecl>({__VA_ARGS__}))) \ |
| << Code |
| using ExpectedDecls = std::vector<PrintedDecl>; |
| |
| 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 <typename T> |
| struct Base { |
| void waldo() {} |
| }; |
| template <typename T> |
| struct Derived : Base<T> { |
| using Base<T>::[[waldo]]; |
| }; |
| )cpp"; |
| EXPECT_DECLS("UnresolvedUsingValueDecl", {"using Base<T>::waldo", Rel::Alias}, |
| {"void waldo()"}); |
| } |
| |
| TEST_F(TargetDeclTest, BaseSpecifier) { |
| Code = R"cpp( |
| struct X {}; |
| struct Y : [[private]] X {}; |
| )cpp"; |
| EXPECT_DECLS("CXXBaseSpecifier", "struct X"); |
| Code = R"cpp( |
| struct X {}; |
| struct Y : [[private X]] {}; |
| )cpp"; |
| EXPECT_DECLS("CXXBaseSpecifier", "struct X"); |
| Code = R"cpp( |
| struct X {}; |
| struct Y : private [[X]] {}; |
| )cpp"; |
| EXPECT_DECLS("RecordTypeLoc", "struct X"); |
| } |
| |
| TEST_F(TargetDeclTest, ConstructorInitList) { |
| Code = R"cpp( |
| struct X { |
| int a; |
| X() : [[a]](42) {} |
| }; |
| )cpp"; |
| EXPECT_DECLS("CXXCtorInitializer", "int a"); |
| |
| Code = R"cpp( |
| struct X { |
| X() : [[X]](1) {} |
| X(int); |
| }; |
| )cpp"; |
| EXPECT_DECLS("RecordTypeLoc", "struct X"); |
| } |
| |
| TEST_F(TargetDeclTest, DesignatedInit) { |
| Flags = {"-xc"}; // array designators are a C99 extension. |
| Code = R"c( |
| struct X { int a; }; |
| struct Y { int b; struct X c[2]; }; |
| struct Y y = { .c[0].[[a]] = 1 }; |
| )c"; |
| EXPECT_DECLS("DesignatedInitExpr", "int a"); |
| } |
| |
| TEST_F(TargetDeclTest, NestedNameSpecifier) { |
| Code = R"cpp( |
| namespace a { namespace b { int c; } } |
| int x = a::[[b::]]c; |
| )cpp"; |
| EXPECT_DECLS("NestedNameSpecifierLoc", "namespace b"); |
| |
| Code = R"cpp( |
| namespace a { struct X { enum { y }; }; } |
| int x = a::[[X::]]y; |
| )cpp"; |
| EXPECT_DECLS("NestedNameSpecifierLoc", "struct X"); |
| |
| Code = R"cpp( |
| template <typename T> |
| int x = [[T::]]y; |
| )cpp"; |
| EXPECT_DECLS("NestedNameSpecifierLoc", "typename T"); |
| |
| Code = R"cpp( |
| namespace a { int x; } |
| namespace b = a; |
| int y = [[b]]::x; |
| )cpp"; |
| EXPECT_DECLS("NestedNameSpecifierLoc", {"namespace b = a", Rel::Alias}, |
| {"namespace a", Rel::Underlying}); |
| } |
| |
| TEST_F(TargetDeclTest, Types) { |
| Code = R"cpp( |
| struct X{}; |
| [[X]] x; |
| )cpp"; |
| EXPECT_DECLS("RecordTypeLoc", "struct X"); |
| |
| Code = R"cpp( |
| struct S{}; |
| typedef S X; |
| [[X]] x; |
| )cpp"; |
| EXPECT_DECLS("TypedefTypeLoc", {"typedef S X", Rel::Alias}, |
| {"struct S", Rel::Underlying}); |
| Code = R"cpp( |
| namespace ns { struct S{}; } |
| typedef ns::S X; |
| [[X]] x; |
| )cpp"; |
| EXPECT_DECLS("TypedefTypeLoc", {"typedef ns::S X", Rel::Alias}, |
| {"struct S", Rel::Underlying}); |
| |
| Code = R"cpp( |
| template<class T> |
| void foo() { [[T]] x; } |
| )cpp"; |
| EXPECT_DECLS("TemplateTypeParmTypeLoc", "class T"); |
| Flags.clear(); |
| |
| Code = R"cpp( |
| template<template<typename> class T> |
| void foo() { [[T<int>]] x; } |
| )cpp"; |
| EXPECT_DECLS("TemplateSpecializationTypeLoc", "template <typename> class T"); |
| Flags.clear(); |
| |
| Code = R"cpp( |
| template<template<typename> class ...T> |
| class C { |
| C<[[T...]]> foo; |
| }; |
| )cpp"; |
| EXPECT_DECLS("TemplateArgumentLoc", {"template <typename> class ...T"}); |
| Flags.clear(); |
| |
| Code = R"cpp( |
| struct S{}; |
| S X; |
| [[decltype]](X) Y; |
| )cpp"; |
| EXPECT_DECLS("DecltypeTypeLoc", {"struct S", Rel::Underlying}); |
| |
| Code = R"cpp( |
| struct S{}; |
| [[auto]] X = S{}; |
| )cpp"; |
| // FIXME: deduced type missing in AST. https://llvm.org/PR42914 |
| EXPECT_DECLS("AutoTypeLoc"); |
| |
| Code = R"cpp( |
| template <typename... E> |
| struct S { |
| static const int size = sizeof...([[E]]); |
| }; |
| )cpp"; |
| EXPECT_DECLS("SizeOfPackExpr", "typename ...E"); |
| |
| Code = R"cpp( |
| template <typename T> |
| class Foo { |
| void f([[Foo]] x); |
| }; |
| )cpp"; |
| EXPECT_DECLS("InjectedClassNameTypeLoc", "class Foo"); |
| } |
| |
| TEST_F(TargetDeclTest, ClassTemplate) { |
| Code = R"cpp( |
| // Implicit specialization. |
| template<int x> class Foo{}; |
| [[Foo<42>]] B; |
| )cpp"; |
| EXPECT_DECLS("TemplateSpecializationTypeLoc", |
| {"template<> class Foo<42>", Rel::TemplateInstantiation}, |
| {"class Foo", Rel::TemplatePattern}); |
| |
| Code = R"cpp( |
| template<typename T> class Foo {}; |
| // The "Foo<int>" SpecializationDecl is incomplete, there is no |
| // instantiation happening. |
| void func([[Foo<int>]] *); |
| )cpp"; |
| EXPECT_DECLS("TemplateSpecializationTypeLoc", |
| {"class Foo", Rel::TemplatePattern}, |
| {"template<> class Foo<int>", Rel::TemplateInstantiation}); |
| |
| Code = R"cpp( |
| // Explicit specialization. |
| template<int x> class Foo{}; |
| template<> class Foo<42>{}; |
| [[Foo<42>]] B; |
| )cpp"; |
| EXPECT_DECLS("TemplateSpecializationTypeLoc", "template<> class Foo<42>"); |
| |
| Code = R"cpp( |
| // Partial specialization. |
| template<typename T> class Foo{}; |
| template<typename T> class Foo<T*>{}; |
| [[Foo<int*>]] B; |
| )cpp"; |
| EXPECT_DECLS("TemplateSpecializationTypeLoc", |
| {"template<> class Foo<int *>", Rel::TemplateInstantiation}, |
| {"template <typename T> class Foo<T *>", Rel::TemplatePattern}); |
| |
| Code = R"cpp( |
| // Template template argument. |
| template<typename T> struct Vector {}; |
| template <template <typename> class Container> |
| struct A {}; |
| A<[[Vector]]> a; |
| )cpp"; |
| EXPECT_DECLS("TemplateArgumentLoc", {"template <typename T> struct Vector"}); |
| |
| Flags.push_back("-std=c++17"); // for CTAD tests |
| |
| Code = R"cpp( |
| // Class template argument deduction |
| template <typename T> |
| struct Test { |
| Test(T); |
| }; |
| void foo() { |
| [[Test]] a(5); |
| } |
| )cpp"; |
| EXPECT_DECLS("DeducedTemplateSpecializationTypeLoc", |
| {"struct Test", Rel::TemplatePattern}); |
| |
| Code = R"cpp( |
| // Deduction guide |
| template <typename T> |
| struct Test { |
| template <typename I> |
| Test(I, I); |
| }; |
| template <typename I> |
| [[Test]](I, I) -> Test<typename I::type>; |
| )cpp"; |
| EXPECT_DECLS("CXXDeductionGuideDecl", {"template <typename T> struct Test"}); |
| } |
| |
| TEST_F(TargetDeclTest, Concept) { |
| Flags.push_back("-std=c++20"); |
| |
| // FIXME: Should we truncate the pretty-printed form of a concept decl |
| // somewhere? |
| |
| Code = R"cpp( |
| template <typename T> |
| concept Fooable = requires (T t) { t.foo(); }; |
| |
| template <typename T> requires [[Fooable]]<T> |
| void bar(T t) { |
| t.foo(); |
| } |
| )cpp"; |
| EXPECT_DECLS( |
| "ConceptSpecializationExpr", |
| {"template <typename T> concept Fooable = requires (T t) { t.foo(); }"}); |
| |
| // trailing requires clause |
| Code = R"cpp( |
| template <typename T> |
| concept Fooable = true; |
| |
| template <typename T> |
| void foo() requires [[Fooable]]<T>; |
| )cpp"; |
| EXPECT_DECLS("ConceptSpecializationExpr", |
| {"template <typename T> concept Fooable = true"}); |
| |
| // constrained-parameter |
| Code = R"cpp( |
| template <typename T> |
| concept Fooable = true; |
| |
| template <[[Fooable]] T> |
| void bar(T t); |
| )cpp"; |
| EXPECT_DECLS("ConceptSpecializationExpr", |
| {"template <typename T> concept Fooable = true"}); |
| |
| // partial-concept-id |
| Code = R"cpp( |
| template <typename T, typename U> |
| concept Fooable = true; |
| |
| template <[[Fooable]]<int> T> |
| void bar(T t); |
| )cpp"; |
| EXPECT_DECLS("ConceptSpecializationExpr", |
| {"template <typename T, typename U> concept Fooable = true"}); |
| } |
| |
| TEST_F(TargetDeclTest, FunctionTemplate) { |
| Code = R"cpp( |
| // Implicit specialization. |
| template<typename T> bool foo(T) { return false; }; |
| bool x = [[foo]](42); |
| )cpp"; |
| EXPECT_DECLS("DeclRefExpr", |
| {"template<> bool foo<int>(int)", Rel::TemplateInstantiation}, |
| {"bool foo(T)", Rel::TemplatePattern}); |
| |
| Code = R"cpp( |
| // Explicit specialization. |
| template<typename T> bool foo(T) { return false; }; |
| template<> bool foo<int>(int) { return false; }; |
| bool x = [[foo]](42); |
| )cpp"; |
| EXPECT_DECLS("DeclRefExpr", "template<> bool foo<int>(int)"); |
| } |
| |
| TEST_F(TargetDeclTest, VariableTemplate) { |
| // Pretty-printer doesn't do a very good job of variable templates :-( |
| Code = R"cpp( |
| // Implicit specialization. |
| template<typename T> int foo; |
| int x = [[foo]]<char>; |
| )cpp"; |
| EXPECT_DECLS("DeclRefExpr", {"int foo", Rel::TemplateInstantiation}, |
| {"int foo", Rel::TemplatePattern}); |
| |
| Code = R"cpp( |
| // Explicit specialization. |
| template<typename T> int foo; |
| template <> bool foo<char>; |
| int x = [[foo]]<char>; |
| )cpp"; |
| EXPECT_DECLS("DeclRefExpr", "bool foo"); |
| |
| Code = R"cpp( |
| // Partial specialization. |
| template<typename T> int foo; |
| template<typename T> bool foo<T*>; |
| bool x = [[foo]]<char*>; |
| )cpp"; |
| EXPECT_DECLS("DeclRefExpr", {"bool foo", Rel::TemplateInstantiation}, |
| {"bool foo", Rel::TemplatePattern}); |
| } |
| |
| TEST_F(TargetDeclTest, TypeAliasTemplate) { |
| Code = R"cpp( |
| template<typename T, int X> class SmallVector {}; |
| template<typename U> using TinyVector = SmallVector<U, 1>; |
| [[TinyVector<int>]] X; |
| )cpp"; |
| EXPECT_DECLS("TemplateSpecializationTypeLoc", |
| {"template<> class SmallVector<int, 1>", |
| Rel::TemplateInstantiation | Rel::Underlying}, |
| {"class SmallVector", Rel::TemplatePattern | Rel::Underlying}, |
| {"using TinyVector = SmallVector<U, 1>", |
| Rel::Alias | Rel::TemplatePattern}); |
| } |
| |
| TEST_F(TargetDeclTest, MemberOfTemplate) { |
| Code = R"cpp( |
| template <typename T> struct Foo { |
| int x(T); |
| }; |
| int y = Foo<int>().[[x]](42); |
| )cpp"; |
| EXPECT_DECLS("MemberExpr", {"int x(int)", Rel::TemplateInstantiation}, |
| {"int x(T)", Rel::TemplatePattern}); |
| |
| Code = R"cpp( |
| template <typename T> struct Foo { |
| template <typename U> |
| int x(T, U); |
| }; |
| int y = Foo<char>().[[x]]('c', 42); |
| )cpp"; |
| EXPECT_DECLS("MemberExpr", |
| {"template<> int x<int>(char, int)", Rel::TemplateInstantiation}, |
| {"int x(T, U)", Rel::TemplatePattern}); |
| } |
| |
| TEST_F(TargetDeclTest, Lambda) { |
| Code = R"cpp( |
| void foo(int x = 42) { |
| auto l = [ [[x]] ]{ return x + 1; }; |
| }; |
| )cpp"; |
| EXPECT_DECLS("DeclRefExpr", "int x = 42"); |
| |
| // It seems like this should refer to another var, with the outer param being |
| // an underlying decl. But it doesn't seem to exist. |
| Code = R"cpp( |
| void foo(int x = 42) { |
| auto l = [x]{ return [[x]] + 1; }; |
| }; |
| )cpp"; |
| EXPECT_DECLS("DeclRefExpr", "int x = 42"); |
| |
| Code = R"cpp( |
| void foo() { |
| auto l = [x = 1]{ return [[x]] + 1; }; |
| }; |
| )cpp"; |
| // FIXME: why both auto and int? |
| EXPECT_DECLS("DeclRefExpr", "auto int x = 1"); |
| } |
| |
| TEST_F(TargetDeclTest, OverloadExpr) { |
| Flags.push_back("--target=x86_64-pc-linux-gnu"); |
| |
| Code = R"cpp( |
| void func(int*); |
| void func(char*); |
| |
| template <class T> |
| void foo(T t) { |
| [[func]](t); |
| }; |
| )cpp"; |
| EXPECT_DECLS("UnresolvedLookupExpr", "void func(int *)", "void func(char *)"); |
| |
| Code = R"cpp( |
| struct X { |
| void func(int*); |
| void func(char*); |
| }; |
| |
| template <class T> |
| void foo(X x, T t) { |
| x.[[func]](t); |
| }; |
| )cpp"; |
| EXPECT_DECLS("UnresolvedMemberExpr", "void func(int *)", "void func(char *)"); |
| |
| Code = R"cpp( |
| struct X { |
| static void *operator new(unsigned long); |
| }; |
| auto* k = [[new]] X(); |
| )cpp"; |
| EXPECT_DECLS("CXXNewExpr", "static void *operator new(unsigned long)"); |
| Code = R"cpp( |
| void *operator new(unsigned long); |
| auto* k = [[new]] int(); |
| )cpp"; |
| EXPECT_DECLS("CXXNewExpr", "void *operator new(unsigned long)"); |
| |
| Code = R"cpp( |
| struct X { |
| static void operator delete(void *) noexcept; |
| }; |
| void k(X* x) { |
| [[delete]] x; |
| } |
| )cpp"; |
| EXPECT_DECLS("CXXDeleteExpr", "static void operator delete(void *) noexcept"); |
| Code = R"cpp( |
| void operator delete(void *) noexcept; |
| void k(int* x) { |
| [[delete]] x; |
| } |
| )cpp"; |
| EXPECT_DECLS("CXXDeleteExpr", "void operator delete(void *) noexcept"); |
| } |
| |
| TEST_F(TargetDeclTest, DependentExprs) { |
| // Heuristic resolution of method of dependent field |
| Code = R"cpp( |
| struct A { void foo() {} }; |
| template <typename T> |
| struct B { |
| A a; |
| void bar() { |
| this->a.[[foo]](); |
| } |
| }; |
| )cpp"; |
| EXPECT_DECLS("CXXDependentScopeMemberExpr", "void foo()"); |
| |
| // Similar to above but base expression involves a function call. |
| Code = R"cpp( |
| struct A { |
| void foo() {} |
| }; |
| struct B { |
| A getA(); |
| }; |
| template <typename T> |
| struct C { |
| B c; |
| void bar() { |
| this->c.getA().[[foo]](); |
| } |
| }; |
| )cpp"; |
| EXPECT_DECLS("CXXDependentScopeMemberExpr", "void foo()"); |
| |
| // Similar to above but uses a function pointer. |
| Code = R"cpp( |
| struct A { |
| void foo() {} |
| }; |
| struct B { |
| using FPtr = A(*)(); |
| FPtr fptr; |
| }; |
| template <typename T> |
| struct C { |
| B c; |
| void bar() { |
| this->c.fptr().[[foo]](); |
| } |
| }; |
| )cpp"; |
| EXPECT_DECLS("CXXDependentScopeMemberExpr", "void foo()"); |
| |
| // Base expression involves a member access into this. |
| Code = R"cpp( |
| struct Bar { |
| int aaaa; |
| }; |
| template <typename T> struct Foo { |
| Bar func(int); |
| void test() { |
| func(1).[[aaaa]]; |
| } |
| }; |
| )cpp"; |
| EXPECT_DECLS("CXXDependentScopeMemberExpr", "int aaaa"); |
| |
| Code = R"cpp( |
| class Foo { |
| public: |
| static Foo k(int); |
| template <typename T> T convert() const; |
| }; |
| template <typename T> |
| void test() { |
| Foo::k(T()).template [[convert]]<T>(); |
| } |
| )cpp"; |
| EXPECT_DECLS("CXXDependentScopeMemberExpr", |
| "template <typename T> T convert() const"); |
| } |
| |
| TEST_F(TargetDeclTest, DependentTypes) { |
| // Heuristic resolution of dependent type name |
| Code = R"cpp( |
| template <typename> |
| struct A { struct B {}; }; |
| |
| template <typename T> |
| void foo(typename A<T>::[[B]]); |
| )cpp"; |
| EXPECT_DECLS("DependentNameTypeLoc", "struct B"); |
| |
| // Heuristic resolution of dependent type name which doesn't get a TypeLoc |
| Code = R"cpp( |
| template <typename> |
| struct A { struct B { struct C {}; }; }; |
| |
| template <typename T> |
| void foo(typename A<T>::[[B]]::C); |
| )cpp"; |
| EXPECT_DECLS("NestedNameSpecifierLoc", "struct B"); |
| |
| // Heuristic resolution of dependent type name whose qualifier is also |
| // dependent |
| Code = R"cpp( |
| template <typename> |
| struct A { struct B { struct C {}; }; }; |
| |
| template <typename T> |
| void foo(typename A<T>::B::[[C]]); |
| )cpp"; |
| EXPECT_DECLS("DependentNameTypeLoc", "struct C"); |
| |
| // Heuristic resolution of dependent template name |
| Code = R"cpp( |
| template <typename> |
| struct A { |
| template <typename> struct B {}; |
| }; |
| |
| template <typename T> |
| void foo(typename A<T>::template [[B]]<int>); |
| )cpp"; |
| EXPECT_DECLS("DependentTemplateSpecializationTypeLoc", |
| "template <typename> struct B"); |
| } |
| |
| TEST_F(TargetDeclTest, TypedefCascade) { |
| Code = R"cpp( |
| struct C { |
| using type = int; |
| }; |
| struct B { |
| using type = C::type; |
| }; |
| struct A { |
| using type = B::type; |
| }; |
| A::[[type]] waldo; |
| )cpp"; |
| EXPECT_DECLS("TypedefTypeLoc", |
| {"using type = int", Rel::Alias | Rel::Underlying}, |
| {"using type = C::type", Rel::Alias | Rel::Underlying}, |
| {"using type = B::type", Rel::Alias}); |
| } |
| |
| TEST_F(TargetDeclTest, RecursiveTemplate) { |
| Flags.push_back("-std=c++20"); // the test case uses concepts |
| |
| Code = R"cpp( |
| template <typename T> |
| concept Leaf = false; |
| |
| template <typename Tree> |
| struct descend_left { |
| using type = typename descend_left<typename Tree::left>::[[type]]; |
| }; |
| |
| template <Leaf Tree> |
| struct descend_left<Tree> { |
| using type = typename Tree::value; |
| }; |
| )cpp"; |
| EXPECT_DECLS("DependentNameTypeLoc", |
| {"using type = typename descend_left<typename Tree::left>::type", |
| Rel::Alias | Rel::Underlying}); |
| } |
| |
| TEST_F(TargetDeclTest, ObjC) { |
| Flags = {"-xobjective-c"}; |
| Code = R"cpp( |
| @interface Foo {} |
| -(void)bar; |
| @end |
| void test(Foo *f) { |
| [f [[bar]] ]; |
| } |
| )cpp"; |
| EXPECT_DECLS("ObjCMessageExpr", "- (void)bar"); |
| |
| Code = R"cpp( |
| @interface Foo { @public int bar; } |
| @end |
| int test(Foo *f) { |
| return [[f->bar]]; |
| } |
| )cpp"; |
| EXPECT_DECLS("ObjCIvarRefExpr", "int bar"); |
| |
| Code = R"cpp( |
| @interface Foo {} |
| -(int) x; |
| -(void) setX:(int)x; |
| @end |
| void test(Foo *f) { |
| [[f.x]] = 42; |
| } |
| )cpp"; |
| EXPECT_DECLS("ObjCPropertyRefExpr", "- (void)setX:(int)x"); |
| |
| Code = R"cpp( |
| @interface I {} |
| @property(retain) I* x; |
| @property(retain) I* y; |
| @end |
| void test(I *f) { |
| [[f.x]].y = 0; |
| } |
| )cpp"; |
| EXPECT_DECLS("ObjCPropertyRefExpr", |
| "@property(atomic, retain, readwrite) I *x"); |
| |
| Code = R"cpp( |
| @interface MYObject |
| @end |
| @interface Interface |
| @property(retain) [[MYObject]] *x; |
| @end |
| )cpp"; |
| EXPECT_DECLS("ObjCInterfaceTypeLoc", "@interface MYObject"); |
| |
| Code = R"cpp( |
| @interface MYObject2 |
| @end |
| @interface Interface |
| @property(retain, nonnull) [[MYObject2]] *x; |
| @end |
| )cpp"; |
| EXPECT_DECLS("ObjCInterfaceTypeLoc", "@interface MYObject2"); |
| |
| Code = R"cpp( |
| @protocol Foo |
| @end |
| id test() { |
| return [[@protocol(Foo)]]; |
| } |
| )cpp"; |
| EXPECT_DECLS("ObjCProtocolExpr", "@protocol Foo"); |
| |
| Code = R"cpp( |
| @interface Foo |
| @end |
| void test([[Foo]] *p); |
| )cpp"; |
| EXPECT_DECLS("ObjCInterfaceTypeLoc", "@interface Foo"); |
| |
| Code = R"cpp(// Don't consider implicit interface as the target. |
| @implementation [[Implicit]] |
| @end |
| )cpp"; |
| EXPECT_DECLS("ObjCImplementationDecl", "@implementation Implicit"); |
| |
| Code = R"cpp( |
| @interface Foo |
| @end |
| @implementation [[Foo]] |
| @end |
| )cpp"; |
| EXPECT_DECLS("ObjCImplementationDecl", "@interface Foo"); |
| |
| Code = R"cpp( |
| @interface Foo |
| @end |
| @interface Foo (Ext) |
| @end |
| @implementation [[Foo]] (Ext) |
| @end |
| )cpp"; |
| EXPECT_DECLS("ObjCCategoryImplDecl", "@interface Foo(Ext)"); |
| |
| Code = R"cpp( |
| @protocol Foo |
| @end |
| void test([[id<Foo>]] p); |
| )cpp"; |
| EXPECT_DECLS("ObjCObjectTypeLoc", "@protocol Foo"); |
| |
| Code = R"cpp( |
| @class C; |
| @protocol Foo |
| @end |
| void test([[C]]<Foo> *p); |
| )cpp"; |
| EXPECT_DECLS("ObjCInterfaceTypeLoc", "@class C;"); |
| |
| Code = R"cpp( |
| @class C; |
| @protocol Foo |
| @end |
| void test(C<[[Foo]]> *p); |
| )cpp"; |
| EXPECT_DECLS("ObjCObjectTypeLoc", "@protocol Foo"); |
| |
| Code = R"cpp( |
| @class C; |
| @protocol Foo |
| @end |
| @protocol Bar |
| @end |
| void test(C<[[Foo]], Bar> *p); |
| )cpp"; |
| // FIXME: We currently can't disambiguate between multiple protocols. |
| EXPECT_DECLS("ObjCObjectTypeLoc", "@protocol Foo", "@protocol Bar"); |
| |
| Code = R"cpp( |
| @interface Foo |
| + (id)sharedInstance; |
| @end |
| @implementation Foo |
| + (id)sharedInstance { return 0; } |
| @end |
| void test() { |
| id value = [[Foo]].sharedInstance; |
| } |
| )cpp"; |
| EXPECT_DECLS("ObjCInterfaceTypeLoc", "@interface Foo"); |
| |
| Code = R"cpp( |
| @interface Foo |
| + (id)sharedInstance; |
| @end |
| @implementation Foo |
| + (id)sharedInstance { return 0; } |
| @end |
| void test() { |
| id value = Foo.[[sharedInstance]]; |
| } |
| )cpp"; |
| EXPECT_DECLS("ObjCPropertyRefExpr", "+ (id)sharedInstance"); |
| |
| Code = R"cpp( |
| @interface Foo |
| + ([[id]])sharedInstance; |
| @end |
| )cpp"; |
| EXPECT_DECLS("TypedefTypeLoc"); |
| |
| Code = R"cpp( |
| @interface Foo |
| + ([[instancetype]])sharedInstance; |
| @end |
| )cpp"; |
| EXPECT_DECLS("TypedefTypeLoc"); |
| } |
| |
| class FindExplicitReferencesTest : public ::testing::Test { |
| protected: |
| struct AllRefs { |
| std::string AnnotatedCode; |
| std::string DumpedReferences; |
| }; |
| |
| /// Parses \p Code, finds function or namespace '::foo' and annotates its body |
| /// with results of findExplicitReferences. |
| /// See actual tests for examples of annotation format. |
| AllRefs annotateReferencesInFoo(llvm::StringRef Code) { |
| TestTU TU; |
| TU.Code = std::string(Code); |
| |
| // FIXME: Auto-completion in a template requires disabling delayed template |
| // parsing. |
| TU.ExtraArgs.push_back("-std=c++20"); |
| TU.ExtraArgs.push_back("-xobjective-c++"); |
| |
| auto AST = TU.build(); |
| auto *TestDecl = &findDecl(AST, "foo"); |
| if (auto *T = llvm::dyn_cast<FunctionTemplateDecl>(TestDecl)) |
| TestDecl = T->getTemplatedDecl(); |
| |
| std::vector<ReferenceLoc> Refs; |
| if (const auto *Func = llvm::dyn_cast<FunctionDecl>(TestDecl)) |
| findExplicitReferences( |
| Func->getBody(), |
| [&Refs](ReferenceLoc R) { Refs.push_back(std::move(R)); }, |
| AST.getHeuristicResolver()); |
| else if (const auto *NS = llvm::dyn_cast<NamespaceDecl>(TestDecl)) |
| findExplicitReferences( |
| NS, |
| [&Refs, &NS](ReferenceLoc R) { |
| // Avoid adding the namespace foo decl to the results. |
| if (R.Targets.size() == 1 && R.Targets.front() == NS) |
| return; |
| Refs.push_back(std::move(R)); |
| }, |
| AST.getHeuristicResolver()); |
| else |
| ADD_FAILURE() << "Failed to find ::foo decl for test"; |
| |
| auto &SM = AST.getSourceManager(); |
| llvm::sort(Refs, [&](const ReferenceLoc &L, const ReferenceLoc &R) { |
| return SM.isBeforeInTranslationUnit(L.NameLoc, R.NameLoc); |
| }); |
| |
| std::string AnnotatedCode; |
| unsigned NextCodeChar = 0; |
| for (unsigned I = 0; I < Refs.size(); ++I) { |
| auto &R = Refs[I]; |
| |
| SourceLocation Pos = R.NameLoc; |
| assert(Pos.isValid()); |
| if (Pos.isMacroID()) // FIXME: figure out how to show macro locations. |
| Pos = SM.getExpansionLoc(Pos); |
| assert(Pos.isFileID()); |
| |
| FileID File; |
| unsigned Offset; |
| std::tie(File, Offset) = SM.getDecomposedLoc(Pos); |
| if (File == SM.getMainFileID()) { |
| // Print the reference in a source code. |
| assert(NextCodeChar <= Offset); |
| AnnotatedCode += Code.substr(NextCodeChar, Offset - NextCodeChar); |
| AnnotatedCode += "$" + std::to_string(I) + "^"; |
| |
| NextCodeChar = Offset; |
| } |
| } |
| AnnotatedCode += Code.substr(NextCodeChar); |
| |
| std::string DumpedReferences; |
| for (unsigned I = 0; I < Refs.size(); ++I) |
| DumpedReferences += std::string(llvm::formatv("{0}: {1}\n", I, Refs[I])); |
| |
| return AllRefs{std::move(AnnotatedCode), std::move(DumpedReferences)}; |
| } |
| }; |
| |
| TEST_F(FindExplicitReferencesTest, All) { |
| std::pair</*Code*/ llvm::StringRef, /*References*/ llvm::StringRef> Cases[] = |
| {// Simple expressions. |
| {R"cpp( |
| int global; |
| int func(); |
| void foo(int param) { |
| $0^global = $1^param + $2^func(); |
| } |
| )cpp", |
| "0: targets = {global}\n" |
| "1: targets = {param}\n" |
| "2: targets = {func}\n"}, |
| {R"cpp( |
| struct X { int a; }; |
| void foo(X x) { |
| $0^x.$1^a = 10; |
| } |
| )cpp", |
| "0: targets = {x}\n" |
| "1: targets = {X::a}\n"}, |
| {R"cpp( |
| // error-ok: testing with broken code |
| int bar(); |
| int foo() { |
| return $0^bar() + $1^bar(42); |
| } |
| )cpp", |
| "0: targets = {bar}\n" |
| "1: targets = {bar}\n"}, |
| // Namespaces and aliases. |
| {R"cpp( |
| namespace ns {} |
| namespace alias = ns; |
| void foo() { |
| using namespace $0^ns; |
| using namespace $1^alias; |
| } |
| )cpp", |
| "0: targets = {ns}\n" |
| "1: targets = {alias}\n"}, |
| // Using declarations. |
| {R"cpp( |
| namespace ns { int global; } |
| void foo() { |
| using $0^ns::$1^global; |
| } |
| )cpp", |
| "0: targets = {ns}\n" |
| "1: targets = {ns::global}, qualifier = 'ns::'\n"}, |
| // Simple types. |
| {R"cpp( |
| struct Struct { int a; }; |
| using Typedef = int; |
| void foo() { |
| $0^Struct $1^x; |
| $2^Typedef $3^y; |
| static_cast<$4^Struct*>(0); |
| } |
| )cpp", |
| "0: targets = {Struct}\n" |
| "1: targets = {x}, decl\n" |
| "2: targets = {Typedef}\n" |
| "3: targets = {y}, decl\n" |
| "4: targets = {Struct}\n"}, |
| // Name qualifiers. |
| {R"cpp( |
| namespace a { namespace b { struct S { typedef int type; }; } } |
| void foo() { |
| $0^a::$1^b::$2^S $3^x; |
| using namespace $4^a::$5^b; |
| $6^S::$7^type $8^y; |
| } |
| )cpp", |
| "0: targets = {a}\n" |
| "1: targets = {a::b}, qualifier = 'a::'\n" |
| "2: targets = {a::b::S}, qualifier = 'a::b::'\n" |
| "3: targets = {x}, decl\n" |
| "4: targets = {a}\n" |
| "5: targets = {a::b}, qualifier = 'a::'\n" |
| "6: targets = {a::b::S}\n" |
| "7: targets = {a::b::S::type}, qualifier = 'struct S::'\n" |
| "8: targets = {y}, decl\n"}, |
| {R"cpp( |
| void foo() { |
| $0^ten: // PRINT "HELLO WORLD!" |
| goto $1^ten; |
| } |
| )cpp", |
| "0: targets = {ten}, decl\n" |
| "1: targets = {ten}\n"}, |
| // Simple templates. |
| {R"cpp( |
| template <class T> struct vector { using value_type = T; }; |
| template <> struct vector<bool> { using value_type = bool; }; |
| void foo() { |
| $0^vector<int> $1^vi; |
| $2^vector<bool> $3^vb; |
| } |
| )cpp", |
| "0: targets = {vector<int>}\n" |
| "1: targets = {vi}, decl\n" |
| "2: targets = {vector<bool>}\n" |
| "3: targets = {vb}, decl\n"}, |
| // Template type aliases. |
| {R"cpp( |
| template <class T> struct vector { using value_type = T; }; |
| template <> struct vector<bool> { using value_type = bool; }; |
| template <class T> using valias = vector<T>; |
| void foo() { |
| $0^valias<int> $1^vi; |
| $2^valias<bool> $3^vb; |
| } |
| )cpp", |
| "0: targets = {valias}\n" |
| "1: targets = {vi}, decl\n" |
| "2: targets = {valias}\n" |
| "3: targets = {vb}, decl\n"}, |
| // Injected class name. |
| {R"cpp( |
| namespace foo { |
| template <typename $0^T> |
| class $1^Bar { |
| ~$2^Bar(); |
| void $3^f($4^Bar); |
| }; |
| } |
| )cpp", |
| "0: targets = {foo::Bar::T}, decl\n" |
| "1: targets = {foo::Bar}, decl\n" |
| "2: targets = {foo::Bar}\n" |
| "3: targets = {foo::Bar::f}, decl\n" |
| "4: targets = {foo::Bar}\n"}, |
| // MemberExpr should know their using declaration. |
| {R"cpp( |
| struct X { void func(int); }; |
| struct Y : X { |
| using X::func; |
| }; |
| void foo(Y y) { |
| $0^y.$1^func(1); |
| } |
| )cpp", |
| "0: targets = {y}\n" |
| "1: targets = {Y::func}\n"}, |
| // DeclRefExpr should know their using declaration. |
| {R"cpp( |
| namespace ns { void bar(int); } |
| using ns::bar; |
| |
| void foo() { |
| $0^bar(10); |
| } |
| )cpp", |
| "0: targets = {bar}\n"}, |
| // References from a macro. |
| {R"cpp( |
| #define FOO a |
| #define BAR b |
| |
| void foo(int a, int b) { |
| $0^FOO+$1^BAR; |
| } |
| )cpp", |
| "0: targets = {a}\n" |
| "1: targets = {b}\n"}, |
| // No references from implicit nodes. |
| {R"cpp( |
| struct vector { |
| int *begin(); |
| int *end(); |
| }; |
| |
| void foo() { |
| for (int $0^x : $1^vector()) { |
| $2^x = 10; |
| } |
| } |
| )cpp", |
| "0: targets = {x}, decl\n" |
| "1: targets = {vector}\n" |
| "2: targets = {x}\n"}, |
| // Handle UnresolvedLookupExpr. |
| // FIXME |
| // This case fails when expensive checks are enabled. |
| // Seems like the order of ns1::func and ns2::func isn't defined. |
| #ifndef EXPENSIVE_CHECKS |
| {R"cpp( |
| namespace ns1 { void func(char*); } |
| namespace ns2 { void func(int*); } |
| using namespace ns1; |
| using namespace ns2; |
| |
| template <class T> |
| void foo(T t) { |
| $0^func($1^t); |
| } |
| )cpp", |
| "0: targets = {ns1::func, ns2::func}\n" |
| "1: targets = {t}\n"}, |
| #endif |
| // Handle UnresolvedMemberExpr. |
| {R"cpp( |
| struct X { |
| void func(char*); |
| void func(int*); |
| }; |
| |
| template <class T> |
| void foo(X x, T t) { |
| $0^x.$1^func($2^t); |
| } |
| )cpp", |
| "0: targets = {x}\n" |
| "1: targets = {X::func, X::func}\n" |
| "2: targets = {t}\n"}, |
| // Handle DependentScopeDeclRefExpr. |
| {R"cpp( |
| template <class T> |
| struct S { |
| static int value; |
| }; |
| |
| template <class T> |
| void foo() { |
| $0^S<$1^T>::$2^value; |
| } |
| )cpp", |
| "0: targets = {S}\n" |
| "1: targets = {T}\n" |
| "2: targets = {S::value}, qualifier = 'S<T>::'\n"}, |
| // Handle CXXDependentScopeMemberExpr. |
| {R"cpp( |
| template <class T> |
| struct S { |
| int value; |
| }; |
| |
| template <class T> |
| void foo(S<T> t) { |
| $0^t.$1^value; |
| } |
| )cpp", |
| "0: targets = {t}\n" |
| "1: targets = {S::value}\n"}, |
| // Type template parameters. |
| {R"cpp( |
| template <class T> |
| void foo() { |
| static_cast<$0^T>(0); |
| $1^T(); |
| $2^T $3^t; |
| } |
| )cpp", |
| "0: targets = {T}\n" |
| "1: targets = {T}\n" |
| "2: targets = {T}\n" |
| "3: targets = {t}, decl\n"}, |
| // Non-type template parameters. |
| {R"cpp( |
| template <int I> |
| void foo() { |
| int $0^x = $1^I; |
| } |
| )cpp", |
| "0: targets = {x}, decl\n" |
| "1: targets = {I}\n"}, |
| // Template template parameters. |
| {R"cpp( |
| template <class T> struct vector {}; |
| |
| template <template<class> class TT, template<class> class ...TP> |
| void foo() { |
| $0^TT<int> $1^x; |
| $2^foo<$3^TT>(); |
| $4^foo<$5^vector>(); |
| $6^foo<$7^TP...>(); |
| } |
| )cpp", |
| "0: targets = {TT}\n" |
| "1: targets = {x}, decl\n" |
| "2: targets = {foo}\n" |
| "3: targets = {TT}\n" |
| "4: targets = {foo}\n" |
| "5: targets = {vector}\n" |
| "6: targets = {foo}\n" |
| "7: targets = {TP}\n"}, |
| // Non-type template parameters with declarations. |
| {R"cpp( |
| int func(); |
| template <int(*)()> struct wrapper {}; |
| |
| template <int(*FuncParam)()> |
| void foo() { |
| $0^wrapper<$1^func> $2^w; |
| $3^FuncParam(); |
| } |
| )cpp", |
| "0: targets = {wrapper<&func>}\n" |
| "1: targets = {func}\n" |
| "2: targets = {w}, decl\n" |
| "3: targets = {FuncParam}\n"}, |
| // declaration references. |
| {R"cpp( |
| namespace ns {} |
| class S {}; |
| void foo() { |
| class $0^Foo { $1^Foo(); ~$2^Foo(); int $3^field; }; |
| int $4^Var; |
| enum $5^E { $6^ABC }; |
| typedef int $7^INT; |
| using $8^INT2 = int; |
| namespace $9^NS = $10^ns; |
| } |
| )cpp", |
| "0: targets = {Foo}, decl\n" |
| "1: targets = {foo()::Foo::Foo}, decl\n" |
| "2: targets = {Foo}\n" |
| "3: targets = {foo()::Foo::field}, decl\n" |
| "4: targets = {Var}, decl\n" |
| "5: targets = {E}, decl\n" |
| "6: targets = {foo()::ABC}, decl\n" |
| "7: targets = {INT}, decl\n" |
| "8: targets = {INT2}, decl\n" |
| "9: targets = {NS}, decl\n" |
| "10: targets = {ns}\n"}, |
| // User-defined conversion operator. |
| {R"cpp( |
| void foo() { |
| class $0^Bar {}; |
| class $1^Foo { |
| public: |
| // FIXME: This should have only one reference to Bar. |
| $2^operator $3^$4^Bar(); |
| }; |
| |
| $5^Foo $6^f; |
| $7^f.$8^operator $9^Bar(); |
| } |
| )cpp", |
| "0: targets = {Bar}, decl\n" |
| "1: targets = {Foo}, decl\n" |
| "2: targets = {foo()::Foo::operator Bar}, decl\n" |
| "3: targets = {Bar}\n" |
| "4: targets = {Bar}\n" |
| "5: targets = {Foo}\n" |
| "6: targets = {f}, decl\n" |
| "7: targets = {f}\n" |
| "8: targets = {foo()::Foo::operator Bar}\n" |
| "9: targets = {Bar}\n"}, |
| // Destructor. |
| {R"cpp( |
| void foo() { |
| class $0^Foo { |
| public: |
| ~$1^Foo() {} |
| |
| void $2^destructMe() { |
| this->~$3^Foo(); |
| } |
| }; |
| |
| $4^Foo $5^f; |
| $6^f.~ /*...*/ $7^Foo(); |
| } |
| )cpp", |
| "0: targets = {Foo}, decl\n" |
| // FIXME: It's better to target destructor's FunctionDecl instead of |
| // the type itself (similar to constructor). |
| "1: targets = {Foo}\n" |
| "2: targets = {foo()::Foo::destructMe}, decl\n" |
| "3: targets = {Foo}\n" |
| "4: targets = {Foo}\n" |
| "5: targets = {f}, decl\n" |
| "6: targets = {f}\n" |
| "7: targets = {Foo}\n"}, |
| // cxx constructor initializer. |
| {R"cpp( |
| class Base {}; |
| void foo() { |
| // member initializer |
| class $0^X { |
| int $1^abc; |
| $2^X(): $3^abc() {} |
| }; |
| // base initializer |
| class $4^Derived : public $5^Base { |
| $6^Base $7^B; |
| $8^Derived() : $9^Base() {} |
| }; |
| // delegating initializer |
| class $10^Foo { |
| $11^Foo(int); |
| $12^Foo(): $13^Foo(111) {} |
| }; |
| } |
| )cpp", |
| "0: targets = {X}, decl\n" |
| "1: targets = {foo()::X::abc}, decl\n" |
| "2: targets = {foo()::X::X}, decl\n" |
| "3: targets = {foo()::X::abc}\n" |
| "4: targets = {Derived}, decl\n" |
| "5: targets = {Base}\n" |
| "6: targets = {Base}\n" |
| "7: targets = {foo()::Derived::B}, decl\n" |
| "8: targets = {foo()::Derived::Derived}, decl\n" |
| "9: targets = {Base}\n" |
| "10: targets = {Foo}, decl\n" |
| "11: targets = {foo()::Foo::Foo}, decl\n" |
| "12: targets = {foo()::Foo::Foo}, decl\n" |
| "13: targets = {Foo}\n"}, |
| // Anonymous entities should not be reported. |
| { |
| R"cpp( |
| void foo() { |
| class {} $0^x; |
| int (*$1^fptr)(int $2^a, int) = nullptr; |
| } |
| )cpp", |
| "0: targets = {x}, decl\n" |
| "1: targets = {fptr}, decl\n" |
| "2: targets = {a}, decl\n"}, |
| // Namespace aliases should be handled properly. |
| { |
| R"cpp( |
| namespace ns { struct Type {}; } |
| namespace alias = ns; |
| namespace rec_alias = alias; |
| |
| void foo() { |
| $0^ns::$1^Type $2^a; |
| $3^alias::$4^Type $5^b; |
| $6^rec_alias::$7^Type $8^c; |
| } |
| )cpp", |
| "0: targets = {ns}\n" |
| "1: targets = {ns::Type}, qualifier = 'ns::'\n" |
| "2: targets = {a}, decl\n" |
| "3: targets = {alias}\n" |
| "4: targets = {ns::Type}, qualifier = 'alias::'\n" |
| "5: targets = {b}, decl\n" |
| "6: targets = {rec_alias}\n" |
| "7: targets = {ns::Type}, qualifier = 'rec_alias::'\n" |
| "8: targets = {c}, decl\n"}, |
| // Handle SizeOfPackExpr. |
| { |
| R"cpp( |
| template <typename... E> |
| void foo() { |
| constexpr int $0^size = sizeof...($1^E); |
| }; |
| )cpp", |
| "0: targets = {size}, decl\n" |
| "1: targets = {E}\n"}, |
| // Class template argument deduction |
| { |
| R"cpp( |
| template <typename T> |
| struct Test { |
| Test(T); |
| }; |
| void foo() { |
| $0^Test $1^a(5); |
| } |
| )cpp", |
| "0: targets = {Test}\n" |
| "1: targets = {a}, decl\n"}, |
| // Templates |
| {R"cpp( |
| namespace foo { |
| template <typename $0^T> |
| class $1^Bar {}; |
| } |
| )cpp", |
| "0: targets = {foo::Bar::T}, decl\n" |
| "1: targets = {foo::Bar}, decl\n"}, |
| // Templates |
| {R"cpp( |
| namespace foo { |
| template <typename $0^T> |
| void $1^func(); |
| } |
| )cpp", |
| "0: targets = {T}, decl\n" |
| "1: targets = {foo::func}, decl\n"}, |
| // Templates |
| {R"cpp( |
| namespace foo { |
| template <typename $0^T> |
| $1^T $2^x; |
| } |
| )cpp", |
| "0: targets = {foo::T}, decl\n" |
| "1: targets = {foo::T}\n" |
| "2: targets = {foo::x}, decl\n"}, |
| // Templates |
| {R"cpp( |
| template<typename T> class vector {}; |
| namespace foo { |
| template <typename $0^T> |
| using $1^V = $2^vector<$3^T>; |
| } |
| )cpp", |
| "0: targets = {foo::T}, decl\n" |
| "1: targets = {foo::V}, decl\n" |
| "2: targets = {vector}\n" |
| "3: targets = {foo::T}\n"}, |
| // Concept |
| { |
| R"cpp( |
| template <typename T> |
| concept Drawable = requires (T t) { t.draw(); }; |
| |
| namespace foo { |
| template <typename $0^T> requires $1^Drawable<$2^T> |
| void $3^bar($4^T $5^t) { |
| $6^t.$7^draw(); |
| } |
| } |
| )cpp", |
| "0: targets = {T}, decl\n" |
| "1: targets = {Drawable}\n" |
| "2: targets = {T}\n" |
| "3: targets = {foo::bar}, decl\n" |
| "4: targets = {T}\n" |
| "5: targets = {t}, decl\n" |
| "6: targets = {t}\n" |
| "7: targets = {}\n"}, |
| // Objective-C: instance variables |
| { |
| R"cpp( |
| @interface I { |
| @public |
| I *_z; |
| } |
| @end |
| I *f; |
| void foo() { |
| $0^f->$1^_z = 0; |
| } |
| )cpp", |
| "0: targets = {f}\n" |
| "1: targets = {I::_z}\n"}, |
| // Objective-C: properties |
| { |
| R"cpp( |
| @interface I {} |
| @property(retain) I* x; |
| @property(retain) I* y; |
| @end |
| I *f; |
| void foo() { |
| $0^f.$1^x.$2^y = 0; |
| } |
| )cpp", |
| "0: targets = {f}\n" |
| "1: targets = {I::x}\n" |
| "2: targets = {I::y}\n"}, |
| // Objective-C: implicit properties |
| { |
| R"cpp( |
| @interface I {} |
| -(I*)x; |
| -(void)setY:(I*)y; |
| @end |
| I *f; |
| void foo() { |
| $0^f.$1^x.$2^y = 0; |
| } |
| )cpp", |
| "0: targets = {f}\n" |
| "1: targets = {I::x}\n" |
| "2: targets = {I::setY:}\n"}, |
| // Objective-C: class properties |
| { |
| R"cpp( |
| @interface I {} |
| @property(class) I *x; |
| @end |
| id local; |
| void foo() { |
| $0^I.$1^x = 0; |
| $2^local = $3^I.$4^x; |
| } |
| )cpp", |
| "0: targets = {I}\n" |
| "1: targets = {I::setX:}\n" |
| "2: targets = {local}\n" |
| "3: targets = {I}\n" |
| "4: targets = {I::x}\n"}, |
| // Objective-C: implicit class properties |
| { |
| R"cpp( |
| @interface I {} |
| +(I*)x; |
| +(void)setX:(I*)x; |
| @end |
| id local; |
| void foo() { |
| $0^I.$1^x = 0; |
| $2^local = $3^I.$4^x; |
| } |
| )cpp", |
| "0: targets = {I}\n" |
| "1: targets = {I::setX:}\n" |
| "2: targets = {local}\n" |
| "3: targets = {I}\n" |
| "4: targets = {I::x}\n"}, |
| {// Objective-C: methods |
| R"cpp( |
| @interface I |
| -(void) a:(int)x b:(int)y; |
| @end |
| void foo(I *i) { |
| [$0^i $1^a:1 b:2]; |
| } |
| )cpp", |
| "0: targets = {i}\n" |
| "1: targets = {I::a:b:}\n"}, |
| {// Objective-C: protocols |
| R"cpp( |
| @interface I |
| @end |
| @protocol P |
| @end |
| void foo() { |
| $0^I<$1^P> *$2^x; |
| } |
| )cpp", |
| "0: targets = {I}\n" |
| "1: targets = {P}\n" |
| "2: targets = {x}, decl\n"}, |
| |
| // Designated initializers. |
| {R"cpp( |
| void foo() { |
| struct $0^Foo { |
| int $1^Bar; |
| }; |
| $2^Foo $3^f { .$4^Bar = 42 }; |
| } |
| )cpp", |
| "0: targets = {Foo}, decl\n" |
| "1: targets = {foo()::Foo::Bar}, decl\n" |
| "2: targets = {Foo}\n" |
| "3: targets = {f}, decl\n" |
| "4: targets = {foo()::Foo::Bar}\n"}, |
| {R"cpp( |
| void foo() { |
| struct $0^Baz { |
| int $1^Field; |
| }; |
| struct $2^Bar { |
| $3^Baz $4^Foo; |
| }; |
| $5^Bar $6^bar { .$7^Foo.$8^Field = 42 }; |
| } |
| )cpp", |
| "0: targets = {Baz}, decl\n" |
| "1: targets = {foo()::Baz::Field}, decl\n" |
| "2: targets = {Bar}, decl\n" |
| "3: targets = {Baz}\n" |
| "4: targets = {foo()::Bar::Foo}, decl\n" |
| "5: targets = {Bar}\n" |
| "6: targets = {bar}, decl\n" |
| "7: targets = {foo()::Bar::Foo}\n" |
| "8: targets = {foo()::Baz::Field}\n"}, |
| {R"cpp( |
| template<typename T> |
| void crash(T); |
| template<typename T> |
| void foo() { |
| $0^crash({.$1^x = $2^T()}); |
| } |
| )cpp", |
| "0: targets = {crash}\n" |
| "1: targets = {}\n" |
| "2: targets = {T}\n"}, |
| // unknown template name should not crash. |
| {R"cpp( |
| template <template <typename> typename T> |
| struct Base {}; |
| namespace foo { |
| template <typename $0^T> |
| struct $1^Derive : $2^Base<$3^T::template $4^Unknown> {}; |
| } |
| )cpp", |
| "0: targets = {foo::Derive::T}, decl\n" |
| "1: targets = {foo::Derive}, decl\n" |
| "2: targets = {Base}\n" |
| "3: targets = {foo::Derive::T}\n" |
| "4: targets = {}, qualifier = 'T::'\n"}, |
| // deduction guide |
| {R"cpp( |
| namespace foo { |
| template <typename $0^T> |
| struct $1^Test { |
| template <typename $2^I> |
| $3^Test($4^I); |
| }; |
| template <typename $5^I> |
| $6^Test($7^I) -> $8^Test<typename $9^I::$10^type>; |
| } |
| )cpp", |
| "0: targets = {T}, decl\n" |
| "1: targets = {foo::Test}, decl\n" |
| "2: targets = {I}, decl\n" |
| "3: targets = {foo::Test::Test<T>}, decl\n" |
| "4: targets = {I}\n" |
| "5: targets = {I}, decl\n" |
| "6: targets = {foo::Test}\n" |
| "7: targets = {I}\n" |
| "8: targets = {foo::Test}\n" |
| "9: targets = {I}\n" |
| "10: targets = {}, qualifier = 'I::'\n"}}; |
| |
| for (const auto &C : Cases) { |
| llvm::StringRef ExpectedCode = C.first; |
| llvm::StringRef ExpectedRefs = C.second; |
| |
| auto Actual = |
| annotateReferencesInFoo(llvm::Annotations(ExpectedCode).code()); |
| EXPECT_EQ(ExpectedCode, Actual.AnnotatedCode); |
| EXPECT_EQ(ExpectedRefs, Actual.DumpedReferences) << ExpectedCode; |
| } |
| } |
| |
| } // namespace |
| } // namespace clangd |
| } // namespace clang |