
Summary: LSP now reflects the declaration/definition distinction. Language server changes: - textDocument/definition now returns a definition if one is found, otherwise the declaration. It no longer returns declaration + definition if they are distinct. - textDocument/declaration returns the best declaration we can find. - For macros, the active macro definition is returned for both methods. - For include directive, the top of the target file is returned for both. There doesn't appear to be a discovery mechanism (we can't return everything to clients that only know about definition), so this changes existing behavior. In practice, it should greatly reduce the fraction of the time we need to show the user a menu of options. C++ API changes: - findDefinitions is replaced by locateSymbolAt, which returns a vector<LocatedSymbol> - one for each symbol under the cursor. - this contains the preferred declaration, the definition (if found), and the symbol name This API enables some potentially-neat extensions, like swapping between decl and def, and exposing the symbol name to the UI in the case of multiple symbols. Reviewers: hokein Subscribers: ilya-biryukov, javed.absar, ioeric, MaskRay, jkorous, arphaman, kadircet, cfe-commits Differential Revision: https://reviews.llvm.org/D57388 llvm-svn: 352864
1371 lines
36 KiB
C++
1371 lines
36 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 "ClangdUnit.h"
|
|
#include "Compiler.h"
|
|
#include "Matchers.h"
|
|
#include "SyncAPI.h"
|
|
#include "TestFS.h"
|
|
#include "TestTU.h"
|
|
#include "XRefs.h"
|
|
#include "index/FileIndex.h"
|
|
#include "index/SymbolCollector.h"
|
|
#include "clang/Index/IndexingAction.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/ScopedPrinter.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
namespace {
|
|
|
|
using testing::ElementsAre;
|
|
using testing::IsEmpty;
|
|
using testing::Matcher;
|
|
using testing::UnorderedElementsAreArray;
|
|
|
|
class IgnoreDiagnostics : public DiagnosticsConsumer {
|
|
void onDiagnosticsReady(PathRef File,
|
|
std::vector<Diag> Diagnostics) override {}
|
|
};
|
|
|
|
MATCHER_P2(FileRange, File, Range, "") {
|
|
return Location{URIForFile::canonicalize(File, testRoot()), Range} == arg;
|
|
}
|
|
|
|
// 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) {}
|
|
int main() {
|
|
[[foo]]([[foo]](42));
|
|
auto *X = &[[foo]];
|
|
}
|
|
)cpp",
|
|
};
|
|
for (const char *Test : Tests) {
|
|
Annotations T(Test);
|
|
auto AST = TestTU::withCode(T.code()).build();
|
|
EXPECT_THAT(findDocumentHighlights(AST, T.point()), HighlightsFrom(T))
|
|
<< Test;
|
|
}
|
|
}
|
|
|
|
MATCHER_P3(Sym, Name, Decl, DefOrNone, "") {
|
|
llvm::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) {
|
|
*result_listener << "Has no definition";
|
|
return false;
|
|
}
|
|
if (Def && arg.Definition->range != *Def) {
|
|
*result_listener << "Definition is " << llvm::to_string(arg.Definition);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
testing::Matcher<LocatedSymbol> Sym(std::string Name, Range Decl) {
|
|
return Sym(Name, Decl, llvm::None);
|
|
}
|
|
MATCHER_P(Sym, Name, "") { return arg.Name == Name; }
|
|
|
|
MATCHER_P(RangeIs, R, "") { return arg.range == R; }
|
|
|
|
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 = SymbolCpp.code();
|
|
TU.HeaderCode = 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(// defintion in AST.
|
|
class [[Forward]] {};
|
|
F^orward create();
|
|
)cpp");
|
|
EXPECT_THAT(
|
|
LocateWithIndex(Test),
|
|
ElementsAre(Sym("Forward", SymbolHeader.range("forward"), Test.range())));
|
|
}
|
|
|
|
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(// 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
|
|
int [[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;
|
|
bar.^x;
|
|
}
|
|
)cpp",
|
|
|
|
R"cpp(// Field, member initializer
|
|
struct Foo {
|
|
int [[x]];
|
|
Foo() : ^x(0) {}
|
|
};
|
|
)cpp",
|
|
|
|
R"cpp(// Field, GNU old-style field designator
|
|
struct Foo { int [[x]]; };
|
|
int main() {
|
|
Foo bar = { ^x : 1 };
|
|
}
|
|
)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",
|
|
|
|
/* FIXME: clangIndex doesn't handle template type parameters
|
|
R"cpp(// Template type parameter
|
|
template <[[typename T]]>
|
|
void foo() { ^T t; }
|
|
)cpp", */
|
|
|
|
R"cpp(// Namespace
|
|
namespace $decl[[ns]] {
|
|
struct Foo { static void bar(); }
|
|
} // namespace ns
|
|
int main() { ^ns::Foo::bar(); }
|
|
)cpp",
|
|
|
|
R"cpp(// Macro
|
|
#define MACRO 0
|
|
#define [[MACRO]] 1
|
|
int main() { return ^MACRO; }
|
|
#define MACRO 2
|
|
#undef macro
|
|
)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(// Symbol concatenated inside macro (not supported)
|
|
int *pi;
|
|
#define POINTER(X) p # X;
|
|
int i = *POINTER(^i);
|
|
)cpp",
|
|
|
|
R"cpp(// Forward class declaration
|
|
class Foo;
|
|
class [[Foo]] {};
|
|
F^oo* foo();
|
|
)cpp",
|
|
|
|
R"cpp(// Function declaration
|
|
void foo();
|
|
void g() { f^oo(); }
|
|
void [[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",
|
|
};
|
|
for (const char *Test : Tests) {
|
|
Annotations T(Test);
|
|
llvm::Optional<Range> WantDecl;
|
|
llvm::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");
|
|
|
|
auto AST = TestTU::withCode(T.code()).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;
|
|
llvm::Optional<Range> GotDef;
|
|
if (Results[0].Definition)
|
|
GotDef = Results[0].Definition->range;
|
|
EXPECT_EQ(WantDef, GotDef) << Test;
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(LocateSymbol, Ambiguous) {
|
|
auto T = Annotations(R"cpp(
|
|
struct Foo {
|
|
Foo();
|
|
Foo(Foo&&);
|
|
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);
|
|
}
|
|
)cpp");
|
|
auto AST = TestTU::withCode(T.code()).build();
|
|
// Ordered assertions are deliberate: we expect a predictable order.
|
|
EXPECT_THAT(locateSymbolAt(AST, T.point("1")),
|
|
ElementsAre(Sym("str"), Sym("Foo")));
|
|
EXPECT_THAT(locateSymbolAt(AST, T.point("2")), ElementsAre(Sym("str")));
|
|
EXPECT_THAT(locateSymbolAt(AST, T.point("3")),
|
|
ElementsAre(Sym("f"), Sym("Foo")));
|
|
EXPECT_THAT(locateSymbolAt(AST, T.point("4")), ElementsAre(Sym("g")));
|
|
EXPECT_THAT(locateSymbolAt(AST, T.point("5")),
|
|
ElementsAre(Sym("f"), Sym("Foo")));
|
|
EXPECT_THAT(locateSymbolAt(AST, T.point("6")),
|
|
ElementsAre(Sym("str"), Sym("Foo"), Sym("Foo")));
|
|
}
|
|
|
|
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);
|
|
|
|
IgnoreDiagnostics DiagConsumer;
|
|
MockFSProvider FS;
|
|
ClangdServer Server(CDB, FS, DiagConsumer, 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] = HeaderInPreambleAnnotations.code();
|
|
auto HeaderNotInPreambleH = testPath("src/header_not_in_preamble.h");
|
|
FS.Files[HeaderNotInPreambleH] = 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())));
|
|
|
|
// 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())));
|
|
|
|
// 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())));
|
|
}
|
|
|
|
TEST(Hover, All) {
|
|
struct OneTest {
|
|
StringRef Input;
|
|
StringRef ExpectedHover;
|
|
};
|
|
|
|
OneTest Tests[] = {
|
|
{
|
|
R"cpp(// No hover
|
|
^int main() {
|
|
}
|
|
)cpp",
|
|
"",
|
|
},
|
|
{
|
|
R"cpp(// Local variable
|
|
int main() {
|
|
int bonjour;
|
|
^bonjour = 2;
|
|
int test1 = bonjour;
|
|
}
|
|
)cpp",
|
|
"Declared in function main\n\nint bonjour",
|
|
},
|
|
{
|
|
R"cpp(// Local variable in method
|
|
struct s {
|
|
void method() {
|
|
int bonjour;
|
|
^bonjour = 2;
|
|
}
|
|
};
|
|
)cpp",
|
|
"Declared in function s::method\n\nint bonjour",
|
|
},
|
|
{
|
|
R"cpp(// Struct
|
|
namespace ns1 {
|
|
struct MyClass {};
|
|
} // namespace ns1
|
|
int main() {
|
|
ns1::My^Class* Params;
|
|
}
|
|
)cpp",
|
|
"Declared in namespace ns1\n\nstruct MyClass {}",
|
|
},
|
|
{
|
|
R"cpp(// Class
|
|
namespace ns1 {
|
|
class MyClass {};
|
|
} // namespace ns1
|
|
int main() {
|
|
ns1::My^Class* Params;
|
|
}
|
|
)cpp",
|
|
"Declared in namespace ns1\n\nclass MyClass {}",
|
|
},
|
|
{
|
|
R"cpp(// Union
|
|
namespace ns1 {
|
|
union MyUnion { int x; int y; };
|
|
} // namespace ns1
|
|
int main() {
|
|
ns1::My^Union Params;
|
|
}
|
|
)cpp",
|
|
"Declared in namespace ns1\n\nunion MyUnion {}",
|
|
},
|
|
{
|
|
R"cpp(// Function definition via pointer
|
|
int foo(int) {}
|
|
int main() {
|
|
auto *X = &^foo;
|
|
}
|
|
)cpp",
|
|
"Declared in global namespace\n\nint foo(int)",
|
|
},
|
|
{
|
|
R"cpp(// Function declaration via call
|
|
int foo(int);
|
|
int main() {
|
|
return ^foo(42);
|
|
}
|
|
)cpp",
|
|
"Declared in global namespace\n\nint foo(int)",
|
|
},
|
|
{
|
|
R"cpp(// Field
|
|
struct Foo { int x; };
|
|
int main() {
|
|
Foo bar;
|
|
bar.^x;
|
|
}
|
|
)cpp",
|
|
"Declared in struct Foo\n\nint x",
|
|
},
|
|
{
|
|
R"cpp(// Field with initialization
|
|
struct Foo { int x = 5; };
|
|
int main() {
|
|
Foo bar;
|
|
bar.^x;
|
|
}
|
|
)cpp",
|
|
"Declared in struct Foo\n\nint x = 5",
|
|
},
|
|
{
|
|
R"cpp(// Static field
|
|
struct Foo { static int x; };
|
|
int main() {
|
|
Foo::^x;
|
|
}
|
|
)cpp",
|
|
"Declared in struct Foo\n\nstatic int x",
|
|
},
|
|
{
|
|
R"cpp(// Field, member initializer
|
|
struct Foo {
|
|
int x;
|
|
Foo() : ^x(0) {}
|
|
};
|
|
)cpp",
|
|
"Declared in struct Foo\n\nint x",
|
|
},
|
|
{
|
|
R"cpp(// Field, GNU old-style field designator
|
|
struct Foo { int x; };
|
|
int main() {
|
|
Foo bar = { ^x : 1 };
|
|
}
|
|
)cpp",
|
|
"Declared in struct Foo\n\nint x",
|
|
},
|
|
{
|
|
R"cpp(// Field, field designator
|
|
struct Foo { int x; };
|
|
int main() {
|
|
Foo bar = { .^x = 2 };
|
|
}
|
|
)cpp",
|
|
"Declared in struct Foo\n\nint x",
|
|
},
|
|
{
|
|
R"cpp(// Method call
|
|
struct Foo { int x(); };
|
|
int main() {
|
|
Foo bar;
|
|
bar.^x();
|
|
}
|
|
)cpp",
|
|
"Declared in struct Foo\n\nint x()",
|
|
},
|
|
{
|
|
R"cpp(// Static method call
|
|
struct Foo { static int x(); };
|
|
int main() {
|
|
Foo::^x();
|
|
}
|
|
)cpp",
|
|
"Declared in struct Foo\n\nstatic int x()",
|
|
},
|
|
{
|
|
R"cpp(// Typedef
|
|
typedef int Foo;
|
|
int main() {
|
|
^Foo bar;
|
|
}
|
|
)cpp",
|
|
"Declared in global namespace\n\ntypedef int Foo",
|
|
},
|
|
{
|
|
R"cpp(// Namespace
|
|
namespace ns {
|
|
struct Foo { static void bar(); }
|
|
} // namespace ns
|
|
int main() { ^ns::Foo::bar(); }
|
|
)cpp",
|
|
"Declared in global namespace\n\nnamespace ns {\n}",
|
|
},
|
|
{
|
|
R"cpp(// Anonymous namespace
|
|
namespace ns {
|
|
namespace {
|
|
int foo;
|
|
} // anonymous namespace
|
|
} // namespace ns
|
|
int main() { ns::f^oo++; }
|
|
)cpp",
|
|
"Declared in namespace ns::(anonymous)\n\nint foo",
|
|
},
|
|
{
|
|
R"cpp(// Macro
|
|
#define MACRO 0
|
|
#define MACRO 1
|
|
int main() { return ^MACRO; }
|
|
#define MACRO 2
|
|
#undef macro
|
|
)cpp",
|
|
"#define MACRO",
|
|
},
|
|
{
|
|
R"cpp(// Forward class declaration
|
|
class Foo;
|
|
class Foo {};
|
|
F^oo* foo();
|
|
)cpp",
|
|
"Declared in global namespace\n\nclass Foo {}",
|
|
},
|
|
{
|
|
R"cpp(// Function declaration
|
|
void foo();
|
|
void g() { f^oo(); }
|
|
void foo() {}
|
|
)cpp",
|
|
"Declared in global namespace\n\nvoid foo()",
|
|
},
|
|
{
|
|
R"cpp(// Enum declaration
|
|
enum Hello {
|
|
ONE, TWO, THREE,
|
|
};
|
|
void foo() {
|
|
Hel^lo hello = ONE;
|
|
}
|
|
)cpp",
|
|
"Declared in global namespace\n\nenum Hello {\n}",
|
|
},
|
|
{
|
|
R"cpp(// Enumerator
|
|
enum Hello {
|
|
ONE, TWO, THREE,
|
|
};
|
|
void foo() {
|
|
Hello hello = O^NE;
|
|
}
|
|
)cpp",
|
|
"Declared in enum Hello\n\nONE",
|
|
},
|
|
{
|
|
R"cpp(// Enumerator in anonymous enum
|
|
enum {
|
|
ONE, TWO, THREE,
|
|
};
|
|
void foo() {
|
|
int hello = O^NE;
|
|
}
|
|
)cpp",
|
|
"Declared in enum (anonymous)\n\nONE",
|
|
},
|
|
{
|
|
R"cpp(// Global variable
|
|
static int hey = 10;
|
|
void foo() {
|
|
he^y++;
|
|
}
|
|
)cpp",
|
|
"Declared in global namespace\n\nstatic int hey = 10",
|
|
},
|
|
{
|
|
R"cpp(// Global variable in namespace
|
|
namespace ns1 {
|
|
static int hey = 10;
|
|
}
|
|
void foo() {
|
|
ns1::he^y++;
|
|
}
|
|
)cpp",
|
|
"Declared in namespace ns1\n\nstatic int hey = 10",
|
|
},
|
|
{
|
|
R"cpp(// Field in anonymous struct
|
|
static struct {
|
|
int hello;
|
|
} s;
|
|
void foo() {
|
|
s.he^llo++;
|
|
}
|
|
)cpp",
|
|
"Declared in struct (anonymous)\n\nint hello",
|
|
},
|
|
{
|
|
R"cpp(// Templated function
|
|
template <typename T>
|
|
T foo() {
|
|
return 17;
|
|
}
|
|
void g() { auto x = f^oo<int>(); }
|
|
)cpp",
|
|
"Declared in global namespace\n\ntemplate <typename T> T foo()",
|
|
},
|
|
{
|
|
R"cpp(// Anonymous union
|
|
struct outer {
|
|
union {
|
|
int abc, def;
|
|
} v;
|
|
};
|
|
void g() { struct outer o; o.v.d^ef++; }
|
|
)cpp",
|
|
"Declared in union outer::(anonymous)\n\nint def",
|
|
},
|
|
{
|
|
R"cpp(// Nothing
|
|
void foo() {
|
|
^
|
|
}
|
|
)cpp",
|
|
"",
|
|
},
|
|
{
|
|
R"cpp(// Simple initialization with auto
|
|
void foo() {
|
|
^auto i = 1;
|
|
}
|
|
)cpp",
|
|
"int",
|
|
},
|
|
{
|
|
R"cpp(// Simple initialization with const auto
|
|
void foo() {
|
|
const ^auto i = 1;
|
|
}
|
|
)cpp",
|
|
"int",
|
|
},
|
|
{
|
|
R"cpp(// Simple initialization with const auto&
|
|
void foo() {
|
|
const ^auto& i = 1;
|
|
}
|
|
)cpp",
|
|
"int",
|
|
},
|
|
{
|
|
R"cpp(// Simple initialization with auto&
|
|
void foo() {
|
|
^auto& i = 1;
|
|
}
|
|
)cpp",
|
|
"int",
|
|
},
|
|
{
|
|
R"cpp(// Simple initialization with auto*
|
|
void foo() {
|
|
int a = 1;
|
|
^auto* i = &a;
|
|
}
|
|
)cpp",
|
|
"int",
|
|
},
|
|
{
|
|
R"cpp(// Auto with initializer list.
|
|
namespace std
|
|
{
|
|
template<class _E>
|
|
class initializer_list {};
|
|
}
|
|
void foo() {
|
|
^auto i = {1,2};
|
|
}
|
|
)cpp",
|
|
"class std::initializer_list<int>",
|
|
},
|
|
{
|
|
R"cpp(// User defined conversion to auto
|
|
struct Bar {
|
|
operator ^auto() const { return 10; }
|
|
};
|
|
)cpp",
|
|
"int",
|
|
},
|
|
{
|
|
R"cpp(// Simple initialization with decltype(auto)
|
|
void foo() {
|
|
^decltype(auto) i = 1;
|
|
}
|
|
)cpp",
|
|
"int",
|
|
},
|
|
{
|
|
R"cpp(// Simple initialization with const decltype(auto)
|
|
void foo() {
|
|
const int j = 0;
|
|
^decltype(auto) i = j;
|
|
}
|
|
)cpp",
|
|
"const int",
|
|
},
|
|
{
|
|
R"cpp(// Simple initialization with const& decltype(auto)
|
|
void foo() {
|
|
int k = 0;
|
|
const int& j = k;
|
|
^decltype(auto) i = j;
|
|
}
|
|
)cpp",
|
|
"const int &",
|
|
},
|
|
{
|
|
R"cpp(// Simple initialization with & decltype(auto)
|
|
void foo() {
|
|
int k = 0;
|
|
int& j = k;
|
|
^decltype(auto) i = j;
|
|
}
|
|
)cpp",
|
|
"int &",
|
|
},
|
|
{
|
|
R"cpp(// decltype with initializer list: nothing
|
|
namespace std
|
|
{
|
|
template<class _E>
|
|
class initializer_list {};
|
|
}
|
|
void foo() {
|
|
^decltype(auto) i = {1,2};
|
|
}
|
|
)cpp",
|
|
"",
|
|
},
|
|
{
|
|
R"cpp(// simple trailing return type
|
|
^auto main() -> int {
|
|
return 0;
|
|
}
|
|
)cpp",
|
|
"int",
|
|
},
|
|
{
|
|
R"cpp(// auto function return with trailing type
|
|
struct Bar {};
|
|
^auto test() -> decltype(Bar()) {
|
|
return Bar();
|
|
}
|
|
)cpp",
|
|
"struct Bar",
|
|
},
|
|
{
|
|
R"cpp(// trailing return type
|
|
struct Bar {};
|
|
auto test() -> ^decltype(Bar()) {
|
|
return Bar();
|
|
}
|
|
)cpp",
|
|
"struct Bar",
|
|
},
|
|
{
|
|
R"cpp(// auto in function return
|
|
struct Bar {};
|
|
^auto test() {
|
|
return Bar();
|
|
}
|
|
)cpp",
|
|
"struct Bar",
|
|
},
|
|
{
|
|
R"cpp(// auto& in function return
|
|
struct Bar {};
|
|
^auto& test() {
|
|
return Bar();
|
|
}
|
|
)cpp",
|
|
"struct Bar",
|
|
},
|
|
{
|
|
R"cpp(// auto* in function return
|
|
struct Bar {};
|
|
^auto* test() {
|
|
Bar* bar;
|
|
return bar;
|
|
}
|
|
)cpp",
|
|
"struct Bar",
|
|
},
|
|
{
|
|
R"cpp(// const auto& in function return
|
|
struct Bar {};
|
|
const ^auto& test() {
|
|
return Bar();
|
|
}
|
|
)cpp",
|
|
"struct Bar",
|
|
},
|
|
{
|
|
R"cpp(// decltype(auto) in function return
|
|
struct Bar {};
|
|
^decltype(auto) test() {
|
|
return Bar();
|
|
}
|
|
)cpp",
|
|
"struct Bar",
|
|
},
|
|
{
|
|
R"cpp(// decltype(auto) reference in function return
|
|
struct Bar {};
|
|
^decltype(auto) test() {
|
|
int a;
|
|
return (a);
|
|
}
|
|
)cpp",
|
|
"int &",
|
|
},
|
|
{
|
|
R"cpp(// decltype lvalue reference
|
|
void foo() {
|
|
int I = 0;
|
|
^decltype(I) J = I;
|
|
}
|
|
)cpp",
|
|
"int",
|
|
},
|
|
{
|
|
R"cpp(// decltype lvalue reference
|
|
void foo() {
|
|
int I= 0;
|
|
int &K = I;
|
|
^decltype(K) J = I;
|
|
}
|
|
)cpp",
|
|
"int &",
|
|
},
|
|
{
|
|
R"cpp(// decltype lvalue reference parenthesis
|
|
void foo() {
|
|
int I = 0;
|
|
^decltype((I)) J = I;
|
|
}
|
|
)cpp",
|
|
"int &",
|
|
},
|
|
{
|
|
R"cpp(// decltype rvalue reference
|
|
void foo() {
|
|
int I = 0;
|
|
^decltype(static_cast<int&&>(I)) J = static_cast<int&&>(I);
|
|
}
|
|
)cpp",
|
|
"int &&",
|
|
},
|
|
{
|
|
R"cpp(// decltype rvalue reference function call
|
|
int && bar();
|
|
void foo() {
|
|
int I = 0;
|
|
^decltype(bar()) J = bar();
|
|
}
|
|
)cpp",
|
|
"int &&",
|
|
},
|
|
{
|
|
R"cpp(// decltype of function with trailing return type.
|
|
struct Bar {};
|
|
auto test() -> decltype(Bar()) {
|
|
return Bar();
|
|
}
|
|
void foo() {
|
|
^decltype(test()) i = test();
|
|
}
|
|
)cpp",
|
|
"struct Bar",
|
|
},
|
|
{
|
|
R"cpp(// decltype of var with decltype.
|
|
void foo() {
|
|
int I = 0;
|
|
decltype(I) J = I;
|
|
^decltype(J) K = J;
|
|
}
|
|
)cpp",
|
|
"int",
|
|
},
|
|
{
|
|
R"cpp(// structured binding. Not supported yet
|
|
struct Bar {};
|
|
void foo() {
|
|
Bar a[2];
|
|
^auto [x,y] = a;
|
|
}
|
|
)cpp",
|
|
"",
|
|
},
|
|
{
|
|
R"cpp(// Template auto parameter. Nothing (Not useful).
|
|
template<^auto T>
|
|
void func() {
|
|
}
|
|
void foo() {
|
|
func<1>();
|
|
}
|
|
)cpp",
|
|
"",
|
|
},
|
|
{
|
|
R"cpp(// More compilcated structured types.
|
|
int bar();
|
|
^auto (*foo)() = bar;
|
|
)cpp",
|
|
"int",
|
|
},
|
|
};
|
|
|
|
for (const OneTest &Test : Tests) {
|
|
Annotations T(Test.Input);
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
TU.ExtraArgs.push_back("-std=c++17");
|
|
auto AST = TU.build();
|
|
if (auto H = getHover(AST, T.point())) {
|
|
EXPECT_NE("", Test.ExpectedHover) << Test.Input;
|
|
EXPECT_EQ(H->contents.value, Test.ExpectedHover.str()) << Test.Input;
|
|
} else
|
|
EXPECT_EQ("", Test.ExpectedHover.str()) << Test.Input;
|
|
}
|
|
}
|
|
|
|
TEST(GoToInclude, All) {
|
|
MockFSProvider FS;
|
|
IgnoreDiagnostics DiagConsumer;
|
|
MockCompilationDatabase CDB;
|
|
ClangdServer Server(CDB, FS, DiagConsumer, 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] = SourceAnnotations.code();
|
|
auto FooH = testPath("foo.h");
|
|
|
|
const char *HeaderContents = R"cpp([[]]#pragma once
|
|
int a;
|
|
)cpp";
|
|
Annotations HeaderAnnotations(HeaderContents);
|
|
FS.Files[FooH] = HeaderAnnotations.code();
|
|
|
|
Server.addDocument(FooH, HeaderAnnotations.code());
|
|
Server.addDocument(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())));
|
|
|
|
// 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())));
|
|
|
|
Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("3"));
|
|
ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error";
|
|
EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", 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())));
|
|
|
|
// 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())));
|
|
|
|
Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("7"));
|
|
ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error";
|
|
EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range())));
|
|
}
|
|
|
|
TEST(LocateSymbol, WithPreamble) {
|
|
// Test stragety: AST should always use the latest preamble instead of last
|
|
// good preamble.
|
|
MockFSProvider FS;
|
|
IgnoreDiagnostics DiagConsumer;
|
|
MockCompilationDatabase CDB;
|
|
ClangdServer Server(CDB, FS, DiagConsumer, 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] = FooWithHeader.code();
|
|
|
|
auto FooH = testPath("foo.h");
|
|
Annotations FooHeader(R"cpp([[]])cpp");
|
|
FS.Files[FooH] = 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())));
|
|
|
|
// Only preamble is built, and no AST is built in this request.
|
|
Server.addDocument(FooCpp, FooWithoutHeader.code(), 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())));
|
|
|
|
// Reset test environment.
|
|
runAddDocument(Server, FooCpp, FooWithHeader.code());
|
|
// Both preamble and AST are built in this request.
|
|
Server.addDocument(FooCpp, FooWithoutHeader.code(), WantDiagnostics::Yes);
|
|
// Use the AST being built in above request.
|
|
EXPECT_THAT(
|
|
cantFail(runLocateSymbolAt(Server, FooCpp, FooWithoutHeader.point())),
|
|
ElementsAre(Sym("foo", FooWithoutHeader.range())));
|
|
}
|
|
|
|
TEST(FindReferences, WithinAST) {
|
|
const char *Tests[] = {
|
|
R"cpp(// Local variable
|
|
int main() {
|
|
int [[foo]];
|
|
[[^foo]] = 2;
|
|
int test1 = [[foo]];
|
|
}
|
|
)cpp",
|
|
|
|
R"cpp(// Struct
|
|
namespace ns1 {
|
|
struct [[Foo]] {};
|
|
} // namespace ns1
|
|
int main() {
|
|
ns1::[[Fo^o]]* Params;
|
|
}
|
|
)cpp",
|
|
|
|
R"cpp(// Forward declaration
|
|
class [[Foo]];
|
|
class [[Foo]] {}
|
|
int main() {
|
|
[[Fo^o]] foo;
|
|
}
|
|
)cpp",
|
|
|
|
R"cpp(// Function
|
|
int [[foo]](int) {}
|
|
int main() {
|
|
auto *X = &[[^foo]];
|
|
[[foo]](42)
|
|
}
|
|
)cpp",
|
|
|
|
R"cpp(// Field
|
|
struct Foo {
|
|
int [[foo]];
|
|
Foo() : [[foo]](0) {}
|
|
};
|
|
int main() {
|
|
Foo f;
|
|
f.[[f^oo]] = 1;
|
|
}
|
|
)cpp",
|
|
|
|
R"cpp(// Method call
|
|
struct Foo { int [[foo]](); };
|
|
int Foo::[[foo]]() {}
|
|
int main() {
|
|
Foo f;
|
|
f.[[^foo]]();
|
|
}
|
|
)cpp",
|
|
|
|
R"cpp(// Typedef
|
|
typedef int [[Foo]];
|
|
int main() {
|
|
[[^Foo]] bar;
|
|
}
|
|
)cpp",
|
|
|
|
R"cpp(// Namespace
|
|
namespace [[ns]] {
|
|
struct Foo {};
|
|
} // namespace ns
|
|
int main() { [[^ns]]::Foo foo; }
|
|
)cpp",
|
|
};
|
|
for (const char *Test : Tests) {
|
|
Annotations T(Test);
|
|
auto AST = TestTU::withCode(T.code()).build();
|
|
std::vector<Matcher<Location>> ExpectedLocations;
|
|
for (const auto &R : T.ranges())
|
|
ExpectedLocations.push_back(RangeIs(R));
|
|
EXPECT_THAT(findReferences(AST, T.point(), 0),
|
|
ElementsAreArray(ExpectedLocations))
|
|
<< Test;
|
|
}
|
|
}
|
|
|
|
TEST(FindReferences, ExplicitSymbols) {
|
|
const char *Tests[] = {
|
|
R"cpp(
|
|
struct Foo { Foo* [self]() const; };
|
|
void f() {
|
|
if (Foo* T = foo.[^self]()) {} // Foo member call expr.
|
|
}
|
|
)cpp",
|
|
|
|
R"cpp(
|
|
struct Foo { Foo(int); };
|
|
Foo f() {
|
|
int [b];
|
|
return [^b]; // Foo constructor expr.
|
|
}
|
|
)cpp",
|
|
|
|
R"cpp(
|
|
struct Foo {};
|
|
void g(Foo);
|
|
Foo [f]();
|
|
void call() {
|
|
g([^f]()); // Foo constructor expr.
|
|
}
|
|
)cpp",
|
|
|
|
R"cpp(
|
|
void [foo](int);
|
|
void [foo](double);
|
|
|
|
namespace ns {
|
|
using ::[fo^o];
|
|
}
|
|
)cpp",
|
|
};
|
|
for (const char *Test : Tests) {
|
|
Annotations T(Test);
|
|
auto AST = TestTU::withCode(T.code()).build();
|
|
std::vector<Matcher<Location>> ExpectedLocations;
|
|
for (const auto &R : T.ranges())
|
|
ExpectedLocations.push_back(RangeIs(R));
|
|
EXPECT_THAT(findReferences(AST, T.point(), 0),
|
|
ElementsAreArray(ExpectedLocations))
|
|
<< Test;
|
|
}
|
|
}
|
|
|
|
TEST(FindReferences, NeedsIndex) {
|
|
const char *Header = "int foo();";
|
|
Annotations Main("int main() { [[f^oo]](); }");
|
|
TestTU TU;
|
|
TU.Code = 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),
|
|
ElementsAre(RangeIs(Main.range())));
|
|
Annotations IndexedMain(R"cpp(
|
|
int main() { [[f^oo]](); }
|
|
)cpp");
|
|
|
|
// References from indexed files are included.
|
|
TestTU IndexedTU;
|
|
IndexedTU.Code = IndexedMain.code();
|
|
IndexedTU.Filename = "Indexed.cpp";
|
|
IndexedTU.HeaderCode = Header;
|
|
EXPECT_THAT(findReferences(AST, Main.point(), 0, IndexedTU.index().get()),
|
|
ElementsAre(RangeIs(Main.range()), RangeIs(IndexedMain.range())));
|
|
|
|
EXPECT_EQ(1u, findReferences(AST, Main.point(), /*Limit*/ 1,
|
|
IndexedTU.index().get())
|
|
.size());
|
|
|
|
// If the main file is in the index, we don't return duplicates.
|
|
// (even if the references are in a different location)
|
|
TU.Code = ("\n\n" + Main.code()).str();
|
|
EXPECT_THAT(findReferences(AST, Main.point(), 0, TU.index().get()),
|
|
ElementsAre(RangeIs(Main.range())));
|
|
}
|
|
|
|
TEST(FindReferences, NoQueryForLocalSymbols) {
|
|
struct RecordingIndex : public MemIndex {
|
|
mutable Optional<llvm::DenseSet<SymbolID>> RefIDs;
|
|
void refs(const RefsRequest &Req,
|
|
llvm::function_ref<void(const Ref &)>) const override {
|
|
RefIDs = Req.IDs;
|
|
}
|
|
};
|
|
|
|
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, None) << T.AnnotatedCode;
|
|
else
|
|
EXPECT_EQ(Rec.RefIDs, None) << T.AnnotatedCode;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace clangd
|
|
} // namespace clang
|