[clangd] Implement findReferences function

clangd will use findReferences to provide LSP's reference feature.

llvm-svn: 341458
This commit is contained in:
Sam McCall 2018-09-05 10:33:36 +00:00
parent 965b598b2a
commit d445f17614
4 changed files with 268 additions and 80 deletions

View File

@ -174,30 +174,27 @@ IdentifiedSymbol getSymbolAtPosition(ParsedAST &AST, SourceLocation Pos) {
return {DeclMacrosFinder.takeDecls(), DeclMacrosFinder.takeMacroInfos()};
}
llvm::Optional<Location>
makeLocation(ParsedAST &AST, const SourceRange &ValSourceRange) {
Range getTokenRange(ParsedAST &AST, SourceLocation TokLoc) {
const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
const LangOptions &LangOpts = AST.getASTContext().getLangOpts();
SourceLocation LocStart = ValSourceRange.getBegin();
SourceLocation LocEnd = Lexer::getLocForEndOfToken(
TokLoc, 0, SourceMgr, AST.getASTContext().getLangOpts());
return {sourceLocToPosition(SourceMgr, TokLoc),
sourceLocToPosition(SourceMgr, LocEnd)};
}
const FileEntry *F =
SourceMgr.getFileEntryForID(SourceMgr.getFileID(LocStart));
llvm::Optional<Location> makeLocation(ParsedAST &AST, SourceLocation TokLoc) {
const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
const FileEntry *F = SourceMgr.getFileEntryForID(SourceMgr.getFileID(TokLoc));
if (!F)
return llvm::None;
SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), 0,
SourceMgr, LangOpts);
Position Begin = sourceLocToPosition(SourceMgr, LocStart);
Position End = sourceLocToPosition(SourceMgr, LocEnd);
Range R = {Begin, End};
Location L;
auto FilePath = getRealPath(F, SourceMgr);
if (!FilePath) {
log("failed to get path!");
return llvm::None;
}
Location L;
L.uri = URIForFile(*FilePath);
L.range = R;
L.range = getTokenRange(AST, TokLoc);
return L;
}
@ -223,7 +220,7 @@ std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos,
for (auto Item : Symbols.Macros) {
auto Loc = Item.Info->getDefinitionLoc();
auto L = makeLocation(AST, SourceRange(Loc, Loc));
auto L = makeLocation(AST, Loc);
if (L)
Result.push_back(*L);
}
@ -266,7 +263,7 @@ std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos,
auto &Candidate = ResultCandidates[Key];
auto Loc = findNameLoc(D);
auto L = makeLocation(AST, SourceRange(Loc, Loc));
auto L = makeLocation(AST, Loc);
// The declaration in the identified symbols is a definition if possible
// otherwise it is declaration.
bool IsDef = getDefinition(D) == D;
@ -316,24 +313,36 @@ std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos,
namespace {
/// Finds document highlights that a given list of declarations refers to.
class DocumentHighlightsFinder : public index::IndexDataConsumer {
std::vector<const Decl *> &Decls;
std::vector<DocumentHighlight> DocumentHighlights;
const ASTContext &AST;
/// Collects references to symbols within the main file.
class ReferenceFinder : public index::IndexDataConsumer {
public:
DocumentHighlightsFinder(ASTContext &AST, Preprocessor &PP,
std::vector<const Decl *> &Decls)
: Decls(Decls), AST(AST) {}
std::vector<DocumentHighlight> takeHighlights() {
// Don't keep the same highlight multiple times.
// This can happen when nodes in the AST are visited twice.
std::sort(DocumentHighlights.begin(), DocumentHighlights.end());
auto Last =
std::unique(DocumentHighlights.begin(), DocumentHighlights.end());
DocumentHighlights.erase(Last, DocumentHighlights.end());
return std::move(DocumentHighlights);
struct Reference {
const Decl *Target;
SourceLocation Loc;
index::SymbolRoleSet Role;
};
ReferenceFinder(ASTContext &AST, Preprocessor &PP,
const std::vector<const Decl *> &TargetDecls)
: AST(AST) {
for (const Decl *D : TargetDecls)
Targets.insert(D);
}
std::vector<Reference> take() && {
std::sort(References.begin(), References.end(),
[](const Reference &L, const Reference &R) {
return std::tie(L.Loc, L.Target, L.Role) <
std::tie(R.Loc, R.Target, R.Role);
});
// We sometimes see duplicates when parts of the AST get traversed twice.
References.erase(std::unique(References.begin(), References.end(),
[](const Reference &L, const Reference &R) {
return std::tie(L.Target, L.Loc, L.Role) ==
std::tie(R.Target, R.Loc, R.Role);
}),
References.end());
return std::move(References);
}
bool
@ -341,63 +350,53 @@ public:
ArrayRef<index::SymbolRelation> Relations,
SourceLocation Loc,
index::IndexDataConsumer::ASTNodeInfo ASTNode) override {
const SourceManager &SourceMgr = AST.getSourceManager();
SourceLocation HighlightStartLoc = SourceMgr.getFileLoc(Loc);
if (SourceMgr.getMainFileID() != SourceMgr.getFileID(HighlightStartLoc) ||
std::find(Decls.begin(), Decls.end(), D) == Decls.end()) {
return true;
}
SourceLocation End;
const LangOptions &LangOpts = AST.getLangOpts();
End = Lexer::getLocForEndOfToken(HighlightStartLoc, 0, SourceMgr, LangOpts);
SourceRange SR(HighlightStartLoc, End);
DocumentHighlightKind Kind = DocumentHighlightKind::Text;
if (static_cast<index::SymbolRoleSet>(index::SymbolRole::Write) & Roles)
Kind = DocumentHighlightKind::Write;
else if (static_cast<index::SymbolRoleSet>(index::SymbolRole::Read) & Roles)
Kind = DocumentHighlightKind::Read;
DocumentHighlights.push_back(getDocumentHighlight(SR, Kind));
const SourceManager &SM = AST.getSourceManager();
Loc = SM.getFileLoc(Loc);
if (SM.isWrittenInMainFile(Loc) && Targets.count(D))
References.push_back({D, Loc, Roles});
return true;
}
private:
DocumentHighlight getDocumentHighlight(SourceRange SR,
DocumentHighlightKind Kind) {
const SourceManager &SourceMgr = AST.getSourceManager();
Position Begin = sourceLocToPosition(SourceMgr, SR.getBegin());
Position End = sourceLocToPosition(SourceMgr, SR.getEnd());
Range R = {Begin, End};
DocumentHighlight DH;
DH.range = R;
DH.kind = Kind;
return DH;
}
llvm::SmallSet<const Decl *, 4> Targets;
std::vector<Reference> References;
const ASTContext &AST;
};
} // namespace
std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
Position Pos) {
const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
SourceLocation SourceLocationBeg =
getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID());
auto Symbols = getSymbolAtPosition(AST, SourceLocationBeg);
std::vector<const Decl *> SelectedDecls = Symbols.Decls;
DocumentHighlightsFinder DocHighlightsFinder(
AST.getASTContext(), AST.getPreprocessor(), SelectedDecls);
std::vector<ReferenceFinder::Reference>
findRefs(const std::vector<const Decl *> &Decls, ParsedAST &AST) {
ReferenceFinder RefFinder(AST.getASTContext(), AST.getPreprocessor(), Decls);
index::IndexingOptions IndexOpts;
IndexOpts.SystemSymbolFilter =
index::IndexingOptions::SystemSymbolFilterKind::All;
IndexOpts.IndexFunctionLocals = true;
indexTopLevelDecls(AST.getASTContext(), AST.getLocalTopLevelDecls(),
DocHighlightsFinder, IndexOpts);
RefFinder, IndexOpts);
return std::move(RefFinder).take();
}
return DocHighlightsFinder.takeHighlights();
} // namespace
std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
Position Pos) {
const SourceManager &SM = AST.getASTContext().getSourceManager();
auto Symbols = getSymbolAtPosition(
AST, getBeginningOfIdentifier(AST, Pos, SM.getMainFileID()));
auto References = findRefs(Symbols.Decls, AST);
std::vector<DocumentHighlight> Result;
for (const auto &Ref : References) {
DocumentHighlight DH;
DH.range = getTokenRange(AST, Ref.Loc);
if (Ref.Role & index::SymbolRoleSet(index::SymbolRole::Write))
DH.kind = DocumentHighlightKind::Write;
else if (Ref.Role & index::SymbolRoleSet(index::SymbolRole::Read))
DH.kind = DocumentHighlightKind::Read;
else
DH.kind = DocumentHighlightKind::Text;
Result.push_back(std::move(DH));
}
return Result;
}
static PrintingPolicy printingPolicyForDecls(PrintingPolicy Base) {
@ -659,5 +658,51 @@ Optional<Hover> getHover(ParsedAST &AST, Position Pos) {
return None;
}
std::vector<Location> findReferences(ParsedAST &AST, Position Pos,
const SymbolIndex *Index) {
std::vector<Location> Results;
const SourceManager &SM = AST.getASTContext().getSourceManager();
auto MainFilePath = getRealPath(SM.getFileEntryForID(SM.getMainFileID()), SM);
if (!MainFilePath) {
elog("Failed to get a path for the main file, so no references");
return Results;
}
auto Loc = getBeginningOfIdentifier(AST, Pos, SM.getMainFileID());
auto Symbols = getSymbolAtPosition(AST, Loc);
// We traverse the AST to find references in the main file.
// TODO: should we handle macros, too?
auto MainFileRefs = findRefs(Symbols.Decls, AST);
for (const auto &Ref : MainFileRefs) {
Location Result;
Result.range = getTokenRange(AST, Ref.Loc);
Result.uri = URIForFile(*MainFilePath);
Results.push_back(std::move(Result));
}
// Now query the index for references from other files.
if (!Index)
return Results;
RefsRequest Req;
for (const Decl *D : Symbols.Decls) {
// Not all symbols can be referenced from outside (e.g. function-locals).
// TODO: we could skip TU-scoped symbols here (e.g. static functions) if
// we know this file isn't a header. The details might be tricky.
if (D->getParentFunctionOrMethod())
continue;
if (auto ID = getSymbolID(D))
Req.IDs.insert(*ID);
}
if (Req.IDs.empty())
return Results;
Index->refs(Req, [&](const Ref &R) {
auto LSPLoc = toLSPLocation(R.Location, /*HintPath=*/*MainFilePath);
// Avoid indexed results for the main file - the AST is authoritative.
if (LSPLoc && LSPLoc->uri.file() != *MainFilePath)
Results.push_back(std::move(*LSPLoc));
});
return Results;
}
} // namespace clangd
} // namespace clang

