Matheus Izvekov 14f7bd63b9
Reland: [clang] preserve class type sugar when taking pointer to member (#132401)
Original PR: #130537
Originally reverted due to revert of dependent commit. Relanding with no
changes.

This changes the MemberPointerType representation to use a
NestedNameSpecifier instead of a Type to represent the base class.

Since the qualifiers are always parsed as nested names, there was an
impedance mismatch when converting these back and forth into types, and
this led to issues in preserving sugar.

The nested names are indeed a better match for these, as the differences
which a QualType can represent cannot be expressed syntatically, and
they represent the use case more exactly, being either dependent or
referring to a CXXRecord, unqualified.

This patch also makes the MemberPointerType able to represent sugar for
a {up/downcast}cast conversion of the base class, although for now the
underlying type is canonical, as preserving the sugar up to that point
requires further work.

As usual, includes a few drive-by fixes in order to make use of the
improvements.
2025-03-21 13:20:52 -03:00

2173 lines
59 KiB
C++

//===-- 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 <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()"});
Code = R"cpp(
namespace ns {
template<typename T> class S {};
}
using ns::S;
template<typename T>
using A = [[S]]<T>;
)cpp";
EXPECT_DECLS("TemplateSpecializationTypeLoc", {"using ns::S", Rel::Alias},
{"template <typename T> class S"},
{"class S", Rel::TemplatePattern});
Code = R"cpp(
namespace ns {
template<typename T> class S {};
}
using ns::S;
template <template <typename> class T> class X {};
using B = X<[[S]]>;
)cpp";
EXPECT_DECLS("TemplateArgumentLoc", {"using ns::S", Rel::Alias},
{"template <typename T> class S"});
Code = R"cpp(
namespace ns {
template<typename T> class S { public: S(T); };
}
using ns::S;
[[S]] s(123);
)cpp";
Flags.push_back("-std=c++17"); // For CTAD feature.
EXPECT_DECLS("DeducedTemplateSpecializationTypeLoc",
{"using ns::S", Rel::Alias}, {"template <typename T> class S"},
{"class S", Rel::TemplatePattern});
Code = R"cpp(
template<typename T>
class Foo { public: class foo {}; };
template <class T> class A : public Foo<T> {
using typename Foo<T>::foo;
[[foo]] abc;
};
)cpp";
EXPECT_DECLS("UnresolvedUsingTypeLoc",
{"using typename Foo<T>::foo", Rel::Alias});
// Using enum.
Flags.push_back("-std=c++20");
Code = R"cpp(
namespace ns { enum class A { X }; }
[[using enum ns::A]];
)cpp";
EXPECT_DECLS("UsingEnumDecl", "enum class A : int");
Code = R"cpp(
namespace ns { enum class A { X }; }
using enum ns::A;
auto m = [[X]];
)cpp";
EXPECT_DECLS("DeclRefExpr", "X");
}
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(
"ConceptReference",
{"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("ConceptReference",
{"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("ConceptReference",
{"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("ConceptReference",
{"template <typename T, typename U> concept Fooable = true"});
}
TEST_F(TargetDeclTest, Coroutine) {
Flags.push_back("-std=c++20");
Code = R"cpp(
namespace std {
template <typename, typename...> struct coroutine_traits;
template <typename> struct coroutine_handle {
template <typename U>
coroutine_handle(coroutine_handle<U>&&) noexcept;
static coroutine_handle from_address(void* __addr) noexcept;
};
} // namespace std
struct executor {};
struct awaitable {};
struct awaitable_frame {
awaitable get_return_object();
void return_void();
void unhandled_exception();
struct result_t {
~result_t();
bool await_ready() const noexcept;
void await_suspend(std::coroutine_handle<void>) noexcept;
void await_resume() const noexcept;
};
result_t initial_suspend() noexcept;
result_t final_suspend() noexcept;
result_t await_transform(executor) noexcept;
};
namespace std {
template <>
struct coroutine_traits<awaitable> {
typedef awaitable_frame promise_type;
};
} // namespace std
awaitable foo() {
co_await [[executor]]();
}
)cpp";
EXPECT_DECLS("RecordTypeLoc", "struct executor");
}
TEST_F(TargetDeclTest, RewrittenBinaryOperator) {
Flags.push_back("-std=c++20");
Code = R"cpp(
namespace std {
struct strong_ordering {
int n;
constexpr operator int() const { return n; }
static const strong_ordering equal, greater, less;
};
constexpr strong_ordering strong_ordering::equal = {0};
constexpr strong_ordering strong_ordering::greater = {1};
constexpr strong_ordering strong_ordering::less = {-1};
}
struct Foo
{
int x;
auto operator<=>(const Foo&) const = default;
};
bool x = (Foo(1) [[!=]] Foo(2));
)cpp";
EXPECT_DECLS("CXXRewrittenBinaryOperator",
{"bool operator==(const Foo &) const noexcept = default"});
}
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, BuiltinTemplates) {
Code = R"cpp(
template <class T, T... Index> struct integer_sequence {};
[[__make_integer_seq]]<integer_sequence, int, 3> X;
)cpp";
EXPECT_DECLS(
"TemplateSpecializationTypeLoc",
{"struct integer_sequence", Rel::TemplatePattern | Rel::Underlying},
{"template<> struct integer_sequence<int, <0, 1, 2>>",
Rel::TemplateInstantiation | Rel::Underlying});
// Dependent context.
Code = R"cpp(
template <class T, T... Index> struct integer_sequence;
template <class T, int N>
using make_integer_sequence = [[__make_integer_seq]]<integer_sequence, T, N>;
)cpp";
EXPECT_DECLS("TemplateSpecializationTypeLoc", );
Code = R"cpp(
template <int N, class... Pack>
using type_pack_element = [[__type_pack_element]]<N, Pack...>;
)cpp";
EXPECT_DECLS("TemplateSpecializationTypeLoc", );
}
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";
// Sized deallocation is enabled by default in C++14 onwards.
EXPECT_DECLS("CXXDeleteExpr",
"void operator delete(void *, unsigned long) noexcept");
}
TEST_F(TargetDeclTest, DependentExprs) {
Flags.push_back("--std=c++20");
// 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("MemberExpr", "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("MemberExpr", "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("MemberExpr", "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");
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";
EXPECT_DECLS("CXXDependentScopeMemberExpr", "void find()");
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";
EXPECT_DECLS("CXXDependentScopeMemberExpr", "void find()");
Code = R"cpp(
struct Waldo {
void find();
};
template <typename T>
using Wally = Waldo;
template <typename>
struct S : Wally<int> {
void Foo() { this->[[find]](); }
};
)cpp";
EXPECT_DECLS("MemberExpr", "void find()");
// Base expression is the type of a non-type template parameter
// which is deduced using CTAD.
Code = R"cpp(
template <int N>
struct Waldo {
const int found = N;
};
template <Waldo W>
int test() {
return W.[[found]];
}
)cpp";
EXPECT_DECLS("CXXDependentScopeMemberExpr", "const int found = N");
}
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");
// Dependent name with recursive definition. We don't expect a
// result, but we shouldn't get into a stack overflow either.
Code = R"cpp(
template <int N>
struct waldo {
typedef typename waldo<N - 1>::type::[[next]] type;
};
)cpp";
EXPECT_DECLS("DependentNameTypeLoc", );
// Similar to above but using mutually recursive templates.
Code = R"cpp(
template <int N>
struct odd;
template <int N>
struct even {
using type = typename odd<N - 1>::type::next;
};
template <int N>
struct odd {
using type = typename even<N - 1>::type::[[next]];
};
)cpp";
EXPECT_DECLS("DependentNameTypeLoc", );
}
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(
@interface Foo
@end
@interface Foo (Ext)
@end
@implementation Foo ([[Ext]])
@end
)cpp";
EXPECT_DECLS("ObjCCategoryImplDecl", "@interface Foo(Ext)");
Code = R"cpp(
void test(id</*error-ok*/[[InvalidProtocol]]> p);
)cpp";
EXPECT_DECLS("ParmVarDecl", "id p");
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("ObjCProtocolLoc", "@protocol Foo");
Code = R"cpp(
@class C;
@protocol Foo
@end
@protocol Bar
@end
void test(C<[[Foo]], Bar> *p);
)cpp";
EXPECT_DECLS("ObjCProtocolLoc", "@protocol Foo");
Code = R"cpp(
@class C;
@protocol Foo
@end
@protocol Bar
@end
void test(C<Foo, [[Bar]]> *p);
)cpp";
EXPECT_DECLS("ObjCProtocolLoc", "@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;
};
TestTU newTU(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++");
return TU;
}
AllRefs annotatedReferences(llvm::StringRef Code, ParsedAST &AST,
std::vector<ReferenceLoc> Refs) {
auto &SM = AST.getSourceManager();
llvm::stable_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)};
}
/// Parses \p Code, and annotates its body with results of
/// findExplicitReferences on all top level decls.
/// See actual tests for examples of annotation format.
AllRefs annotateAllReferences(llvm::StringRef Code) {
TestTU TU = newTU(Code);
auto AST = TU.build();
std::vector<ReferenceLoc> Refs;
for (auto *TopLevel : AST.getLocalTopLevelDecls())
findExplicitReferences(
TopLevel, [&Refs](ReferenceLoc R) { Refs.push_back(std::move(R)); },
AST.getHeuristicResolver());
return annotatedReferences(Code, AST, std::move(Refs));
}
/// 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 = newTU(Code);
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 if (const auto *OC = llvm::dyn_cast<ObjCContainerDecl>(TestDecl))
findExplicitReferences(
OC, [&Refs](ReferenceLoc R) { Refs.push_back(std::move(R)); },
AST.getHeuristicResolver());
else
ADD_FAILURE() << "Failed to find ::foo decl for test";
return annotatedReferences(Code, AST, std::move(Refs));
}
};
TEST_F(FindExplicitReferencesTest, AllRefsInFoo) {
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"},
// Using enum declarations.
{R"cpp(
namespace ns { enum class A {}; }
void foo() {
using enum $0^ns::$1^A;
}
)cpp",
"0: targets = {ns}\n"
"1: targets = {ns::A}, 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 = '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.
{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"},
// 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() {
$0^class {} $1^x;
int (*$2^fptr)(int $3^a, int) = nullptr;
}
)cpp",
"0: targets = {(unnamed)}\n"
"1: targets = {x}, decl\n"
"2: targets = {fptr}, decl\n"
"3: 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;
}
}
TEST_F(FindExplicitReferencesTest, AllRefs) {
std::pair</*Code*/ llvm::StringRef, /*References*/ llvm::StringRef> Cases[] =
{{R"cpp(
@interface $0^MyClass
@end
@implementation $1^$2^MyClass
@end
)cpp",
"0: targets = {MyClass}, decl\n"
"1: targets = {MyClass}\n"
"2: targets = {MyClass}, decl\n"},
{R"cpp(
@interface $0^MyClass
@end
@interface $1^MyClass ($2^Category)
@end
@implementation $3^MyClass ($4^$5^Category)
@end
)cpp",
"0: targets = {MyClass}, decl\n"
"1: targets = {MyClass}\n"
"2: targets = {Category}, decl\n"
"3: targets = {MyClass}\n"
"4: targets = {Category}\n"
"5: targets = {Category}, decl\n"}};
for (const auto &C : Cases) {
llvm::StringRef ExpectedCode = C.first;
llvm::StringRef ExpectedRefs = C.second;
auto Actual = annotateAllReferences(llvm::Annotations(ExpectedCode).code());
EXPECT_EQ(ExpectedCode, Actual.AnnotatedCode);
EXPECT_EQ(ExpectedRefs, Actual.DumpedReferences) << ExpectedCode;
}
}
} // namespace
} // namespace clangd
} // namespace clang