[clangd] Add decl/def support to SymbolDetails

Add an optional declarationRange and definitionRange to SymbolDetails.

This will allow SourceKit-LSP to implement toggling between goto
definition/declaration based on whether the symbol at the cursor
is a definition or declaration.

Differential Revision: https://reviews.llvm.org/D130041
This commit is contained in:
David Goldman 2022-07-18 16:32:59 -04:00
parent 2d9eae4152
commit 61ef0ab701
6 changed files with 195 additions and 84 deletions

View File

@ -14,6 +14,7 @@
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/DeclarationName.h"
#include "clang/AST/ExprCXX.h"

View File

@ -734,7 +734,9 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
bool operator==(const SymbolDetails &LHS, const SymbolDetails &RHS) {
return LHS.name == RHS.name && LHS.containerName == RHS.containerName &&
LHS.USR == RHS.USR && LHS.ID == RHS.ID;
LHS.USR == RHS.USR && LHS.ID == RHS.ID &&
LHS.declarationRange == RHS.declarationRange &&
LHS.definitionRange == RHS.definitionRange;
}
llvm::json::Value toJSON(const SymbolDetails &P) {
@ -755,6 +757,12 @@ llvm::json::Value toJSON(const SymbolDetails &P) {
if (P.ID)
Result["id"] = P.ID.str();
if (P.declarationRange)
Result["declarationRange"] = *P.declarationRange;
if (P.definitionRange)
Result["definitionRange"] = *P.definitionRange;
// FIXME: workaround for older gcc/clang
return std::move(Result);
}

View File

@ -1093,6 +1093,10 @@ struct SymbolDetails {
std::string USR;
SymbolID ID;
llvm::Optional<Location> declarationRange;
llvm::Optional<Location> definitionRange;
};
llvm::json::Value toJSON(const SymbolDetails &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolDetails &);

View File

@ -1481,7 +1481,7 @@ std::vector<SymbolDetails> getSymbolInfo(ParsedAST &AST, Position Pos) {
llvm::consumeError(CurLoc.takeError());
return {};
}
auto MainFilePath = AST.tuPath();
std::vector<SymbolDetails> Results;
// We also want the targets of using-decls, so we include
@ -1489,6 +1489,8 @@ std::vector<SymbolDetails> getSymbolInfo(ParsedAST &AST, Position Pos) {
DeclRelationSet Relations = DeclRelation::TemplatePattern |
DeclRelation::Alias | DeclRelation::Underlying;
for (const NamedDecl *D : getDeclAtPosition(AST, *CurLoc, Relations)) {
D = getPreferredDecl(D);
SymbolDetails NewSymbol;
std::string QName = printQualifiedName(*D);
auto SplitQName = splitQualifiedName(QName);
@ -1505,6 +1507,12 @@ std::vector<SymbolDetails> getSymbolInfo(ParsedAST &AST, Position Pos) {
NewSymbol.USR = std::string(USR.str());
NewSymbol.ID = SymbolID(NewSymbol.USR);
}
if (const NamedDecl *Def = getDefinition(D))
NewSymbol.definitionRange = makeLocation(
AST.getASTContext(), nameLocation(*Def, SM), MainFilePath);
NewSymbol.declarationRange =
makeLocation(AST.getASTContext(), nameLocation(*D, SM), MainFilePath);
Results.push_back(std::move(NewSymbol));
}

View File

@ -1,10 +1,36 @@
# RUN: clangd -lit-test < %s | FileCheck %s
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
---
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///simple.cpp","languageId":"cpp","version":1,"text":"void foo(); int main() { foo(); }\n"}}}
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///simple.cpp","languageId":"cpp","version":1,"text":"void foo(); void foo() {} int main() { foo(); }\n"}}}
---
{"jsonrpc":"2.0","id":1,"method":"textDocument/symbolInfo","params":{"textDocument":{"uri":"test:///simple.cpp"},"position":{"line":0,"character":27}}}
{"jsonrpc":"2.0","id":1,"method":"textDocument/symbolInfo","params":{"textDocument":{"uri":"test:///simple.cpp"},"position":{"line":0,"character":40}}}
# CHECK: "containerName": null,
# CHECK-NEXT: "declarationRange": {
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 8,
# CHECK-NEXT: "line": 0
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 5,
# CHECK-NEXT: "line": 0
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/simple.cpp"
# CHECK-NEXT: },
# CHECK-NEXT: "definitionRange": {
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 20,
# CHECK-NEXT: "line": 0
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 17,
# CHECK-NEXT: "line": 0
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/simple.cpp"
# CHECK-NEXT: },
# CHECK-NEXT: "id": "CA2EBE44A1D76D2A",
# CHECK-NEXT: "name": "foo",
# CHECK-NEXT: "usr": "c:@F@foo#"

View File

@ -18,45 +18,79 @@ namespace {
using ::testing::UnorderedElementsAreArray;
auto CreateExpectedSymbolDetails = [](const std::string &Name,
const std::string &Container,
const std::string &USR) {
return SymbolDetails{Name, Container, USR, SymbolID(USR)};
// Partial SymbolDetails with the rest filled in at testing time.
struct ExpectedSymbolDetails {
std::string Name;
std::string Container;
std::string USR;
const char *DeclMarker = nullptr;
const char *DefMarker = nullptr;
};
TEST(SymbolInfoTests, All) {
std::pair<const char *, std::vector<SymbolDetails>>
std::pair<const char *, std::vector<ExpectedSymbolDetails>>
TestInputExpectedOutput[] = {
{
R"cpp( // Simple function reference - declaration
void foo();
void $decl[[foo]]();
int bar() {
fo^o();
}
)cpp",
{CreateExpectedSymbolDetails("foo", "", "c:@F@foo#")}},
{ExpectedSymbolDetails{"foo", "", "c:@F@foo#", "decl"}}},
{
R"cpp( // Simple function reference - definition
void foo() {}
void $def[[foo]]() {}
int bar() {
fo^o();
}
)cpp",
{CreateExpectedSymbolDetails("foo", "", "c:@F@foo#")}},
{ExpectedSymbolDetails{"foo", "", "c:@F@foo#", "def", "def"}}},
{
R"cpp( // Simple function reference - decl and def
void $decl[[foo]]();
void $def[[foo]]() {}
int bar() {
fo^o();
}
)cpp",
{ExpectedSymbolDetails{"foo", "", "c:@F@foo#", "decl", "def"}}},
{
R"cpp( // Simple class reference - decl and def
@interface $decl[[Foo]]
@end
@implementation $def[[Foo]]
@end
void doSomething(F^oo *obj) {}
)cpp",
{ExpectedSymbolDetails{"Foo", "", "c:objc(cs)Foo", "decl",
"def"}}},
{
R"cpp( // Simple method reference - decl and def
@interface Foo
- (void)$decl[[foo]];
@end
@implementation Foo
- (void)$def[[fo^o]] {}
@end
)cpp",
{ExpectedSymbolDetails{"foo", "Foo::", "c:objc(cs)Foo(im)foo",
"decl", "def"}}},
{
R"cpp( // Function in namespace reference
namespace bar {
void foo();
void $decl[[foo]]();
int baz() {
fo^o();
}
}
)cpp",
{CreateExpectedSymbolDetails("foo", "bar::", "c:@N@bar@F@foo#")}},
{ExpectedSymbolDetails{"foo", "bar::", "c:@N@bar@F@foo#",
"decl"}}},
{
R"cpp( // Function in different namespace reference
namespace bar {
void foo();
void $decl[[foo]]();
}
namespace barbar {
int baz() {
@ -64,10 +98,11 @@ TEST(SymbolInfoTests, All) {
}
}
)cpp",
{CreateExpectedSymbolDetails("foo", "bar::", "c:@N@bar@F@foo#")}},
{ExpectedSymbolDetails{"foo", "bar::", "c:@N@bar@F@foo#",
"decl"}}},
{
R"cpp( // Function in global namespace reference
void foo();
void $decl[[foo]]();
namespace Nbar {
namespace Nbaz {
int baz() {
@ -76,11 +111,11 @@ TEST(SymbolInfoTests, All) {
}
}
)cpp",
{CreateExpectedSymbolDetails("foo", "", "c:@F@foo#")}},
{ExpectedSymbolDetails{"foo", "", "c:@F@foo#", "decl"}}},
{
R"cpp( // Function in anonymous namespace reference
namespace {
void foo();
void $decl[[foo]]();
}
namespace barbar {
int baz() {
@ -88,13 +123,13 @@ TEST(SymbolInfoTests, All) {
}
}
)cpp",
{CreateExpectedSymbolDetails("foo", "(anonymous)",
"c:TestTU.cpp@aN@F@foo#")}},
{ExpectedSymbolDetails{"foo", "(anonymous)",
"c:TestTU.cpp@aN@F@foo#", "decl"}}},
{
R"cpp( // Function reference - ADL
namespace bar {
struct BarType {};
void foo(const BarType&);
void $decl[[foo]](const BarType&);
}
namespace barbar {
int baz() {
@ -103,67 +138,71 @@ TEST(SymbolInfoTests, All) {
}
}
)cpp",
{CreateExpectedSymbolDetails(
"foo", "bar::", "c:@N@bar@F@foo#&1$@N@bar@S@BarType#")}},
{ExpectedSymbolDetails{
"foo", "bar::", "c:@N@bar@F@foo#&1$@N@bar@S@BarType#",
"decl"}}},
{
R"cpp( // Global value reference
int value;
int $def[[value]];
void foo(int) { }
void bar() {
foo(val^ue);
}
)cpp",
{CreateExpectedSymbolDetails("value", "", "c:@value")}},
{ExpectedSymbolDetails{"value", "", "c:@value", "def", "def"}}},
{
R"cpp( // Local value reference
void foo() { int aaa; int bbb = aa^a; }
void foo() { int $def[[aaa]]; int bbb = aa^a; }
)cpp",
{CreateExpectedSymbolDetails("aaa", "foo",
"c:TestTU.cpp@49@F@foo#@aaa")}},
{ExpectedSymbolDetails{"aaa", "foo", "c:TestTU.cpp@49@F@foo#@aaa",
"def", "def"}}},
{
R"cpp( // Function param
void bar(int aaa) {
void bar(int $def[[aaa]]) {
int bbb = a^aa;
}
)cpp",
{CreateExpectedSymbolDetails("aaa", "bar",
"c:TestTU.cpp@38@F@bar#I#@aaa")}},
{ExpectedSymbolDetails{
"aaa", "bar", "c:TestTU.cpp@38@F@bar#I#@aaa", "def", "def"}}},
{
R"cpp( // Lambda capture
void foo() {
int ii;
int $def[[ii]];
auto lam = [ii]() {
return i^i;
};
}
)cpp",
{CreateExpectedSymbolDetails("ii", "foo",
"c:TestTU.cpp@54@F@foo#@ii")}},
{ExpectedSymbolDetails{"ii", "foo", "c:TestTU.cpp@54@F@foo#@ii",
"def", "def"}}},
{
R"cpp( // Macro reference
#define MACRO 5\nint i = MAC^RO;
)cpp",
{CreateExpectedSymbolDetails("MACRO", "",
"c:TestTU.cpp@38@macro@MACRO")}},
{ExpectedSymbolDetails{"MACRO", "",
"c:TestTU.cpp@38@macro@MACRO"}}},
{
R"cpp( // Macro reference
#define MACRO 5\nint i = MACRO^;
)cpp",
{CreateExpectedSymbolDetails("MACRO", "",
"c:TestTU.cpp@38@macro@MACRO")}},
{ExpectedSymbolDetails{"MACRO", "",
"c:TestTU.cpp@38@macro@MACRO"}}},
{
R"cpp( // Multiple symbols returned - using overloaded function name
void foo() {}
void foo(bool) {}
void foo(int) {}
void $def[[foo]]() {}
void $def_bool[[foo]](bool) {}
void $def_int[[foo]](int) {}
namespace bar {
using ::fo^o;
using ::$decl[[fo^o]];
}
)cpp",
{CreateExpectedSymbolDetails("foo", "", "c:@F@foo#"),
CreateExpectedSymbolDetails("foo", "", "c:@F@foo#b#"),
CreateExpectedSymbolDetails("foo", "", "c:@F@foo#I#"),
CreateExpectedSymbolDetails("foo", "bar::", "c:@N@bar@UD@foo")}},
{ExpectedSymbolDetails{"foo", "", "c:@F@foo#", "def", "def"},
ExpectedSymbolDetails{"foo", "", "c:@F@foo#b#", "def_bool",
"def_bool"},
ExpectedSymbolDetails{"foo", "", "c:@F@foo#I#", "def_int",
"def_int"},
ExpectedSymbolDetails{"foo", "bar::", "c:@N@bar@UD@foo",
"decl"}}},
{
R"cpp( // Multiple symbols returned - implicit conversion
struct foo {};
@ -172,133 +211,142 @@ TEST(SymbolInfoTests, All) {
};
void func_baz1(bar) {}
void func_baz2() {
foo ff;
foo $def[[ff]];
func_baz1(f^f);
}
)cpp",
{CreateExpectedSymbolDetails(
"ff", "func_baz2", "c:TestTU.cpp@218@F@func_baz2#@ff")}},
{ExpectedSymbolDetails{"ff", "func_baz2",
"c:TestTU.cpp@218@F@func_baz2#@ff", "def",
"def"}}},
{
R"cpp( // Type reference - declaration
struct foo;
struct $decl[[foo]];
void bar(fo^o*);
)cpp",
{CreateExpectedSymbolDetails("foo", "", "c:@S@foo")}},
{ExpectedSymbolDetails{"foo", "", "c:@S@foo", "decl"}}},
{
R"cpp( // Type reference - definition
struct foo {};
struct $def[[foo]] {};
void bar(fo^o*);
)cpp",
{CreateExpectedSymbolDetails("foo", "", "c:@S@foo")}},
{ExpectedSymbolDetails{"foo", "", "c:@S@foo", "def", "def"}}},
{
R"cpp( // Type Reference - template argument
struct foo {};
struct $def[[foo]] {};
template<class T> struct bar {};
void baz() {
bar<fo^o> b;
}
)cpp",
{CreateExpectedSymbolDetails("foo", "", "c:@S@foo")}},
{ExpectedSymbolDetails{"foo", "", "c:@S@foo", "def", "def"}}},
{
R"cpp( // Template parameter reference - type param
template<class TT> struct bar {
template<class $def[[TT]]> struct bar {
T^T t;
};
)cpp",
{CreateExpectedSymbolDetails("TT", "bar::", "c:TestTU.cpp@65")}},
{ExpectedSymbolDetails{"TT", "bar::", "c:TestTU.cpp@65", "def",
"def"}}},
{
R"cpp( // Template parameter reference - type param
template<int NN> struct bar {
template<int $def[[NN]]> struct bar {
int a = N^N;
};
)cpp",
{CreateExpectedSymbolDetails("NN", "bar::", "c:TestTU.cpp@65")}},
{ExpectedSymbolDetails{"NN", "bar::", "c:TestTU.cpp@65", "def",
"def"}}},
{
R"cpp( // Class member reference - objec
struct foo {
int aa;
int $def[[aa]];
};
void bar() {
foo f;
f.a^a;
}
)cpp",
{CreateExpectedSymbolDetails("aa", "foo::", "c:@S@foo@FI@aa")}},
{ExpectedSymbolDetails{"aa", "foo::", "c:@S@foo@FI@aa", "def",
"def"}}},
{
R"cpp( // Class member reference - pointer
struct foo {
int aa;
int $def[[aa]];
};
void bar() {
&foo::a^a;
}
)cpp",
{CreateExpectedSymbolDetails("aa", "foo::", "c:@S@foo@FI@aa")}},
{ExpectedSymbolDetails{"aa", "foo::", "c:@S@foo@FI@aa", "def",
"def"}}},
{
R"cpp( // Class method reference - objec
struct foo {
void aa() {}
void $def[[aa]]() {}
};
void bar() {
foo f;
f.a^a();
}
)cpp",
{CreateExpectedSymbolDetails("aa", "foo::", "c:@S@foo@F@aa#")}},
{ExpectedSymbolDetails{"aa", "foo::", "c:@S@foo@F@aa#", "def",
"def"}}},
{
R"cpp( // Class method reference - pointer
struct foo {
void aa() {}
void $def[[aa]]() {}
};
void bar() {
&foo::a^a;
}
)cpp",
{CreateExpectedSymbolDetails("aa", "foo::", "c:@S@foo@F@aa#")}},
{ExpectedSymbolDetails{"aa", "foo::", "c:@S@foo@F@aa#", "def",
"def"}}},
{
R"cpp( // Typedef
typedef int foo;
typedef int $decl[[foo]];
void bar() {
fo^o a;
}
)cpp",
{CreateExpectedSymbolDetails("foo", "", "c:TestTU.cpp@T@foo")}},
{ExpectedSymbolDetails{"foo", "", "c:TestTU.cpp@T@foo", "decl"}}},
{
R"cpp( // Type alias
using foo = int;
using $decl[[foo]] = int;
void bar() {
fo^o a;
}
)cpp",
{CreateExpectedSymbolDetails("foo", "", "c:@foo")}},
{ExpectedSymbolDetails{"foo", "", "c:@foo", "decl"}}},
{
R"cpp( // Namespace reference
namespace foo {}
namespace $decl[[foo]] {}
using namespace fo^o;
)cpp",
{CreateExpectedSymbolDetails("foo", "", "c:@N@foo")}},
{ExpectedSymbolDetails{"foo", "", "c:@N@foo", "decl"}}},
{
R"cpp( // Enum value reference
enum foo { bar, baz };
enum foo { $def[[bar]], baz };
void f() {
foo fff = ba^r;
}
)cpp",
{CreateExpectedSymbolDetails("bar", "foo", "c:@E@foo@bar")}},
{ExpectedSymbolDetails{"bar", "foo", "c:@E@foo@bar", "def",
"def"}}},
{
R"cpp( // Enum class value reference
enum class foo { bar, baz };
enum class foo { $def[[bar]], baz };
void f() {
foo fff = foo::ba^r;
}
)cpp",
{CreateExpectedSymbolDetails("bar", "foo::", "c:@E@foo@bar")}},
{ExpectedSymbolDetails{"bar", "foo::", "c:@E@foo@bar", "def",
"def"}}},
{
R"cpp( // Parameters in declarations
void foo(int ba^r);
void foo(int $def[[ba^r]]);
)cpp",
{CreateExpectedSymbolDetails("bar", "foo",
"c:TestTU.cpp@50@F@foo#I#@bar")}},
{ExpectedSymbolDetails{
"bar", "foo", "c:TestTU.cpp@50@F@foo#I#@bar", "def", "def"}}},
{
R"cpp( // Type inference with auto keyword
struct foo {};
@ -321,10 +369,26 @@ TEST(SymbolInfoTests, All) {
for (const auto &T : TestInputExpectedOutput) {
Annotations TestInput(T.first);
auto AST = TestTU::withCode(TestInput.code()).build();
TestTU TU;
TU.Code = std::string(TestInput.code());
TU.ExtraArgs.push_back("-xobjective-c++");
auto AST = TU.build();
std::vector<SymbolDetails> Expected;
for (const auto &Sym : T.second) {
llvm::Optional<Location> Decl, Def;
if (Sym.DeclMarker)
Decl = Location{URIForFile::canonicalize(testPath(TU.Filename), ""),
TestInput.range(Sym.DeclMarker)};
if (Sym.DefMarker)
Def = Location{URIForFile::canonicalize(testPath(TU.Filename), ""),
TestInput.range(Sym.DefMarker)};
Expected.push_back(
{Sym.Name, Sym.Container, Sym.USR, SymbolID(Sym.USR), Decl, Def});
}
EXPECT_THAT(getSymbolInfo(AST, TestInput.point()),
UnorderedElementsAreArray(T.second))
UnorderedElementsAreArray(Expected))
<< T.first;
}
}