View File

@ -34,6 +34,10 @@ std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
/// Get the hover information when hovering at \p Pos.
llvm::Optional<Hover> getHover(ParsedAST &AST, Position Pos);
/// Returns reference locations of the symbol at a specified \p Pos.
std::vector<Location> findReferences(ParsedAST &AST, Position Pos,
const SymbolIndex *Index = nullptr);
} // namespace clangd
} // namespace clang

View File

@ -49,8 +49,10 @@ SymbolSlab TestTU::headerSymbols() const {
}
std::unique_ptr<SymbolIndex> TestTU::index() const {
// FIXME: we should generate proper refs for TestTU.
return MemIndex::build(headerSymbols(), RefSlab());
auto AST = build();
auto Content = indexAST(AST.getASTContext(), AST.getPreprocessorPtr(),
AST.getLocalTopLevelDecls());
return MemIndex::build(std::move(Content.first), std::move(Content.second));
}
const Symbol &findSymbol(const SymbolSlab &Slab, llvm::StringRef QName) {

View File

@ -1068,6 +1068,143 @@ TEST(GoToDefinition, WithPreamble) {
ElementsAre(Location{FooCppUri, FooWithoutHeader.range()}));
}
TEST(FindReferences, WithinAST) {
const char *Tests[] = {
R"cpp(// Local variable
int main() {
int $foo[[foo]];
$foo[[^foo]] = 2;
int test1 = $foo[[foo]];
}
)cpp",
R"cpp(// Struct
namespace ns1 {
struct $foo[[Foo]] {};
} // namespace ns1
int main() {
ns1::$foo[[Fo^o]]* Params;
}
)cpp",
R"cpp(// Function
int $foo[[foo]](int) {}
int main() {
auto *X = &$foo[[^foo]];
$foo[[foo]](42)
}
)cpp",
R"cpp(// Field
struct Foo {
int $foo[[foo]];
Foo() : $foo[[foo]](0) {}
};
int main() {
Foo f;
f.$foo[[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[[Foo]];
int main() {
$foo[[^Foo]] bar;
}
)cpp",
R"cpp(// Namespace
namespace $foo[[ns]] {
struct Foo {};
} // namespace ns
int main() { $foo[[^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("foo"))
ExpectedLocations.push_back(RangeIs(R));
EXPECT_THAT(findReferences(AST, T.point()),
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(), /*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(), IndexedTU.index().get()),
ElementsAre(RangeIs(Main.range()), RangeIs(IndexedMain.range())));
// 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(), TU.index().get()),
ElementsAre(RangeIs(Main.range())));
};
TEST(FindReferences, NoQueryForLocalSymbols) {
struct RecordingIndex : public MemIndex {
mutable Optional<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(), &Rec);
if (T.WantQuery)
EXPECT_NE(Rec.RefIDs, llvm::None) << T.AnnotatedCode;
else
EXPECT_EQ(Rec.RefIDs, llvm::None) << T.AnnotatedCode;
}
};
} // namespace
} // namespace clangd
} // namespace clang