frayien 147bce7858
[clangd] textDocument/documentLink to support include statements with macro argument (#137550)
See issue https://github.com/clangd/clangd/issues/2375

The clangd textDocument/documentLink request does not support include
statements with macro argument and fails by returning an invalid range.

This PR adds support for macro argument by detecting what form of
include statement is currently being processed and returning a range
accordingly

First time contributing to LLVM, so if you have advice, questions or if
I messed up procedure please tell me !

---------

Co-authored-by: Adrien Garbani <garbani.adrien@frayien.fr>
2025-08-04 11:37:37 +02:00

2824 lines
78 KiB
C++

//===-- XRefsTests.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 "Annotations.h"
#include "AST.h"
#include "ParsedAST.h"
#include "Protocol.h"
#include "SourceCode.h"
#include "SyncAPI.h"
#include "TestFS.h"
#include "TestTU.h"
#include "XRefs.h"
#include "index/MemIndex.h"
#include "clang/AST/Decl.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/ScopedPrinter.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <optional>
#include <string>
#include <vector>
namespace clang {
namespace clangd {
namespace {
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::Matcher;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
using ::testing::UnorderedPointwise;
std::string guard(llvm::StringRef Code) {
return "#pragma once\n" + Code.str();
}
MATCHER_P2(FileRange, File, Range, "") {
return Location{URIForFile::canonicalize(File, testRoot()), Range} == arg;
}
MATCHER(declRange, "") {
const LocatedSymbol &Sym = ::testing::get<0>(arg);
const Range &Range = ::testing::get<1>(arg);
return Sym.PreferredDeclaration.range == Range;
}
// Extracts ranges from an annotated example, and constructs a matcher for a
// highlight set. Ranges should be named $read/$write as appropriate.
Matcher<const std::vector<DocumentHighlight> &>
highlightsFrom(const Annotations &Test) {
std::vector<DocumentHighlight> Expected;
auto Add = [&](const Range &R, DocumentHighlightKind K) {
Expected.emplace_back();
Expected.back().range = R;
Expected.back().kind = K;
};
for (const auto &Range : Test.ranges())
Add(Range, DocumentHighlightKind::Text);
for (const auto &Range : Test.ranges("read"))
Add(Range, DocumentHighlightKind::Read);
for (const auto &Range : Test.ranges("write"))
Add(Range, DocumentHighlightKind::Write);
return UnorderedElementsAreArray(Expected);
}
TEST(HighlightsTest, All) {
const char *Tests[] = {
R"cpp(// Local variable
int main() {
int [[bonjour]];
$write[[^bonjour]] = 2;
int test1 = $read[[bonjour]];
}
)cpp",
R"cpp(// Struct
namespace ns1 {
struct [[MyClass]] {
static void foo([[MyClass]]*) {}
};
} // namespace ns1
int main() {
ns1::[[My^Class]]* Params;
}
)cpp",
R"cpp(// Function
int [[^foo]](int) { return 0; }
int main() {
[[foo]]([[foo]](42));
auto *X = &[[foo]];
}
)cpp",
R"cpp(// Function parameter in decl
void foo(int [[^bar]]);
)cpp",
R"cpp(// Not touching any identifiers.
struct Foo {
[[~]]Foo() {};
};
void foo() {
Foo f;
f.[[^~]]Foo();
}
)cpp",
R"cpp(// ObjC methods with split selectors.
@interface Foo
+(void) [[x]]:(int)a [[y]]:(int)b;
@end
@implementation Foo
+(void) [[x]]:(int)a [[y]]:(int)b {}
@end
void go() {
[Foo [[x]]:2 [[^y]]:4];
}
)cpp",
R"cpp( // Label
int main() {
goto [[^theLabel]];
[[theLabel]]:
return 1;
}
)cpp",
};
for (const char *Test : Tests) {
Annotations T(Test);
auto TU = TestTU::withCode(T.code());
TU.ExtraArgs.push_back("-xobjective-c++");
auto AST = TU.build();
EXPECT_THAT(findDocumentHighlights(AST, T.point()), highlightsFrom(T))
<< Test;
}
}
TEST(HighlightsTest, ControlFlow) {
const char *Tests[] = {
R"cpp(
// Highlight same-function returns.
int fib(unsigned n) {
if (n <= 1) [[ret^urn]] 1;
[[return]] fib(n - 1) + fib(n - 2);
// Returns from other functions not highlighted.
auto Lambda = [] { return; };
class LocalClass { void x() { return; } };
}
)cpp",
R"cpp(
#define FAIL() return false
#define DO(x) { x; }
bool foo(int n) {
if (n < 0) [[FAIL]]();
DO([[re^turn]] true)
}
)cpp",
R"cpp(
// Highlight loop control flow
int magic() {
int counter = 0;
[[^for]] (char c : "fruit loops!") {
if (c == ' ') [[continue]];
counter += c;
if (c == '!') [[break]];
if (c == '?') [[return]] -1;
}
return counter;
}
)cpp",
R"cpp(
// Highlight loop and same-loop control flow
void nonsense() {
[[while]] (true) {
if (false) [[bre^ak]];
switch (1) break;
[[continue]];
}
}
)cpp",
R"cpp(
// Highlight switch for break (but not other breaks).
void describe(unsigned n) {
[[switch]](n) {
case 0:
break;
[[default]]:
[[^break]];
}
}
)cpp",
R"cpp(
// Highlight case and exits for switch-break (but not other cases).
void describe(unsigned n) {
[[switch]](n) {
case 0:
break;
[[case]] 1:
[[default]]:
[[return]];
[[^break]];
}
}
)cpp",
R"cpp(
// Highlight exits and switch for case
void describe(unsigned n) {
[[switch]](n) {
case 0:
break;
[[case]] 1:
[[d^efault]]:
[[return]];
[[break]];
}
}
)cpp",
R"cpp(
// Highlight nothing for switch.
void describe(unsigned n) {
s^witch(n) {
case 0:
break;
case 1:
default:
return;
break;
}
}
)cpp",
R"cpp(
// FIXME: match exception type against catch blocks
int catchy() {
try { // wrong: highlight try with matching catch
try { // correct: has no matching catch
[[thr^ow]] "oh no!";
} catch (int) { } // correct: catch doesn't match type
[[return]] -1; // correct: exits the matching catch
} catch (const char*) { } // wrong: highlight matching catch
[[return]] 42; // wrong: throw doesn't exit function
}
)cpp",
R"cpp(
// Loop highlights goto exiting the loop, but not jumping within it.
void jumpy() {
[[wh^ile]](1) {
up:
if (0) [[goto]] out;
goto up;
}
out: return;
}
)cpp",
};
for (const char *Test : Tests) {
Annotations T(Test);
auto TU = TestTU::withCode(T.code());
TU.ExtraArgs.push_back("-fexceptions"); // FIXME: stop testing on PS4.
auto AST = TU.build();
EXPECT_THAT(findDocumentHighlights(AST, T.point()), highlightsFrom(T))
<< Test;
}
}
MATCHER_P3(sym, Name, Decl, DefOrNone, "") {
std::optional<Range> Def = DefOrNone;
if (Name != arg.Name) {
*result_listener << "Name is " << arg.Name;
return false;
}
if (Decl != arg.PreferredDeclaration.range) {
*result_listener << "Declaration is "
<< llvm::to_string(arg.PreferredDeclaration);
return false;
}
if (!Def && !arg.Definition)
return true;
if (Def && !arg.Definition) {
*result_listener << "Has no definition";
return false;
}
if (!Def && arg.Definition) {
*result_listener << "Definition is " << llvm::to_string(*arg.Definition);
return false;
}
if (arg.Definition->range != *Def) {
*result_listener << "Definition is " << llvm::to_string(*arg.Definition);
return false;
}
return true;
}
MATCHER_P(sym, Name, "") { return arg.Name == Name; }
MATCHER_P(rangeIs, R, "") { return arg.Loc.range == R; }
MATCHER_P(containerIs, C, "") {
return arg.Loc.containerName.value_or("") == C;
}
MATCHER_P(attrsAre, A, "") { return arg.Attributes == A; }
MATCHER_P(hasID, ID, "") { return arg.ID == ID; }
TEST(LocateSymbol, WithIndex) {
Annotations SymbolHeader(R"cpp(
class $forward[[Forward]];
class $foo[[Foo]] {};
void $f1[[f1]]();
inline void $f2[[f2]]() {}
)cpp");
Annotations SymbolCpp(R"cpp(
class $forward[[forward]] {};
void $f1[[f1]]() {}
)cpp");
TestTU TU;
TU.Code = std::string(SymbolCpp.code());
TU.HeaderCode = std::string(SymbolHeader.code());
auto Index = TU.index();
auto LocateWithIndex = [&Index](const Annotations &Main) {
auto AST = TestTU::withCode(Main.code()).build();
return clangd::locateSymbolAt(AST, Main.point(), Index.get());
};
Annotations Test(R"cpp(// only declaration in AST.
void [[f1]]();
int main() {
^f1();
}
)cpp");
EXPECT_THAT(LocateWithIndex(Test),
ElementsAre(sym("f1", Test.range(), SymbolCpp.range("f1"))));
Test = Annotations(R"cpp(// definition in AST.
void [[f1]]() {}
int main() {
^f1();
}
)cpp");
EXPECT_THAT(LocateWithIndex(Test),
ElementsAre(sym("f1", SymbolHeader.range("f1"), Test.range())));
Test = Annotations(R"cpp(// forward declaration in AST.
class [[Foo]];
F^oo* create();
)cpp");
EXPECT_THAT(LocateWithIndex(Test),
ElementsAre(sym("Foo", Test.range(), SymbolHeader.range("foo"))));
Test = Annotations(R"cpp(// definition in AST.
class [[Forward]] {};
F^orward create();
)cpp");
EXPECT_THAT(
LocateWithIndex(Test),
ElementsAre(sym("Forward", SymbolHeader.range("forward"), Test.range())));
}
TEST(LocateSymbol, AnonymousStructFields) {
auto Code = Annotations(R"cpp(
struct $2[[Foo]] {
struct { int $1[[x]]; };
void foo() {
// Make sure the implicit base is skipped.
$1^x = 42;
}
};
// Check that we don't skip explicit bases.
int a = $2^Foo{}.x;
)cpp");
TestTU TU = TestTU::withCode(Code.code());
auto AST = TU.build();
EXPECT_THAT(locateSymbolAt(AST, Code.point("1"), TU.index().get()),
UnorderedElementsAre(sym("x", Code.range("1"), Code.range("1"))));
EXPECT_THAT(
locateSymbolAt(AST, Code.point("2"), TU.index().get()),
UnorderedElementsAre(sym("Foo", Code.range("2"), Code.range("2"))));
}
TEST(LocateSymbol, FindOverrides) {
auto Code = Annotations(R"cpp(
class Foo {
virtual void $1[[fo^o]]() = 0;
};
class Bar : public Foo {
void $2[[foo]]() override;
};
)cpp");
TestTU TU = TestTU::withCode(Code.code());
auto AST = TU.build();
EXPECT_THAT(locateSymbolAt(AST, Code.point(), TU.index().get()),
UnorderedElementsAre(sym("foo", Code.range("1"), std::nullopt),
sym("foo", Code.range("2"), std::nullopt)));
}
TEST(LocateSymbol, FindOverridesFromDefObjC) {
auto Code = Annotations(R"objc(
@protocol Fooey
- (void)foo;
@end
@interface Base
- (void)foo;
@end
@interface Foo : Base<Fooey>
- (void)$1[[foo]];
@end
@interface Bar : Foo
- (void)$2[[foo]];
@end
@implementation Bar
- (void)$3[[fo^o]] {}
@end
)objc");
TestTU TU = TestTU::withCode(Code.code());
TU.ExtraArgs.push_back("-xobjective-c++");
auto AST = TU.build();
EXPECT_THAT(
locateSymbolAt(AST, Code.point(), TU.index().get()),
UnorderedElementsAre(sym("foo", Code.range("1"), std::nullopt),
sym("foo", Code.range("2"), Code.range("3"))));
}
TEST(LocateSymbol, NoOverridesFromDeclObjC) {
auto Code = Annotations(R"objc(
@protocol Fooey
- (void)foo;
@end
@interface Base
- (void)foo;
@end
@interface Foo : Base<Fooey>
- (void)foo;
@end
@interface Bar : Foo
- (void)$2[[fo^o]];
@end
@implementation Bar
- (void)$3[[foo]] {}
@end
)objc");
TestTU TU = TestTU::withCode(Code.code());
TU.ExtraArgs.push_back("-xobjective-c++");
auto AST = TU.build();
EXPECT_THAT(
locateSymbolAt(AST, Code.point(), TU.index().get()),
UnorderedElementsAre(sym("foo", Code.range("2"), Code.range("3"))));
}
TEST(LocateSymbol, ObjCNoOverridesOnUsage) {
auto Code = Annotations(R"objc(
@interface Foo
- (void)foo;
@end
@interface Bar : Foo
- (void)$1[[foo]];
@end
@implementation Bar
- (void)$2[[foo]] {}
@end
void doSomething(Bar *bar) {
[bar fo^o];
}
)objc");
TestTU TU = TestTU::withCode(Code.code());
TU.ExtraArgs.push_back("-xobjective-c++");
auto AST = TU.build();
EXPECT_THAT(
locateSymbolAt(AST, Code.point(), TU.index().get()),
UnorderedElementsAre(sym("foo", Code.range("1"), Code.range("2"))));
}
TEST(LocateSymbol, WithIndexPreferredLocation) {
Annotations SymbolHeader(R"cpp(
class $p[[Proto]] {};
void $f[[func]]() {};
)cpp");
TestTU TU;
TU.HeaderCode = std::string(SymbolHeader.code());
TU.HeaderFilename = "x.proto"; // Prefer locations in codegen files.
auto Index = TU.index();
Annotations Test(R"cpp(// only declaration in AST.
// Shift to make range different.
class Proto;
void func() {}
P$p^roto* create() {
fu$f^nc();
return nullptr;
}
)cpp");
auto AST = TestTU::withCode(Test.code()).build();
{
auto Locs = clangd::locateSymbolAt(AST, Test.point("p"), Index.get());
auto CodeGenLoc = SymbolHeader.range("p");
EXPECT_THAT(Locs, ElementsAre(sym("Proto", CodeGenLoc, CodeGenLoc)));
}
{
auto Locs = clangd::locateSymbolAt(AST, Test.point("f"), Index.get());
auto CodeGenLoc = SymbolHeader.range("f");
EXPECT_THAT(Locs, ElementsAre(sym("func", CodeGenLoc, CodeGenLoc)));
}
}
TEST(LocateSymbol, All) {
// Ranges in tests:
// $decl is the declaration location (if absent, no symbol is located)
// $def is the definition location (if absent, symbol has no definition)
// unnamed range becomes both $decl and $def.
const char *Tests[] = {
R"cpp(
struct X {
union {
int [[a]];
float b;
};
};
int test(X &x) {
return x.^a;
}
)cpp",
R"cpp(// Local variable
int main() {
int [[bonjour]];
^bonjour = 2;
int test1 = bonjour;
}
)cpp",
R"cpp(// Struct
namespace ns1 {
struct [[MyClass]] {};
} // namespace ns1
int main() {
ns1::My^Class* Params;
}
)cpp",
R"cpp(// Function definition via pointer
void [[foo]](int) {}
int main() {
auto *X = &^foo;
}
)cpp",
R"cpp(// Function declaration via call
int $decl[[foo]](int);
int main() {
return ^foo(42);
}
)cpp",
R"cpp(// Field
struct Foo { int [[x]]; };
int main() {
Foo bar;
(void)bar.^x;
}
)cpp",
R"cpp(// Field, member initializer
struct Foo {
int [[x]];
Foo() : ^x(0) {}
};
)cpp",
R"cpp(// Field, field designator
struct Foo { int [[x]]; };
int main() {
Foo bar = { .^x = 2 };
}
)cpp",
R"cpp(// Method call
struct Foo { int $decl[[x]](); };
int main() {
Foo bar;
bar.^x();
}
)cpp",
R"cpp(// Typedef
typedef int $decl[[Foo]];
int main() {
^Foo bar;
}
)cpp",
R"cpp(// Template type parameter
template <typename [[T]]>
void foo() { ^T t; }
)cpp",
R"cpp(// Template template type parameter
template <template<typename> class [[T]]>
void foo() { ^T<int> t; }
)cpp",
R"cpp(// Namespace
namespace $decl[[ns]] {
struct Foo { static void bar(); };
} // namespace ns
int main() { ^ns::Foo::bar(); }
)cpp",
R"cpp(// Macro
class TTT { public: int a; };
#define [[FF]](S) if (int b = S.a) {}
void f() {
TTT t;
F^F(t);
}
)cpp",
R"cpp(// Macro argument
int [[i]];
#define ADDRESSOF(X) &X;
int *j = ADDRESSOF(^i);
)cpp",
R"cpp(// Macro argument appearing multiple times in expansion
#define VALIDATE_TYPE(x) (void)x;
#define ASSERT(expr) \
do { \
VALIDATE_TYPE(expr); \
if (!expr); \
} while (false)
bool [[waldo]]() { return true; }
void foo() {
ASSERT(wa^ldo());
}
)cpp",
R"cpp(// Symbol concatenated inside macro (not supported)
int *pi;
#define POINTER(X) p ## X;
int x = *POINTER(^i);
)cpp",
R"cpp(// Forward class declaration
class $decl[[Foo]];
class $def[[Foo]] {};
F^oo* foo();
)cpp",
R"cpp(// Function declaration
void $decl[[foo]]();
void g() { f^oo(); }
void $def[[foo]]() {}
)cpp",
R"cpp(
#define FF(name) class name##_Test {};
[[FF]](my);
void f() { my^_Test a; }
)cpp",
R"cpp(
#define FF() class [[Test]] {};
FF();
void f() { T^est a; }
)cpp",
R"cpp(// explicit template specialization
template <typename T>
struct Foo { void bar() {} };
template <>
struct [[Foo]]<int> { void bar() {} };
void foo() {
Foo<char> abc;
Fo^o<int> b;
}
)cpp",
R"cpp(// implicit template specialization
template <typename T>
struct [[Foo]] { void bar() {} };
template <>
struct Foo<int> { void bar() {} };
void foo() {
Fo^o<char> abc;
Foo<int> b;
}
)cpp",
R"cpp(// partial template specialization
template <typename T>
struct Foo { void bar() {} };
template <typename T>
struct [[Foo]]<T*> { void bar() {} };
^Foo<int*> x;
)cpp",
R"cpp(// function template specializations
template <class T>
void foo(T) {}
template <>
void [[foo]](int) {}
void bar() {
fo^o(10);
}
)cpp",
R"cpp(// variable template decls
template <class T>
T var = T();
template <>
double [[var]]<int> = 10;
double y = va^r<int>;
)cpp",
R"cpp(// No implicit constructors
struct X {
X(X&& x) = default;
};
X $decl[[makeX]]();
void foo() {
auto x = m^akeX();
}
)cpp",
R"cpp(
struct X {
X& $decl[[operator]]++();
};
void foo(X& x) {
+^+x;
}
)cpp",
R"cpp(
struct S1 { void f(); };
struct S2 { S1 * $decl[[operator]]->(); };
void test(S2 s2) {
s2-^>f();
}
)cpp",
R"cpp(// Declaration of explicit template specialization
template <typename T>
struct $decl[[$def[[Foo]]]] {};
template <>
struct Fo^o<int> {};
)cpp",
R"cpp(// Declaration of partial template specialization
template <typename T>
struct $decl[[$def[[Foo]]]] {};
template <typename T>
struct Fo^o<T*> {};
)cpp",
R"cpp(// Definition on ClassTemplateDecl
namespace ns {
// Forward declaration.
template<typename T>
struct $decl[[Foo]];
template <typename T>
struct $def[[Foo]] {};
}
using ::ns::Fo^o;
)cpp",
R"cpp(// auto builtin type (not supported)
^auto x = 42;
)cpp",
R"cpp(// auto on lambda
auto x = [[[]]]{};
^auto y = x;
)cpp",
R"cpp(// auto on struct
namespace ns1 {
struct [[S1]] {};
} // namespace ns1
^auto x = ns1::S1{};
)cpp",
R"cpp(// decltype on struct
namespace ns1 {
struct [[S1]] {};
} // namespace ns1
ns1::S1 i;
^decltype(i) j;
)cpp",
R"cpp(// decltype(auto) on struct
namespace ns1 {
struct [[S1]] {};
} // namespace ns1
ns1::S1 i;
ns1::S1& j = i;
^decltype(auto) k = j;
)cpp",
R"cpp(// auto on template class
template<typename T> class [[Foo]] {};
^auto x = Foo<int>();
)cpp",
R"cpp(// auto on template class with forward declared class
template<typename T> class [[Foo]] {};
class X;
^auto x = Foo<X>();
)cpp",
R"cpp(// auto on specialized template class
template<typename T> class Foo {};
template<> class [[Foo]]<int> {};
^auto x = Foo<int>();
)cpp",
R"cpp(// auto on initializer list.
namespace std
{
template<class _E>
class [[initializer_list]] { const _E *a, *b; };
}
^auto i = {1,2};
)cpp",
R"cpp(// auto function return with trailing type
struct [[Bar]] {};
^auto test() -> decltype(Bar()) {
return Bar();
}
)cpp",
R"cpp(// decltype in trailing return type
struct [[Bar]] {};
auto test() -> ^decltype(Bar()) {
return Bar();
}
)cpp",
R"cpp(// auto in function return
struct [[Bar]] {};
^auto test() {
return Bar();
}
)cpp",
R"cpp(// auto& in function return
struct [[Bar]] {};
^auto& test() {
static Bar x;
return x;
}
)cpp",
R"cpp(// auto* in function return
struct [[Bar]] {};
^auto* test() {
Bar* x;
return x;
}
)cpp",
R"cpp(// const auto& in function return
struct [[Bar]] {};
const ^auto& test() {
static Bar x;
return x;
}
)cpp",
R"cpp(// auto lambda param where there's a single instantiation
struct [[Bar]] {};
auto Lambda = [](^auto){ return 0; };
int x = Lambda(Bar{});
)cpp",
R"cpp(// decltype(auto) in function return
struct [[Bar]] {};
^decltype(auto) test() {
return Bar();
}
)cpp",
R"cpp(// decltype of function with trailing return type.
struct [[Bar]] {};
auto test() -> decltype(Bar()) {
return Bar();
}
void foo() {
^decltype(test()) i = test();
}
)cpp",
R"cpp(// Override specifier jumps to overridden method
class Y { virtual void $decl[[a]]() = 0; };
class X : Y { void a() ^override {} };
)cpp",
R"cpp(// Final specifier jumps to overridden method
class Y { virtual void $decl[[a]]() = 0; };
class X : Y { void a() ^final {} };
)cpp",
R"cpp(// Heuristic resolution of dependent method
template <typename T>
struct S {
void [[bar]]() {}
};
template <typename T>
void foo(S<T> arg) {
arg.ba^r();
}
)cpp",
R"cpp(// Heuristic resolution of dependent method via this->
template <typename T>
struct S {
void [[foo]]() {
this->fo^o();
}
};
)cpp",
R"cpp(// Heuristic resolution of dependent static method
template <typename T>
struct S {
static void [[bar]]() {}
};
template <typename T>
void foo() {
S<T>::ba^r();
}
)cpp",
R"cpp(// Heuristic resolution of dependent method
// invoked via smart pointer
template <typename> struct S { void [[foo]]() {} };
template <typename T> struct unique_ptr {
T* operator->();
};
template <typename T>
void test(unique_ptr<S<T>>& V) {
V->fo^o();
}
)cpp",
R"cpp(// Heuristic resolution of dependent enumerator
template <typename T>
struct Foo {
enum class E { [[A]], B };
E e = E::A^;
};
)cpp",
R"cpp(// Enum base
typedef int $decl[[MyTypeDef]];
enum Foo : My^TypeDef {};
)cpp",
R"cpp(// Enum base
typedef int $decl[[MyTypeDef]];
enum Foo : My^TypeDef;
)cpp",
R"cpp(// Enum base
using $decl[[MyTypeDef]] = int;
enum Foo : My^TypeDef {};
)cpp",
R"objc(
@protocol Dog;
@protocol $decl[[Dog]]
- (void)bark;
@end
id<Do^g> getDoggo() {
return 0;
}
)objc",
R"objc(
@interface Cat
@end
@implementation Cat
@end
@interface $decl[[Cat]] (Exte^nsion)
- (void)meow;
@end
@implementation $def[[Cat]] (Extension)
- (void)meow {}
@end
)objc",
R"objc(
@class $decl[[Foo]];
Fo^o * getFoo() {
return 0;
}
)objc",
R"objc(// Prefer interface definition over forward declaration
@class Foo;
@interface $decl[[Foo]]
@end
Fo^o * getFoo() {
return 0;
}
)objc",
R"objc(
@class Foo;
@interface $decl[[Foo]]
@end
@implementation $def[[Foo]]
@end
Fo^o * getFoo() {
return 0;
}
)objc",
R"objc(// Method decl and definition for ObjC class.
@interface Cat
- (void)$decl[[meow]];
@end
@implementation Cat
- (void)$def[[meow]] {}
@end
void makeNoise(Cat *kitty) {
[kitty me^ow];
}
)objc",
R"objc(// Method decl and definition for ObjC category.
@interface Dog
@end
@interface Dog (Play)
- (void)$decl[[runAround]];
@end
@implementation Dog (Play)
- (void)$def[[runAround]] {}
@end
void play(Dog *dog) {
[dog run^Around];
}
)objc",
R"objc(// Method decl and definition for ObjC class extension.
@interface Dog
@end
@interface Dog ()
- (void)$decl[[howl]];
@end
@implementation Dog
- (void)$def[[howl]] {}
@end
void play(Dog *dog) {
[dog ho^wl];
}
)objc",
R"cpp(
struct PointerIntPairInfo {
static void *$decl[[getPointer]](void *Value);
};
template <typename Info = PointerIntPairInfo> struct PointerIntPair {
void *Value;
void *getPointer() const { return Info::get^Pointer(Value); }
};
)cpp",
R"cpp(// Deducing this
struct S {
int bar(this S&);
};
void foo() {
S [[waldo]];
int x = wa^ldo.bar();
}
)cpp"};
for (const char *Test : Tests) {
Annotations T(Test);
std::optional<Range> WantDecl;
std::optional<Range> WantDef;
if (!T.ranges().empty())
WantDecl = WantDef = T.range();
if (!T.ranges("decl").empty())
WantDecl = T.range("decl");
if (!T.ranges("def").empty())
WantDef = T.range("def");
TestTU TU;
TU.Code = std::string(T.code());
TU.ExtraArgs.push_back("-xobjective-c++");
TU.ExtraArgs.push_back("-std=c++23");
auto AST = TU.build();
auto Results = locateSymbolAt(AST, T.point());
if (!WantDecl) {
EXPECT_THAT(Results, IsEmpty()) << Test;
} else {
ASSERT_THAT(Results, ::testing::SizeIs(1)) << Test;
EXPECT_EQ(Results[0].PreferredDeclaration.range, *WantDecl) << Test;
EXPECT_TRUE(Results[0].ID) << Test;
std::optional<Range> GotDef;
if (Results[0].Definition)
GotDef = Results[0].Definition->range;
EXPECT_EQ(WantDef, GotDef) << Test;
}
}
}
TEST(LocateSymbol, ValidSymbolID) {
auto T = Annotations(R"cpp(
#define MACRO(x, y) ((x) + (y))
int add(int x, int y) { return $MACRO^MACRO(x, y); }
int sum = $add^add(1, 2);
)cpp");
TestTU TU = TestTU::withCode(T.code());
auto AST = TU.build();
auto Index = TU.index();
EXPECT_THAT(locateSymbolAt(AST, T.point("add"), Index.get()),
ElementsAre(AllOf(sym("add"),
hasID(getSymbolID(&findDecl(AST, "add"))))));
EXPECT_THAT(
locateSymbolAt(AST, T.point("MACRO"), Index.get()),
ElementsAre(AllOf(sym("MACRO"),
hasID(findSymbol(TU.headerSymbols(), "MACRO").ID))));
}
TEST(LocateSymbol, AllMulti) {
// Ranges in tests:
// $declN is the declaration location
// $defN is the definition location (if absent, symbol has no definition)
//
// NOTE:
// N starts at 0.
struct ExpectedRanges {
Range WantDecl;
std::optional<Range> WantDef;
};
const char *Tests[] = {
R"objc(
@interface $decl0[[Cat]]
@end
@implementation $def0[[Cat]]
@end
@interface $decl1[[Ca^t]] (Extension)
- (void)meow;
@end
@implementation $def1[[Cat]] (Extension)
- (void)meow {}
@end
)objc",
R"objc(
@interface $decl0[[Cat]]
@end
@implementation $def0[[Cat]]
@end
@interface $decl1[[Cat]] (Extension)
- (void)meow;
@end
@implementation $def1[[Ca^t]] (Extension)
- (void)meow {}
@end
)objc",
R"objc(
@interface $decl0[[Cat]]
@end
@interface $decl1[[Ca^t]] ()
- (void)meow;
@end
@implementation $def0[[$def1[[Cat]]]]
- (void)meow {}
@end
)objc",
};
for (const char *Test : Tests) {
Annotations T(Test);
std::vector<ExpectedRanges> Ranges;
for (int Idx = 0; true; Idx++) {
bool HasDecl = !T.ranges("decl" + std::to_string(Idx)).empty();
bool HasDef = !T.ranges("def" + std::to_string(Idx)).empty();
if (!HasDecl && !HasDef)
break;
ExpectedRanges Range;
if (HasDecl)
Range.WantDecl = T.range("decl" + std::to_string(Idx));
if (HasDef)
Range.WantDef = T.range("def" + std::to_string(Idx));
Ranges.push_back(Range);
}
TestTU TU;
TU.Code = std::string(T.code());
TU.ExtraArgs.push_back("-xobjective-c++");
auto AST = TU.build();
auto Results = locateSymbolAt(AST, T.point());
ASSERT_THAT(Results, ::testing::SizeIs(Ranges.size())) << Test;
for (size_t Idx = 0; Idx < Ranges.size(); Idx++) {
EXPECT_EQ(Results[Idx].PreferredDeclaration.range, Ranges[Idx].WantDecl)
<< "($decl" << Idx << ")" << Test;
std::optional<Range> GotDef;
if (Results[Idx].Definition)
GotDef = Results[Idx].Definition->range;
EXPECT_EQ(GotDef, Ranges[Idx].WantDef) << "($def" << Idx << ")" << Test;
}
}
}
// LocateSymbol test cases that produce warnings.
// These are separated out from All so that in All we can assert
// that there are no diagnostics.
TEST(LocateSymbol, Warnings) {
const char *Tests[] = {
R"cpp(// Field, GNU old-style field designator
struct Foo { int [[x]]; };
int main() {
Foo bar = { ^x : 1 };
}
)cpp",
R"cpp(// Macro
#define MACRO 0
#define [[MACRO]] 1
int main() { return ^MACRO; }
#define MACRO 2
#undef macro
)cpp",
};
for (const char *Test : Tests) {
Annotations T(Test);
std::optional<Range> WantDecl;
std::optional<Range> WantDef;
if (!T.ranges().empty())
WantDecl = WantDef = T.range();
if (!T.ranges("decl").empty())
WantDecl = T.range("decl");
if (!T.ranges("def").empty())
WantDef = T.range("def");
TestTU TU;
TU.Code = std::string(T.code());
auto AST = TU.build();
auto Results = locateSymbolAt(AST, T.point());
if (!WantDecl) {
EXPECT_THAT(Results, IsEmpty()) << Test;
} else {
ASSERT_THAT(Results, ::testing::SizeIs(1)) << Test;
EXPECT_EQ(Results[0].PreferredDeclaration.range, *WantDecl) << Test;
std::optional<Range> GotDef;
if (Results[0].Definition)
GotDef = Results[0].Definition->range;
EXPECT_EQ(WantDef, GotDef) << Test;
}
}
}
TEST(LocateSymbol, TextualSmoke) {
auto T = Annotations(
R"cpp(
struct [[MyClass]] {};
// Comment mentioning M^yClass
)cpp");
auto TU = TestTU::withCode(T.code());
auto AST = TU.build();
auto Index = TU.index();
EXPECT_THAT(
locateSymbolAt(AST, T.point(), Index.get()),
ElementsAre(AllOf(sym("MyClass", T.range(), T.range()),
hasID(getSymbolID(&findDecl(AST, "MyClass"))))));
}
TEST(LocateSymbol, Textual) {
const char *Tests[] = {
R"cpp(// Comment
struct [[MyClass]] {};
// Comment mentioning M^yClass
)cpp",
R"cpp(// String
struct MyClass {};
// Not triggered for string literal tokens.
const char* s = "String literal mentioning M^yClass";
)cpp",
R"cpp(// Ifdef'ed out code
struct [[MyClass]] {};
#ifdef WALDO
M^yClass var;
#endif
)cpp",
R"cpp(// Macro definition
struct [[MyClass]] {};
#define DECLARE_MYCLASS_OBJ(name) M^yClass name;
)cpp",
R"cpp(// Invalid code
/*error-ok*/
int myFunction(int);
// Not triggered for token which survived preprocessing.
int var = m^yFunction();
)cpp"};
for (const char *Test : Tests) {
Annotations T(Test);
std::optional<Range> WantDecl;
if (!T.ranges().empty())
WantDecl = T.range();
auto TU = TestTU::withCode(T.code());
auto AST = TU.build();
auto Index = TU.index();
auto Word = SpelledWord::touching(
cantFail(sourceLocationInMainFile(AST.getSourceManager(), T.point())),
AST.getTokens(), AST.getLangOpts());
if (!Word) {
ADD_FAILURE() << "No word touching point!" << Test;
continue;
}
auto Results = locateSymbolTextually(*Word, AST, Index.get(),
testPath(TU.Filename), ASTNodeKind());
if (!WantDecl) {
EXPECT_THAT(Results, IsEmpty()) << Test;
} else {
ASSERT_THAT(Results, ::testing::SizeIs(1)) << Test;
EXPECT_EQ(Results[0].PreferredDeclaration.range, *WantDecl) << Test;
}
}
} // namespace
TEST(LocateSymbol, Ambiguous) {
auto T = Annotations(R"cpp(
struct Foo {
Foo();
Foo(Foo&&);
$ConstructorLoc[[Foo]](const char*);
};
Foo f();
void g(Foo foo);
void call() {
const char* str = "123";
Foo a = $1^str;
Foo b = Foo($2^str);
Foo c = $3^f();
$4^g($5^f());
g($6^str);
Foo ab$7^c;
Foo ab$8^cd("asdf");
Foo foox = Fo$9^o("asdf");
Foo abcde$10^("asdf");
Foo foox2 = Foo$11^("asdf");
}
template <typename T>
struct S {
void $NonstaticOverload1[[bar]](int);
void $NonstaticOverload2[[bar]](float);
static void $StaticOverload1[[baz]](int);
static void $StaticOverload2[[baz]](float);
};
template <typename T, typename U>
void dependent_call(S<T> s, U u) {
s.ba$12^r(u);
S<T>::ba$13^z(u);
}
)cpp");
auto TU = TestTU::withCode(T.code());
// FIXME: Go-to-definition in a template requires disabling delayed template
// parsing.
TU.ExtraArgs.push_back("-fno-delayed-template-parsing");
auto AST = TU.build();
// Ordered assertions are deliberate: we expect a predictable order.
EXPECT_THAT(locateSymbolAt(AST, T.point("1")), ElementsAre(sym("str")));
EXPECT_THAT(locateSymbolAt(AST, T.point("2")), ElementsAre(sym("str")));
EXPECT_THAT(locateSymbolAt(AST, T.point("3")), ElementsAre(sym("f")));
EXPECT_THAT(locateSymbolAt(AST, T.point("4")), ElementsAre(sym("g")));
EXPECT_THAT(locateSymbolAt(AST, T.point("5")), ElementsAre(sym("f")));
EXPECT_THAT(locateSymbolAt(AST, T.point("6")), ElementsAre(sym("str")));
// FIXME: Target the constructor as well.
EXPECT_THAT(locateSymbolAt(AST, T.point("7")), ElementsAre(sym("abc")));
// FIXME: Target the constructor as well.
EXPECT_THAT(locateSymbolAt(AST, T.point("8")), ElementsAre(sym("abcd")));
// FIXME: Target the constructor as well.
EXPECT_THAT(locateSymbolAt(AST, T.point("9")), ElementsAre(sym("Foo")));
EXPECT_THAT(locateSymbolAt(AST, T.point("10")),
ElementsAre(sym("Foo", T.range("ConstructorLoc"), std::nullopt)));
EXPECT_THAT(locateSymbolAt(AST, T.point("11")),
ElementsAre(sym("Foo", T.range("ConstructorLoc"), std::nullopt)));
// These assertions are unordered because the order comes from
// CXXRecordDecl::lookupDependentName() which doesn't appear to provide
// an order guarantee.
EXPECT_THAT(locateSymbolAt(AST, T.point("12")),
UnorderedElementsAre(
sym("bar", T.range("NonstaticOverload1"), std::nullopt),
sym("bar", T.range("NonstaticOverload2"), std::nullopt)));
EXPECT_THAT(locateSymbolAt(AST, T.point("13")),
UnorderedElementsAre(
sym("baz", T.range("StaticOverload1"), std::nullopt),
sym("baz", T.range("StaticOverload2"), std::nullopt)));
}
TEST(LocateSymbol, TextualDependent) {
// Put the declarations in the header to make sure we are
// finding them via the index heuristic and not the
// nearby-ident heuristic.
Annotations Header(R"cpp(
struct Foo {
void $FooLoc[[uniqueMethodName]]();
};
struct Bar {
void $BarLoc[[uniqueMethodName]]();
};
)cpp");
Annotations Source(R"cpp(
template <typename T>
void f(T t) {
t.u^niqueMethodName();
}
)cpp");
TestTU TU;
TU.Code = std::string(Source.code());
TU.HeaderCode = std::string(Header.code());
auto AST = TU.build();
auto Index = TU.index();
// Need to use locateSymbolAt() since we are testing an
// interaction between locateASTReferent() and
// locateSymbolNamedTextuallyAt().
auto Results = locateSymbolAt(AST, Source.point(), Index.get());
EXPECT_THAT(
Results,
UnorderedElementsAre(
sym("uniqueMethodName", Header.range("FooLoc"), std::nullopt),
sym("uniqueMethodName", Header.range("BarLoc"), std::nullopt)));
}
TEST(LocateSymbol, Alias) {
const char *Tests[] = {
R"cpp(
template <class T> struct function {};
template <class T> using [[callback]] = function<T()>;
c^allback<int> foo;
)cpp",
// triggered on non-definition of a renaming alias: should not give any
// underlying decls.
R"cpp(
class Foo {};
typedef Foo [[Bar]];
B^ar b;
)cpp",
R"cpp(
class Foo {};
using [[Bar]] = Foo; // definition
Ba^r b;
)cpp",
// triggered on the underlying decl of a renaming alias.
R"cpp(
class [[Foo]];
using Bar = Fo^o;
)cpp",
// triggered on definition of a non-renaming alias: should give underlying
// decls.
R"cpp(
namespace ns { class [[Foo]] {}; }
using ns::F^oo;
)cpp",
R"cpp(
namespace ns { int [[x]](char); int [[x]](double); }
using ns::^x;
)cpp",
R"cpp(
namespace ns { int [[x]](char); int x(double); }
using ns::[[x]];
int y = ^x('a');
)cpp",
R"cpp(
namespace ns { class [[Foo]] {}; }
using ns::[[Foo]];
F^oo f;
)cpp",
// other cases that don't matter much.
R"cpp(
class Foo {};
typedef Foo [[Ba^r]];
)cpp",
R"cpp(
class Foo {};
using [[B^ar]] = Foo;
)cpp",
// Member of dependent base
R"cpp(
template <typename T>
struct Base {
void [[waldo]]() {}
};
template <typename T>
struct Derived : Base<T> {
using Base<T>::w^aldo;
};
)cpp",
};
for (const auto *Case : Tests) {
SCOPED_TRACE(Case);
auto T = Annotations(Case);
auto AST = TestTU::withCode(T.code()).build();
EXPECT_THAT(locateSymbolAt(AST, T.point()),
UnorderedPointwise(declRange(), T.ranges()));
}
}
TEST(LocateSymbol, RelPathsInCompileCommand) {
// The source is in "/clangd-test/src".
// We build in "/clangd-test/build".
Annotations SourceAnnotations(R"cpp(
#include "header_in_preamble.h"
int [[foo]];
#include "header_not_in_preamble.h"
int baz = f$p1^oo + bar_pre$p2^amble + bar_not_pre$p3^amble;
)cpp");
Annotations HeaderInPreambleAnnotations(R"cpp(
int [[bar_preamble]];
)cpp");
Annotations HeaderNotInPreambleAnnotations(R"cpp(
int [[bar_not_preamble]];
)cpp");
// Make the compilation paths appear as ../src/foo.cpp in the compile
// commands.
SmallString<32> RelPathPrefix("..");
llvm::sys::path::append(RelPathPrefix, "src");
std::string BuildDir = testPath("build");
MockCompilationDatabase CDB(BuildDir, RelPathPrefix);
MockFS FS;
ClangdServer Server(CDB, FS, ClangdServer::optsForTest());
// Fill the filesystem.
auto FooCpp = testPath("src/foo.cpp");
FS.Files[FooCpp] = "";
auto HeaderInPreambleH = testPath("src/header_in_preamble.h");
FS.Files[HeaderInPreambleH] = std::string(HeaderInPreambleAnnotations.code());
auto HeaderNotInPreambleH = testPath("src/header_not_in_preamble.h");
FS.Files[HeaderNotInPreambleH] =
std::string(HeaderNotInPreambleAnnotations.code());
runAddDocument(Server, FooCpp, SourceAnnotations.code());
// Go to a definition in main source file.
auto Locations =
runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("p1"));
EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(*Locations, ElementsAre(sym("foo", SourceAnnotations.range(),
SourceAnnotations.range())));
// Go to a definition in header_in_preamble.h.
Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("p2"));
EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(
*Locations,
ElementsAre(sym("bar_preamble", HeaderInPreambleAnnotations.range(),
HeaderInPreambleAnnotations.range())));
// Go to a definition in header_not_in_preamble.h.
Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("p3"));
EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(*Locations,
ElementsAre(sym("bar_not_preamble",
HeaderNotInPreambleAnnotations.range(),
HeaderNotInPreambleAnnotations.range())));
}
TEST(GoToInclude, All) {
MockFS FS;
MockCompilationDatabase CDB;
ClangdServer Server(CDB, FS, ClangdServer::optsForTest());
auto FooCpp = testPath("foo.cpp");
const char *SourceContents = R"cpp(
#include ^"$2^foo.h$3^"
#include "$4^invalid.h"
int b = a;
// test
int foo;
#in$5^clude "$6^foo.h"$7^
)cpp";
Annotations SourceAnnotations(SourceContents);
FS.Files[FooCpp] = std::string(SourceAnnotations.code());
auto FooH = testPath("foo.h");
const char *HeaderContents = R"cpp([[]]#pragma once
int a;
)cpp";
Annotations HeaderAnnotations(HeaderContents);
FS.Files[FooH] = std::string(HeaderAnnotations.code());
runAddDocument(Server, FooH, HeaderAnnotations.code());
runAddDocument(Server, FooCpp, SourceAnnotations.code());
// Test include in preamble.
auto Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point());
ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error";
EXPECT_THAT(*Locations, ElementsAre(sym("foo.h", HeaderAnnotations.range(),
HeaderAnnotations.range())));
// Test include in preamble, last char.
Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("2"));
ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error";
EXPECT_THAT(*Locations, ElementsAre(sym("foo.h", HeaderAnnotations.range(),
HeaderAnnotations.range())));
Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("3"));
ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error";
EXPECT_THAT(*Locations, ElementsAre(sym("foo.h", HeaderAnnotations.range(),
HeaderAnnotations.range())));
// Test include outside of preamble.
Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("6"));
ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error";
EXPECT_THAT(*Locations, ElementsAre(sym("foo.h", HeaderAnnotations.range(),
HeaderAnnotations.range())));
// Test a few positions that do not result in Locations.
Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("4"));
ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error";
EXPECT_THAT(*Locations, IsEmpty());
Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("5"));
ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error";
EXPECT_THAT(*Locations, ElementsAre(sym("foo.h", HeaderAnnotations.range(),
HeaderAnnotations.range())));
Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("7"));
ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error";
EXPECT_THAT(*Locations, ElementsAre(sym("foo.h", HeaderAnnotations.range(),
HeaderAnnotations.range())));
// Objective C #import directive.
Annotations ObjC(R"objc(
#import "^foo.h"
)objc");
auto FooM = testPath("foo.m");
FS.Files[FooM] = std::string(ObjC.code());
runAddDocument(Server, FooM, ObjC.code());
Locations = runLocateSymbolAt(Server, FooM, ObjC.point());
ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error";
EXPECT_THAT(*Locations, ElementsAre(sym("foo.h", HeaderAnnotations.range(),
HeaderAnnotations.range())));
}
TEST(LocateSymbol, WithPreamble) {
// Test stragety: AST should always use the latest preamble instead of last
// good preamble.
MockFS FS;
MockCompilationDatabase CDB;
ClangdServer Server(CDB, FS, ClangdServer::optsForTest());
auto FooCpp = testPath("foo.cpp");
// The trigger locations must be the same.
Annotations FooWithHeader(R"cpp(#include "fo^o.h")cpp");
Annotations FooWithoutHeader(R"cpp(double [[fo^o]]();)cpp");
FS.Files[FooCpp] = std::string(FooWithHeader.code());
auto FooH = testPath("foo.h");
Annotations FooHeader(R"cpp([[]])cpp");
FS.Files[FooH] = std::string(FooHeader.code());
runAddDocument(Server, FooCpp, FooWithHeader.code());
// LocateSymbol goes to a #include file: the result comes from the preamble.
EXPECT_THAT(
cantFail(runLocateSymbolAt(Server, FooCpp, FooWithHeader.point())),
ElementsAre(sym("foo.h", FooHeader.range(), FooHeader.range())));
// Only preamble is built, and no AST is built in this request.
Server.addDocument(FooCpp, FooWithoutHeader.code(), "null",
WantDiagnostics::No);
// We build AST here, and it should use the latest preamble rather than the
// stale one.
EXPECT_THAT(
cantFail(runLocateSymbolAt(Server, FooCpp, FooWithoutHeader.point())),
ElementsAre(sym("foo", FooWithoutHeader.range(), std::nullopt)));
// Reset test environment.
runAddDocument(Server, FooCpp, FooWithHeader.code());
// Both preamble and AST are built in this request.
Server.addDocument(FooCpp, FooWithoutHeader.code(), "null",
WantDiagnostics::Yes);
// Use the AST being built in above request.
EXPECT_THAT(
cantFail(runLocateSymbolAt(Server, FooCpp, FooWithoutHeader.point())),
ElementsAre(sym("foo", FooWithoutHeader.range(), std::nullopt)));
}
TEST(LocateSymbol, NearbyTokenSmoke) {
auto T = Annotations(R"cpp(
// prints e^rr and crashes
void die(const char* [[err]]);
)cpp");
auto AST = TestTU::withCode(T.code()).build();
// We don't pass an index, so can't hit index-based fallback.
EXPECT_THAT(locateSymbolAt(AST, T.point()),
ElementsAre(sym("err", T.range(), T.range())));
}
TEST(LocateSymbol, NearbyIdentifier) {
const char *Tests[] = {
R"cpp(
// regular identifiers (won't trigger)
int hello;
int y = he^llo;
)cpp",
R"cpp(
// disabled preprocessor sections
int [[hello]];
#if 0
int y = ^hello;
#endif
)cpp",
R"cpp(
// comments
// he^llo, world
int [[hello]];
)cpp",
R"cpp(
// not triggered by string literals
int hello;
const char* greeting = "h^ello, world";
)cpp",
R"cpp(
// can refer to macro invocations
#define INT int
[[INT]] x;
// I^NT
)cpp",
R"cpp(
// can refer to macro invocations (even if they expand to nothing)
#define EMPTY
[[EMPTY]] int x;
// E^MPTY
)cpp",
R"cpp(
// prefer nearest occurrence, backwards is worse than forwards
int hello;
int x = hello;
// h^ello
int y = [[hello]];
int z = hello;
)cpp",
R"cpp(
// short identifiers find near results
int [[hi]];
// h^i
)cpp",
R"cpp(
// short identifiers don't find far results
int hi;
// h^i
int x = hi;
)cpp",
R"cpp(
// prefer nearest occurrence even if several matched tokens
// have the same value of `floor(log2(<token line> - <word line>))`.
int hello;
int x = hello, y = hello;
int z = [[hello]];
// h^ello
)cpp"};
for (const char *Test : Tests) {
Annotations T(Test);
auto AST = TestTU::withCode(T.code()).build();
const auto &SM = AST.getSourceManager();
std::optional<Range> Nearby;
auto Word =
SpelledWord::touching(cantFail(sourceLocationInMainFile(SM, T.point())),
AST.getTokens(), AST.getLangOpts());
if (!Word) {
ADD_FAILURE() << "No word at point! " << Test;
continue;
}
if (const auto *Tok = findNearbyIdentifier(*Word, AST.getTokens()))
Nearby = halfOpenToRange(SM, CharSourceRange::getCharRange(
Tok->location(), Tok->endLocation()));
if (T.ranges().empty())
EXPECT_THAT(Nearby, Eq(std::nullopt)) << Test;
else
EXPECT_EQ(Nearby, T.range()) << Test;
}
}
TEST(FindImplementations, Inheritance) {
llvm::StringRef Test = R"cpp(
struct $0^Base {
virtual void F$1^oo();
void C$4^oncrete();
};
struct $0[[Child1]] : Base {
void $1[[Fo$3^o]]() override;
virtual void B$2^ar();
void Concrete(); // No implementations for concrete methods.
};
struct Child2 : Child1 {
void $3[[Foo]]() override;
void $2[[Bar]]() override;
};
void FromReference() {
$0^Base* B;
B->Fo$1^o();
B->C$4^oncrete();
&Base::Fo$1^o;
Child1 * C1;
C1->B$2^ar();
C1->Fo$3^o();
}
// CRTP should work.
template<typename T>
struct $5^TemplateBase {};
struct $5[[Child3]] : public TemplateBase<Child3> {};
// Local classes.
void LocationFunction() {
struct $0[[LocalClass1]] : Base {
void $1[[Foo]]() override;
};
struct $6^LocalBase {
virtual void $7^Bar();
};
struct $6[[LocalClass2]]: LocalBase {
void $7[[Bar]]() override;
};
}
)cpp";
Annotations Code(Test);
auto TU = TestTU::withCode(Code.code());
auto AST = TU.build();
auto Index = TU.index();
for (StringRef Label : {"0", "1", "2", "3", "4", "5", "6", "7"}) {
for (const auto &Point : Code.points(Label)) {
EXPECT_THAT(findImplementations(AST, Point, Index.get()),
UnorderedPointwise(declRange(), Code.ranges(Label)))
<< Code.code() << " at " << Point << " for Label " << Label;
}
}
}
TEST(FindImplementations, InheritanceObjC) {
llvm::StringRef Test = R"objc(
@interface $base^Base
- (void)fo$foo^o;
@end
@protocol Protocol
- (void)$protocol^protocol;
@end
@interface $ChildDecl[[Child]] : Base <Protocol>
- (void)concrete;
- (void)$fooDecl[[foo]];
@end
@implementation $ChildDef[[Child]]
- (void)concrete {}
- (void)$fooDef[[foo]] {}
- (void)$protocolDef[[protocol]] {}
@end
)objc";
Annotations Code(Test);
auto TU = TestTU::withCode(Code.code());
TU.ExtraArgs.push_back("-xobjective-c++");
auto AST = TU.build();
auto Index = TU.index();
EXPECT_THAT(findImplementations(AST, Code.point("base"), Index.get()),
UnorderedElementsAre(sym("Child", Code.range("ChildDecl"),
Code.range("ChildDef"))));
EXPECT_THAT(findImplementations(AST, Code.point("foo"), Index.get()),
UnorderedElementsAre(
sym("foo", Code.range("fooDecl"), Code.range("fooDef"))));
EXPECT_THAT(findImplementations(AST, Code.point("protocol"), Index.get()),
UnorderedElementsAre(sym("protocol", Code.range("protocolDef"),
Code.range("protocolDef"))));
}
TEST(FindImplementations, CaptureDefinition) {
llvm::StringRef Test = R"cpp(
struct Base {
virtual void F^oo();
};
struct Child1 : Base {
void $Decl[[Foo]]() override;
};
struct Child2 : Base {
void $Child2[[Foo]]() override;
};
void Child1::$Def[[Foo]]() { /* Definition */ }
)cpp";
Annotations Code(Test);
auto TU = TestTU::withCode(Code.code());
auto AST = TU.build();
EXPECT_THAT(
findImplementations(AST, Code.point(), TU.index().get()),
UnorderedElementsAre(sym("Foo", Code.range("Decl"), Code.range("Def")),
sym("Foo", Code.range("Child2"), std::nullopt)))
<< Test;
}
TEST(FindType, All) {
Annotations HeaderA(R"cpp(
struct $Target[[Target]] { operator int() const; };
struct Aggregate { Target a, b; };
Target t;
Target make();
template <typename T> struct $smart_ptr[[smart_ptr]] {
T& operator*();
T* operator->();
T* get();
};
)cpp");
auto TU = TestTU::withHeaderCode(HeaderA.code());
for (const llvm::StringRef Case : {
"str^uct Target;",
"T^arget x;",
"Target ^x;",
"a^uto x = Target{};",
"namespace m { Target tgt; } auto x = m^::tgt;",
"Target funcCall(); auto x = ^funcCall();",
"Aggregate a = { {}, ^{} };",
"Aggregate a = { ^.a=t, };",
"struct X { Target a; X() : ^a() {} };",
"^using T = Target; ^T foo();",
"^template <int> Target foo();",
"void x() { try {} ^catch(Target e) {} }",
"void x() { ^throw t; }",
"int x() { ^return t; }",
"void x() { ^switch(t) {} }",
"void x() { ^delete (Target*)nullptr; }",
"Target& ^tref = t;",
"void x() { ^if (t) {} }",
"void x() { ^while (t) {} }",
"void x() { ^do { } while (t); }",
"void x() { ^make(); }",
"void x(smart_ptr<Target> &t) { t.^get(); }",
"^auto x = []() { return t; };",
"Target* ^tptr = &t;",
"Target ^tarray[3];",
}) {
Annotations A(Case);
TU.Code = A.code().str();
ParsedAST AST = TU.build();
ASSERT_GT(A.points().size(), 0u) << Case;
for (auto Pos : A.points())
EXPECT_THAT(findType(AST, Pos, nullptr),
ElementsAre(
sym("Target", HeaderA.range("Target"), HeaderA.range("Target"))))
<< Case;
}
for (const llvm::StringRef Case : {
"smart_ptr<Target> ^tsmart;",
}) {
Annotations A(Case);
TU.Code = A.code().str();
ParsedAST AST = TU.build();
EXPECT_THAT(findType(AST, A.point(), nullptr),
UnorderedElementsAre(
sym("Target", HeaderA.range("Target"), HeaderA.range("Target")),
sym("smart_ptr", HeaderA.range("smart_ptr"), HeaderA.range("smart_ptr"))
))
<< Case;
}
}
TEST(FindType, Definition) {
Annotations A(R"cpp(
class $decl[[X]];
X *^x;
class $def[[X]] {};
)cpp");
auto TU = TestTU::withCode(A.code().str());
ParsedAST AST = TU.build();
EXPECT_THAT(findType(AST, A.point(), nullptr),
ElementsAre(sym("X", A.range("decl"), A.range("def"))));
}
TEST(FindType, Index) {
Annotations Def(R"cpp(
// This definition is only available through the index.
class [[X]] {};
)cpp");
TestTU DefTU = TestTU::withHeaderCode(Def.code());
DefTU.HeaderFilename = "def.h";
auto DefIdx = DefTU.index();
Annotations A(R"cpp(
class [[X]];
X *^x;
)cpp");
auto TU = TestTU::withCode(A.code().str());
ParsedAST AST = TU.build();
EXPECT_THAT(findType(AST, A.point(), DefIdx.get()),
ElementsAre(sym("X", A.range(), Def.range())));
}
void checkFindRefs(llvm::StringRef Test, bool UseIndex = false) {
Annotations T(Test);
auto TU = TestTU::withCode(T.code());
TU.ExtraArgs.push_back("-std=c++20");
TU.ExtraArgs.push_back("-xobjective-c++");
auto AST = TU.build();
std::vector<Matcher<ReferencesResult::Reference>> ExpectedLocations;
for (const auto &[R, Context] : T.rangesWithPayload())
ExpectedLocations.push_back(
AllOf(rangeIs(R), containerIs(Context), attrsAre(0u)));
// $def is actually shorthand for both definition and declaration.
// If we have cases that are definition-only, we should change this.
for (const auto &[R, Context] : T.rangesWithPayload("def"))
ExpectedLocations.push_back(AllOf(rangeIs(R), containerIs(Context),
attrsAre(ReferencesResult::Definition |
ReferencesResult::Declaration)));
for (const auto &[R, Context] : T.rangesWithPayload("decl"))
ExpectedLocations.push_back(AllOf(rangeIs(R), containerIs(Context),
attrsAre(ReferencesResult::Declaration)));
for (const auto &[R, Context] : T.rangesWithPayload("overridedecl"))
ExpectedLocations.push_back(AllOf(
rangeIs(R), containerIs(Context),
attrsAre(ReferencesResult::Declaration | ReferencesResult::Override)));
for (const auto &[R, Context] : T.rangesWithPayload("overridedef"))
ExpectedLocations.push_back(AllOf(rangeIs(R), containerIs(Context),
attrsAre(ReferencesResult::Declaration |
ReferencesResult::Definition |
ReferencesResult::Override)));
for (const auto &P : T.points()) {
EXPECT_THAT(findReferences(AST, P, 0, UseIndex ? TU.index().get() : nullptr,
/*AddContext*/ true)
.References,
UnorderedElementsAreArray(ExpectedLocations))
<< "Failed for Refs at " << P << "\n"
<< Test;
}
}
TEST(FindReferences, WithinAST) {
const char *Tests[] = {
R"cpp(// Local variable
int main() {
int $def(main)[[foo]];
$(main)[[^foo]] = 2;
int test1 = $(main)[[foo]];
}
)cpp",
R"cpp(// Struct
namespace ns1 {
struct $def(ns1)[[Foo]] {};
} // namespace ns1
int main() {
ns1::$(main)[[Fo^o]]* Params;
}
)cpp",
R"cpp(// Forward declaration
class $decl[[Foo]];
class $def[[Foo]] {};
int main() {
$(main)[[Fo^o]] foo;
}
)cpp",
R"cpp(// Function
int $def[[foo]](int) { return 0; }
int main() {
auto *X = &$(main)[[^foo]];
$(main)[[foo]](42);
}
)cpp",
R"cpp(// Field
struct Foo {
int $def(Foo)[[foo]];
Foo() : $(Foo::Foo)[[foo]](0) {}
};
int main() {
Foo f;
f.$(main)[[f^oo]] = 1;
}
)cpp",
R"cpp(// Method call
struct Foo { int $decl(Foo)[[foo]](); };
int Foo::$def(Foo)[[foo]]() { return 0; }
int main() {
Foo f;
f.$(main)[[^foo]]();
}
)cpp",
R"cpp(// Constructor
struct Foo {
$decl(Foo)[[F^oo]](int);
};
void foo() {
Foo f = $(foo)[[Foo]](42);
}
)cpp",
R"cpp(// Typedef
typedef int $def[[Foo]];
int main() {
$(main)[[^Foo]] bar;
}
)cpp",
R"cpp(// Namespace
namespace $decl[[ns]] { // FIXME: def?
struct Foo {};
} // namespace ns
int main() { $(main)[[^ns]]::Foo foo; }
)cpp",
R"cpp(// Macros
#define TYPE(X) X
#define FOO Foo
#define CAT(X, Y) X##Y
class $def[[Fo^o]] {};
void test() {
TYPE($(test)[[Foo]]) foo;
$(test)[[FOO]] foo2;
TYPE(TYPE($(test)[[Foo]])) foo3;
$(test)[[CAT]](Fo, o) foo4;
}
)cpp",
R"cpp(// Macros
#define $def[[MA^CRO]](X) (X+1)
void test() {
int x = $[[MACRO]]($[[MACRO]](1));
}
)cpp",
R"cpp(// Macro outside preamble
int breakPreamble;
#define $def[[MA^CRO]](X) (X+1)
void test() {
int x = $[[MACRO]]($[[MACRO]](1));
}
)cpp",
R"cpp(
int $def[[v^ar]] = 0;
void foo(int s = $(foo)[[var]]);
)cpp",
R"cpp(
template <typename T>
class $def[[Fo^o]] {};
void func($(func)[[Foo]]<int>);
)cpp",
R"cpp(
template <typename T>
class $def[[Foo]] {};
void func($(func)[[Fo^o]]<int>);
)cpp",
R"cpp(// Not touching any identifiers.
struct Foo {
$def(Foo)[[~]]Foo() {};
};
void foo() {
Foo f;
f.$(foo)[[^~]]Foo();
}
)cpp",
R"cpp(// Lambda capture initializer
void foo() {
int $def(foo)[[w^aldo]] = 42;
auto lambda = [x = $(foo)[[waldo]]](){};
}
)cpp",
R"cpp(// Renaming alias
template <typename> class Vector {};
using $def[[^X]] = Vector<int>;
$(x1)[[X]] x1;
Vector<int> x2;
Vector<double> y;
)cpp",
R"cpp(// Dependent code
template <typename T> void $decl[[foo]](T t);
template <typename T> void bar(T t) { $(bar)[[foo]](t); } // foo in bar is uninstantiated.
void baz(int x) { $(baz)[[f^oo]](x); }
)cpp",
R"cpp(
namespace ns {
struct S{};
void $decl(ns)[[foo]](S s);
} // namespace ns
template <typename T> void foo(T t);
// FIXME: Maybe report this foo as a ref to ns::foo (because of ADL)
// when bar<ns::S> is instantiated?
template <typename T> void bar(T t) { foo(t); }
void baz(int x) {
ns::S s;
bar<ns::S>(s);
$(baz)[[f^oo]](s);
}
)cpp",
R"cpp(// unresolved member expression
struct Foo {
template <typename T> void $decl(Foo)[[b^ar]](T t);
};
template <typename T> void test(Foo F, T t) {
F.$(test)[[bar]](t);
}
)cpp",
// Enum base
R"cpp(
typedef int $def[[MyTypeD^ef]];
enum MyEnum : $(MyEnum)[[MyTy^peDef]] { };
)cpp",
R"cpp(
typedef int $def[[MyType^Def]];
enum MyEnum : $(MyEnum)[[MyTypeD^ef]];
)cpp",
R"cpp(
using $def[[MyTypeD^ef]] = int;
enum MyEnum : $(MyEnum)[[MyTy^peDef]] { };
)cpp",
// UDL
R"cpp(
bool $decl[[operator]]"" _u^dl(unsigned long long value);
bool x = $(x)[[1_udl]];
)cpp",
R"cpp(
struct S {
public:
static void $decl(S)[[operator]] delete(void *);
static void deleteObject(S *S) {
$(S::deleteObject)[[de^lete]] S;
}
};
)cpp",
// Array designators
R"cpp(
const int $def[[F^oo]] = 0;
int Bar[] = {
[$(Bar)[[F^oo]]...$(Bar)[[Fo^o]] + 1] = 0,
[$(Bar)[[^Foo]] + 2] = 1
};
)cpp"};
for (const char *Test : Tests)
checkFindRefs(Test);
}
TEST(FindReferences, ConceptsWithinAST) {
constexpr llvm::StringLiteral Code = R"cpp(
template <class T>
concept $def[[IsSmal^l]] = sizeof(T) <= 8;
template <class T>
concept IsSmallPtr = requires(T x) {
{ *x } -> $(IsSmallPtr)[[IsSmal^l]];
};
$(i)[[IsSmall]] auto i = 'c';
template<$(foo)[[IsSmal^l]] U> void foo();
template<class U> void bar() requires $(bar)[[IsSmal^l]]<U>;
template<class U> requires $(baz)[[IsSmal^l]]<U> void baz();
static_assert([[IsSma^ll]]<char>);
)cpp";
checkFindRefs(Code);
}
TEST(FindReferences, ConceptReq) {
constexpr llvm::StringLiteral Code = R"cpp(
template <class T>
concept $def[[IsSmal^l]] = sizeof(T) <= 8;
template <class T>
concept IsSmallPtr = requires(T x) {
{ *x } -> $(IsSmallPtr)[[IsSmal^l]];
};
)cpp";
checkFindRefs(Code);
}
TEST(FindReferences, RequiresExprParameters) {
constexpr llvm::StringLiteral Code = R"cpp(
template <class T>
concept IsSmall = sizeof(T) <= 8;
template <class T>
concept IsSmallPtr = requires(T $def[[^x]]) {
{ *$(IsSmallPtr)[[^x]] } -> IsSmall;
};
)cpp";
checkFindRefs(Code);
}
TEST(FindReferences, IncludeOverrides) {
llvm::StringRef Test =
R"cpp(
class Base {
public:
virtu^al void $decl(Base)[[f^unc]]() ^= ^0;
};
class Derived : public Base {
public:
void $overridedecl(Derived::func)[[func]]() override;
};
void Derived::$overridedef[[func]]() {}
class Derived2 : public Base {
void $overridedef(Derived2::func)[[func]]() override {}
};
void test(Derived* D, Base* B) {
D->func(); // No references to the overrides.
B->$(test)[[func]]();
})cpp";
checkFindRefs(Test, /*UseIndex=*/true);
}
TEST(FindReferences, IncludeOverridesObjC) {
llvm::StringRef Test =
R"objc(
@interface Base
- (void)$decl(Base)[[f^unc]];
@end
@interface Derived : Base
- (void)$overridedecl(Derived::func)[[func]];
@end
@implementation Derived
- (void)$overridedef[[func]] {}
@end
void test(Derived *derived, Base *base) {
[derived func]; // No references to the overrides.
[base $(test)[[func]]];
})objc";
checkFindRefs(Test, /*UseIndex=*/true);
}
TEST(FindReferences, RefsToBaseMethod) {
llvm::StringRef Test =
R"cpp(
class BaseBase {
public:
virtual void $(BaseBase)[[func]]();
};
class Base : public BaseBase {
public:
void $(Base)[[func]]() override;
};
class Derived : public Base {
public:
void $decl(Derived)[[fu^nc]]() over^ride;
};
void test(BaseBase* BB, Base* B, Derived* D) {
// refs to overridden methods in complete type hierarchy are reported.
BB->$(test)[[func]]();
B->$(test)[[func]]();
D->$(test)[[fu^nc]]();
})cpp";
checkFindRefs(Test, /*UseIndex=*/true);
}
TEST(FindReferences, RefsToBaseMethodObjC) {
llvm::StringRef Test =
R"objc(
@interface BaseBase
- (void)$(BaseBase)[[func]];
@end
@interface Base : BaseBase
- (void)$(Base)[[func]];
@end
@interface Derived : Base
- (void)$decl(Derived)[[fu^nc]];
@end
void test(BaseBase *bb, Base *b, Derived *d) {
// refs to overridden methods in complete type hierarchy are reported.
[bb $(test)[[func]]];
[b $(test)[[func]]];
[d $(test)[[fu^nc]]];
})objc";
checkFindRefs(Test, /*UseIndex=*/true);
}
TEST(FindReferences, MainFileReferencesOnly) {
llvm::StringRef Test =
R"cpp(
void test() {
int [[fo^o]] = 1;
// refs not from main file should not be included.
#include "foo.inc"
})cpp";
Annotations Code(Test);
auto TU = TestTU::withCode(Code.code());
TU.AdditionalFiles["foo.inc"] = R"cpp(
foo = 3;
)cpp";
auto AST = TU.build();
std::vector<Matcher<ReferencesResult::Reference>> ExpectedLocations;
for (const auto &R : Code.ranges())
ExpectedLocations.push_back(rangeIs(R));
EXPECT_THAT(findReferences(AST, Code.point(), 0).References,
ElementsAreArray(ExpectedLocations))
<< Test;
}
TEST(FindReferences, ExplicitSymbols) {
const char *Tests[] = {
R"cpp(
struct Foo { Foo* $decl(Foo)[[self]]() const; };
void f() {
Foo foo;
if (Foo* T = foo.$(f)[[^self]]()) {} // Foo member call expr.
}
)cpp",
R"cpp(
struct Foo { Foo(int); };
Foo f() {
int $def(f)[[b]];
return $(f)[[^b]]; // Foo constructor expr.
}
)cpp",
R"cpp(
struct Foo {};
void g(Foo);
Foo $decl[[f]]();
void call() {
g($(call)[[^f]]()); // Foo constructor expr.
}
)cpp",
R"cpp(
void $decl[[foo]](int);
void $decl[[foo]](double);
namespace ns {
using ::$decl(ns)[[fo^o]];
}
)cpp",
R"cpp(
struct X {
operator bool();
};
int test() {
X $def(test)[[a]];
$(test)[[a]].operator bool();
if ($(test)[[a^]]) {} // ignore implicit conversion-operator AST node
return 0;
}
)cpp",
};
for (const char *Test : Tests)
checkFindRefs(Test);
}
TEST(FindReferences, UsedSymbolsFromInclude) {
const char *Tests[] = {
R"cpp( [[#include ^"bar.h"]]
#include <vector>
int fstBar = [[bar1]]();
int sndBar = [[bar2]]();
[[Bar]] bar;
int macroBar = [[BAR]];
std::vector<int> vec;
)cpp",
R"cpp([[#in^clude <vector>]]
std::[[vector]]<int> vec;
)cpp",
R"cpp(
[[#include ^"udl_header.h"]]
auto x = [[1_b]];
)cpp",
};
for (const char *Test : Tests) {
Annotations T(Test);
auto TU = TestTU::withCode(T.code());
TU.ExtraArgs.push_back("-std=c++20");
TU.AdditionalFiles["bar.h"] = guard(R"cpp(
#define BAR 5
int bar1();
int bar2();
class Bar {};
)cpp");
TU.AdditionalFiles["system/vector"] = guard(R"cpp(
namespace std {
template<typename>
class vector{};
}
)cpp");
TU.AdditionalFiles["udl_header.h"] = guard(R"cpp(
bool operator"" _b(unsigned long long value);
)cpp");
TU.ExtraArgs.push_back("-isystem" + testPath("system"));
auto AST = TU.build();
std::vector<Matcher<ReferencesResult::Reference>> ExpectedLocations;
for (const auto &R : T.ranges())
ExpectedLocations.push_back(AllOf(rangeIs(R), attrsAre(0u)));
for (const auto &P : T.points())
EXPECT_THAT(findReferences(AST, P, 0).References,
UnorderedElementsAreArray(ExpectedLocations))
<< "Failed for Refs at " << P << "\n"
<< Test;
}
}
TEST(FindReferences, NeedsIndexForSymbols) {
const char *Header = "int foo();";
Annotations Main("int main() { [[f^oo]](); }");
TestTU TU;
TU.Code = std::string(Main.code());
TU.HeaderCode = Header;
auto AST = TU.build();
// References in main file are returned without index.
EXPECT_THAT(
findReferences(AST, Main.point(), 0, /*Index=*/nullptr).References,
ElementsAre(rangeIs(Main.range())));
Annotations IndexedMain(R"cpp(
int $decl[[foo]]() { return 42; }
void bar() { $bar(bar)[[foo]](); }
struct S { void bar() { $S(S::bar)[[foo]](); } };
namespace N { void bar() { $N(N::bar)[[foo]](); } }
)cpp");
// References from indexed files are included.
TestTU IndexedTU;
IndexedTU.Code = std::string(IndexedMain.code());
IndexedTU.Filename = "Indexed.cpp";
IndexedTU.HeaderCode = Header;
EXPECT_THAT(
findReferences(AST, Main.point(), 0, IndexedTU.index().get(),
/*AddContext*/ true)
.References,
ElementsAre(
rangeIs(Main.range()),
AllOf(rangeIs(IndexedMain.range("decl")),
attrsAre(ReferencesResult::Declaration |
ReferencesResult::Definition)),
AllOf(rangeIs(IndexedMain.range("bar")), containerIs("bar")),
AllOf(rangeIs(IndexedMain.range("S")), containerIs("S::bar")),
AllOf(rangeIs(IndexedMain.range("N")), containerIs("N::bar"))));
auto LimitRefs =
findReferences(AST, Main.point(), /*Limit*/ 1, IndexedTU.index().get());
EXPECT_EQ(1u, LimitRefs.References.size());
EXPECT_TRUE(LimitRefs.HasMore);
// Avoid indexed results for the main file. Use AST for the mainfile.
TU.Code = ("\n\n" + Main.code()).str();
EXPECT_THAT(findReferences(AST, Main.point(), 0, TU.index().get()).References,
ElementsAre(rangeIs(Main.range())));
}
TEST(FindReferences, NeedsIndexForMacro) {
const char *Header = "#define MACRO(X) (X+1)";
Annotations Main(R"cpp(
int main() {
int a = [[MA^CRO]](1);
}
)cpp");
TestTU TU;
TU.Code = std::string(Main.code());
TU.HeaderCode = Header;
auto AST = TU.build();
// References in main file are returned without index.
EXPECT_THAT(
findReferences(AST, Main.point(), 0, /*Index=*/nullptr).References,
ElementsAre(rangeIs(Main.range())));
Annotations IndexedMain(R"cpp(
int indexed_main() {
int a = [[MACRO]](1);
return 0;
}
)cpp");
// References from indexed files are included.
TestTU IndexedTU;
IndexedTU.Code = std::string(IndexedMain.code());
IndexedTU.Filename = "Indexed.cpp";
IndexedTU.HeaderCode = Header;
EXPECT_THAT(
findReferences(AST, Main.point(), 0, IndexedTU.index().get()).References,
ElementsAre(rangeIs(Main.range()), rangeIs(IndexedMain.range())));
auto LimitRefs =
findReferences(AST, Main.point(), /*Limit*/ 1, IndexedTU.index().get());
EXPECT_EQ(1u, LimitRefs.References.size());
EXPECT_TRUE(LimitRefs.HasMore);
}
TEST(FindReferences, NoQueryForLocalSymbols) {
struct RecordingIndex : public MemIndex {
mutable std::optional<llvm::DenseSet<SymbolID>> RefIDs;
bool refs(const RefsRequest &Req,
llvm::function_ref<void(const Ref &)>) const override {
RefIDs = Req.IDs;
return false;
}
};
struct Test {
StringRef AnnotatedCode;
bool WantQuery;
} Tests[] = {
{"int ^x;", true},
// For now we don't assume header structure which would allow skipping.
{"namespace { int ^x; }", true},
{"static int ^x;", true},
// Anything in a function certainly can't be referenced though.
{"void foo() { int ^x; }", false},
{"void foo() { struct ^x{}; }", false},
{"auto lambda = []{ int ^x; };", false},
};
for (Test T : Tests) {
Annotations File(T.AnnotatedCode);
RecordingIndex Rec;
auto AST = TestTU::withCode(File.code()).build();
findReferences(AST, File.point(), 0, &Rec);
if (T.WantQuery)
EXPECT_NE(Rec.RefIDs, std::nullopt) << T.AnnotatedCode;
else
EXPECT_EQ(Rec.RefIDs, std::nullopt) << T.AnnotatedCode;
}
}
TEST(GetNonLocalDeclRefs, All) {
struct Case {
llvm::StringRef AnnotatedCode;
std::vector<std::string> ExpectedDecls;
} Cases[] = {
{
// VarDecl and ParamVarDecl
R"cpp(
void bar();
void ^foo(int baz) {
int x = 10;
bar();
})cpp",
{"bar"},
},
{
// Method from class
R"cpp(
class Foo { public: void foo(); };
class Bar {
void foo();
void bar();
};
void Bar::^foo() {
Foo f;
bar();
f.foo();
})cpp",
{"Bar", "Bar::bar", "Foo", "Foo::foo"},
},
{
// Local types
R"cpp(
void ^foo() {
class Foo { public: void foo() {} };
class Bar { public: void bar() {} };
Foo f;
Bar b;
b.bar();
f.foo();
})cpp",
{},
},
{
// Template params
R"cpp(
template <typename T, template<typename> class Q>
void ^foo() {
T x;
Q<T> y;
})cpp",
{},
},
};
for (const Case &C : Cases) {
Annotations File(C.AnnotatedCode);
auto AST = TestTU::withCode(File.code()).build();
SourceLocation SL = llvm::cantFail(
sourceLocationInMainFile(AST.getSourceManager(), File.point()));
const FunctionDecl *FD =
llvm::dyn_cast<FunctionDecl>(&findDecl(AST, [SL](const NamedDecl &ND) {
return ND.getLocation() == SL && llvm::isa<FunctionDecl>(ND);
}));
ASSERT_NE(FD, nullptr);
auto NonLocalDeclRefs = getNonLocalDeclRefs(AST, FD);
std::vector<std::string> Names;
for (const Decl *D : NonLocalDeclRefs) {
if (const auto *ND = llvm::dyn_cast<NamedDecl>(D))
Names.push_back(ND->getQualifiedNameAsString());
}
EXPECT_THAT(Names, UnorderedElementsAreArray(C.ExpectedDecls))
<< File.code();
}
}
TEST(DocumentLinks, All) {
Annotations MainCpp(R"cpp(
#define HEADER_AA "faa.h"
#define HEADER_BB "fbb.h"
#define GET_HEADER(X) HEADER_ ## X
#/*comments*/include /*comments*/ $foo[["foo.h"]] //more comments
int end_of_preamble = 0;
#include $bar[[<bar.h>]]
#include $AA[[GET_HEADER]](AA) // Some comment !
# /* What about */ \
include /* multiple line */ \
$BB[[GET_HEADER]]( /* statements ? */ \
BB /* :) */ )
)cpp");
TestTU TU;
TU.Code = std::string(MainCpp.code());
TU.AdditionalFiles = {
{"faa.h", ""}, {"fbb.h", ""}, {"foo.h", ""}, {"bar.h", ""}};
TU.ExtraArgs = {"-isystem."};
auto AST = TU.build();
EXPECT_THAT(
clangd::getDocumentLinks(AST),
ElementsAre(
DocumentLink({MainCpp.range("foo"),
URIForFile::canonicalize(testPath("foo.h"), "")}),
DocumentLink({MainCpp.range("bar"),
URIForFile::canonicalize(testPath("bar.h"), "")}),
DocumentLink({MainCpp.range("AA"),
URIForFile::canonicalize(testPath("faa.h"), "")}),
DocumentLink({MainCpp.range("BB"),
URIForFile::canonicalize(testPath("fbb.h"), "")})));
}
} // namespace
} // namespace clangd
} // namespace clang