Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

2103 lines
57 KiB
C++
Raw Normal View History

//===-- 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(
"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, 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",
{"std::strong_ordering operator<=>(const Foo &) const = default",
Rel::TemplatePattern},
{"bool operator==(const Foo &) const noexcept = default",
Rel::TemplateInstantiation});
}
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");
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("CXXDependentScopeMemberExpr", "void find()");
}
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(
@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 = '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.
{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() {
[clang] Implement ElaboratedType sugaring for types written bare Without this patch, clang will not wrap in an ElaboratedType node types written without a keyword and nested name qualifier, which goes against the intent that we should produce an AST which retains enough details to recover how things are written. The lack of this sugar is incompatible with the intent of the type printer default policy, which is to print types as written, but to fall back and print them fully qualified when they are desugared. An ElaboratedTypeLoc without keyword / NNS uses no storage by itself, but still requires pointer alignment due to pre-existing bug in the TypeLoc buffer handling. --- Troubleshooting list to deal with any breakage seen with this patch: 1) The most likely effect one would see by this patch is a change in how a type is printed. The type printer will, by design and default, print types as written. There are customization options there, but not that many, and they mainly apply to how to print a type that we somehow failed to track how it was written. This patch fixes a problem where we failed to distinguish between a type that was written without any elaborated-type qualifiers, such as a 'struct'/'class' tags and name spacifiers such as 'std::', and one that has been stripped of any 'metadata' that identifies such, the so called canonical types. Example: ``` namespace foo { struct A {}; A a; }; ``` If one were to print the type of `foo::a`, prior to this patch, this would result in `foo::A`. This is how the type printer would have, by default, printed the canonical type of A as well. As soon as you add any name qualifiers to A, the type printer would suddenly start accurately printing the type as written. This patch will make it print it accurately even when written without qualifiers, so we will just print `A` for the initial example, as the user did not really write that `foo::` namespace qualifier. 2) This patch could expose a bug in some AST matcher. Matching types is harder to get right when there is sugar involved. For example, if you want to match a type against being a pointer to some type A, then you have to account for getting a type that is sugar for a pointer to A, or being a pointer to sugar to A, or both! Usually you would get the second part wrong, and this would work for a very simple test where you don't use any name qualifiers, but you would discover is broken when you do. The usual fix is to either use the matcher which strips sugar, which is annoying to use as for example if you match an N level pointer, you have to put N+1 such matchers in there, beginning to end and between all those levels. But in a lot of cases, if the property you want to match is present in the canonical type, it's easier and faster to just match on that... This goes with what is said in 1), if you want to match against the name of a type, and you want the name string to be something stable, perhaps matching on the name of the canonical type is the better choice. 3) This patch could expose a bug in how you get the source range of some TypeLoc. For some reason, a lot of code is using getLocalSourceRange(), which only looks at the given TypeLoc node. This patch introduces a new, and more common TypeLoc node which contains no source locations on itself. This is not an inovation here, and some other, more rare TypeLoc nodes could also have this property, but if you use getLocalSourceRange on them, it's not going to return any valid locations, because it doesn't have any. The right fix here is to always use getSourceRange() or getBeginLoc/getEndLoc which will dive into the inner TypeLoc to get the source range if it doesn't find it on the top level one. You can use getLocalSourceRange if you are really into micro-optimizations and you have some outside knowledge that the TypeLocs you are dealing with will always include some source location. 4) Exposed a bug somewhere in the use of the normal clang type class API, where you have some type, you want to see if that type is some particular kind, you try a `dyn_cast` such as `dyn_cast<TypedefType>` and that fails because now you have an ElaboratedType which has a TypeDefType inside of it, which is what you wanted to match. Again, like 2), this would usually have been tested poorly with some simple tests with no qualifications, and would have been broken had there been any other kind of type sugar, be it an ElaboratedType or a TemplateSpecializationType or a SubstTemplateParmType. The usual fix here is to use `getAs` instead of `dyn_cast`, which will look deeper into the type. Or use `getAsAdjusted` when dealing with TypeLocs. For some reason the API is inconsistent there and on TypeLocs getAs behaves like a dyn_cast. 5) It could be a bug in this patch perhaps. Let me know if you need any help! Signed-off-by: Matheus Izvekov <mizvekov@gmail.com> Differential Revision: https://reviews.llvm.org/D112374
2021-10-11 18:15:36 +02:00
$0^class {} $1^x;
int (*$2^fptr)(int $3^a, int) = nullptr;
}
)cpp",
"0: targets = {(unnamed)}\n"
[clang] Implement ElaboratedType sugaring for types written bare Without this patch, clang will not wrap in an ElaboratedType node types written without a keyword and nested name qualifier, which goes against the intent that we should produce an AST which retains enough details to recover how things are written. The lack of this sugar is incompatible with the intent of the type printer default policy, which is to print types as written, but to fall back and print them fully qualified when they are desugared. An ElaboratedTypeLoc without keyword / NNS uses no storage by itself, but still requires pointer alignment due to pre-existing bug in the TypeLoc buffer handling. --- Troubleshooting list to deal with any breakage seen with this patch: 1) The most likely effect one would see by this patch is a change in how a type is printed. The type printer will, by design and default, print types as written. There are customization options there, but not that many, and they mainly apply to how to print a type that we somehow failed to track how it was written. This patch fixes a problem where we failed to distinguish between a type that was written without any elaborated-type qualifiers, such as a 'struct'/'class' tags and name spacifiers such as 'std::', and one that has been stripped of any 'metadata' that identifies such, the so called canonical types. Example: ``` namespace foo { struct A {}; A a; }; ``` If one were to print the type of `foo::a`, prior to this patch, this would result in `foo::A`. This is how the type printer would have, by default, printed the canonical type of A as well. As soon as you add any name qualifiers to A, the type printer would suddenly start accurately printing the type as written. This patch will make it print it accurately even when written without qualifiers, so we will just print `A` for the initial example, as the user did not really write that `foo::` namespace qualifier. 2) This patch could expose a bug in some AST matcher. Matching types is harder to get right when there is sugar involved. For example, if you want to match a type against being a pointer to some type A, then you have to account for getting a type that is sugar for a pointer to A, or being a pointer to sugar to A, or both! Usually you would get the second part wrong, and this would work for a very simple test where you don't use any name qualifiers, but you would discover is broken when you do. The usual fix is to either use the matcher which strips sugar, which is annoying to use as for example if you match an N level pointer, you have to put N+1 such matchers in there, beginning to end and between all those levels. But in a lot of cases, if the property you want to match is present in the canonical type, it's easier and faster to just match on that... This goes with what is said in 1), if you want to match against the name of a type, and you want the name string to be something stable, perhaps matching on the name of the canonical type is the better choice. 3) This patch could expose a bug in how you get the source range of some TypeLoc. For some reason, a lot of code is using getLocalSourceRange(), which only looks at the given TypeLoc node. This patch introduces a new, and more common TypeLoc node which contains no source locations on itself. This is not an inovation here, and some other, more rare TypeLoc nodes could also have this property, but if you use getLocalSourceRange on them, it's not going to return any valid locations, because it doesn't have any. The right fix here is to always use getSourceRange() or getBeginLoc/getEndLoc which will dive into the inner TypeLoc to get the source range if it doesn't find it on the top level one. You can use getLocalSourceRange if you are really into micro-optimizations and you have some outside knowledge that the TypeLocs you are dealing with will always include some source location. 4) Exposed a bug somewhere in the use of the normal clang type class API, where you have some type, you want to see if that type is some particular kind, you try a `dyn_cast` such as `dyn_cast<TypedefType>` and that fails because now you have an ElaboratedType which has a TypeDefType inside of it, which is what you wanted to match. Again, like 2), this would usually have been tested poorly with some simple tests with no qualifications, and would have been broken had there been any other kind of type sugar, be it an ElaboratedType or a TemplateSpecializationType or a SubstTemplateParmType. The usual fix here is to use `getAs` instead of `dyn_cast`, which will look deeper into the type. Or use `getAsAdjusted` when dealing with TypeLocs. For some reason the API is inconsistent there and on TypeLocs getAs behaves like a dyn_cast. 5) It could be a bug in this patch perhaps. Let me know if you need any help! Signed-off-by: Matheus Izvekov <mizvekov@gmail.com> Differential Revision: https://reviews.llvm.org/D112374
2021-10-11 18:15:36 +02:00
"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