diff --git a/clang-tools-extra/clangd/index/Ref.h b/clang-tools-extra/clangd/index/Ref.h --- a/clang-tools-extra/clangd/index/Ref.h +++ b/clang-tools-extra/clangd/index/Ref.h @@ -27,10 +27,43 @@ /// This is a bitfield which can be combined from different kinds. enum class RefKind : uint8_t { Unknown = 0, - Declaration = static_cast(index::SymbolRole::Declaration), - Definition = static_cast(index::SymbolRole::Definition), - Reference = static_cast(index::SymbolRole::Reference), - All = Declaration | Definition | Reference, + // Points to symbol declaration. Example: + // + // class Foo; + // ^ Foo declaration + // Foo foo; + // ^ this does not reference Foo declaration + Declaration = 1 << 0, + // Points to symbol definition. Example: + // + // int foo(); + // ^ references foo declaration, but not foo definition + // int foo() { return 42; } + // ^ references foo definition, but not declaration + // bool bar() { return true; } + // ^ references both definition and declaration + Definition = 1 << 1, + // Points to symbol reference. Example: + // + // int Foo = 42; + // int Bar = Foo + 1; + // ^ this is a reference to Foo + Reference = 1 << 2, + // The reference explicitly spells out declaration's name. Such references can + // not come from macro expansions or implicit AST nodes. + // + // class Foo {}; + // ^ references declaration, definition and explicitly spells out name + // #define MACRO Foo + // + // Foo foo; + // ^ this reference explicitly spells out Foo's name + // struct Bar { + // MACRO Internal; + // ^ this references Foo, but does not explicitly spell out its name + // }; + Spelled = 1 << 3, + All = Declaration | Definition | Reference | Spelled, }; inline RefKind operator|(RefKind L, RefKind R) { diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -28,6 +28,7 @@ #include "clang/Index/IndexingAction.h" #include "clang/Index/USRGeneration.h" #include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/Syntax/Tokens.h" #include "llvm/Support/Casting.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" @@ -180,7 +181,16 @@ } RefKind toRefKind(index::SymbolRoleSet Roles) { - return static_cast(static_cast(RefKind::All) & Roles); + RefKind Result = RefKind::Unknown; + if (Roles & static_cast(index::SymbolRole::Declaration)) + Result |= RefKind::Declaration; + if (Roles & static_cast(index::SymbolRole::Definition)) + Result |= RefKind::Definition; + if (Roles & static_cast(index::SymbolRole::Reference)) + Result |= RefKind::Reference; + if (!(Roles & static_cast(index::SymbolRole::Implicit))) + Result |= RefKind::Spelled; + return Result; } bool shouldIndexRelation(const index::SymbolRelation &R) { @@ -291,7 +301,7 @@ // occurrence inside the base-specifier. processRelations(*ND, *ID, Relations); - bool CollectRef = static_cast(Opts.RefFilter) & Roles; + bool CollectRef = static_cast(Opts.RefFilter & toRefKind(Roles)); bool IsOnlyRef = !(Roles & (static_cast(index::SymbolRole::Declaration) | static_cast(index::SymbolRole::Definition))); @@ -577,11 +587,30 @@ CollectRef(IDAndRefs.first, LocAndRole); } // Populate Refs slab from DeclRefs. - if (auto MainFileURI = GetURI(SM.getMainFileID())) { - for (const auto &It : DeclRefs) { - if (auto ID = getSymbolID(It.first)) { - for (const auto &LocAndRole : It.second) - CollectRef(*ID, LocAndRole); + llvm::DenseMap> FilesToTokensCache; + for (auto &DeclAndRef : DeclRefs) { + if (auto ID = getSymbolID(DeclAndRef.first)) { + for (auto &LocAndRole : DeclAndRef.second) { + const auto FileID = SM.getFileID(LocAndRole.first); + // FIXME: It's better to use TokenBuffer by passing spelled tokens from + // the caller of SymbolCollector. + if (!FilesToTokensCache.count(FileID)) + FilesToTokensCache[FileID] = + syntax::tokenize(FileID, SM, ASTCtx->getLangOpts()); + llvm::ArrayRef Tokens = FilesToTokensCache[FileID]; + // Check if the referenced symbol is spelled exactly the same way the + // corresponding NamedDecl is. If it isn't, mark this reference as + // implicit. An example of implicit reference (implicit = !spelled) + // would be a macro expansion. + const auto *IdentifierToken = + spelledIdentifierTouching(LocAndRole.first, Tokens); + DeclarationName Name = DeclAndRef.first->getDeclName(); + if (!IdentifierToken || + (Name.isIdentifier() && + Name.getAsString() != IdentifierToken->text(SM))) + LocAndRole.second |= + static_cast(index::SymbolRole::Implicit); + CollectRef(*ID, LocAndRole); } } } diff --git a/clang-tools-extra/clangd/index/SymbolLocation.h b/clang-tools-extra/clangd/index/SymbolLocation.h --- a/clang-tools-extra/clangd/index/SymbolLocation.h +++ b/clang-tools-extra/clangd/index/SymbolLocation.h @@ -9,6 +9,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOL_LOCATION_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOL_LOCATION_H +#include "Protocol.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/raw_ostream.h" #include @@ -88,6 +89,8 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolLocation &); +Range toRange(const SymbolLocation &L); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/index/SymbolLocation.cpp b/clang-tools-extra/clangd/index/SymbolLocation.cpp --- a/clang-tools-extra/clangd/index/SymbolLocation.cpp +++ b/clang-tools-extra/clangd/index/SymbolLocation.cpp @@ -36,5 +36,14 @@ << "-" << L.End.line() << ":" << L.End.column() << ")"; } +Range toRange(const SymbolLocation &L) { + Range R; + R.start.line = L.Start.line(); + R.start.character = L.Start.column(); + R.end.line = L.End.line(); + R.end.character = L.End.column(); + return R; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -272,15 +272,6 @@ return FilteredChanges; } -Range toRange(const SymbolLocation &L) { - Range R; - R.start.line = L.Start.line(); - R.start.character = L.Start.column(); - R.end.line = L.End.line(); - R.end.character = L.End.column(); - return R; -} - // Return all rename occurrences (using the index) outside of the main file, // grouped by the absolute file path. llvm::Expected>> diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp b/clang-tools-extra/clangd/unittests/RenameTests.cpp --- a/clang-tools-extra/clangd/unittests/RenameTests.cpp +++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp @@ -33,7 +33,7 @@ // Convert a Range to a Ref. Ref refWithRange(const clangd::Range &Range, const std::string &URI) { Ref Result; - Result.Kind = RefKind::Reference; + Result.Kind = RefKind::Reference | RefKind::Spelled; Result.Location.Start.setLine(Range.start.line); Result.Location.Start.setColumn(Range.start.character); Result.Location.End.setLine(Range.end.line); @@ -837,7 +837,7 @@ { // variables. R"cpp( - static const int [[VA^R]] = 123; + static const int [[VA^R]] = 123; )cpp", R"cpp( #include "foo.h" diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -100,10 +100,14 @@ MATCHER(RefRange, "") { const Ref &Pos = ::testing::get<0>(arg); const Range &Range = ::testing::get<1>(arg); - return std::make_tuple(Pos.Location.Start.line(), Pos.Location.Start.column(), - Pos.Location.End.line(), Pos.Location.End.column()) == - std::make_tuple(Range.start.line, Range.start.character, - Range.end.line, Range.end.character); + return toRange(Pos.Location) == Range; +} +MATCHER_P2(RefKindIs, Kind, Ranges, "") { + for (const auto &Reference : arg) + if ((llvm::find(Ranges, toRange(Reference.Location)) != end(Ranges)) == + ((Reference.Kind & Kind) == RefKind::Unknown)) + return false; + return true; } ::testing::Matcher &> HaveRanges(const std::vector Ranges) { @@ -700,6 +704,32 @@ EXPECT_THAT(Refs, IsEmpty()); } +TEST_F(SymbolCollectorTest, SpelledReference) { + Annotations Header(R"cpp( + struct Foo; + #define MACRO Foo + )cpp"); + Annotations Main(R"cpp( + struct $spelled[[Foo]] { + $spelled[[Foo]](); + ~$spelled[[Foo]](); + }; + $spelled[[Foo]] Variable1; + $implicit[[MACRO]] Variable2; + )cpp"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.RefsInHeaders = false; + runSymbolCollector(Header.code(), Main.code()); + const auto SpelledRanges = Main.ranges("spelled"); + const auto ImplicitRanges = Main.ranges("implicit"); + auto AllRanges = SpelledRanges; + AllRanges.insert(end(AllRanges), begin(ImplicitRanges), end(ImplicitRanges)); + EXPECT_THAT( + Refs, Contains(Pair(findSymbol(Symbols, "Foo").ID, + AllOf(HaveRanges(AllRanges), + RefKindIs(RefKind::Spelled, SpelledRanges))))); +} + TEST_F(SymbolCollectorTest, NameReferences) { CollectorOpts.RefFilter = RefKind::All; CollectorOpts.RefsInHeaders = true; diff --git a/clang/include/clang/Tooling/Syntax/Tokens.h b/clang/include/clang/Tooling/Syntax/Tokens.h --- a/clang/include/clang/Tooling/Syntax/Tokens.h +++ b/clang/include/clang/Tooling/Syntax/Tokens.h @@ -316,11 +316,16 @@ /// The spelled tokens that overlap or touch a spelling location Loc. /// This always returns 0-2 tokens. llvm::ArrayRef +spelledTokensTouching(SourceLocation Loc, llvm::ArrayRef Tokens); +llvm::ArrayRef spelledTokensTouching(SourceLocation Loc, const syntax::TokenBuffer &Tokens); /// The identifier token that overlaps or touches a spelling location Loc. /// If there is none, returns nullptr. const syntax::Token * +spelledIdentifierTouching(SourceLocation Loc, + llvm::ArrayRef Tokens); +const syntax::Token * spelledIdentifierTouching(SourceLocation Loc, const syntax::TokenBuffer &Tokens); diff --git a/clang/lib/Tooling/Syntax/Tokens.cpp b/clang/lib/Tooling/Syntax/Tokens.cpp --- a/clang/lib/Tooling/Syntax/Tokens.cpp +++ b/clang/lib/Tooling/Syntax/Tokens.cpp @@ -256,21 +256,27 @@ llvm::ArrayRef syntax::spelledTokensTouching(SourceLocation Loc, - const syntax::TokenBuffer &Tokens) { + llvm::ArrayRef Tokens) { assert(Loc.isFileID()); - llvm::ArrayRef All = - Tokens.spelledTokens(Tokens.sourceManager().getFileID(Loc)); auto *Right = llvm::partition_point( - All, [&](const syntax::Token &Tok) { return Tok.location() < Loc; }); - bool AcceptRight = Right != All.end() && Right->location() <= Loc; - bool AcceptLeft = Right != All.begin() && (Right - 1)->endLocation() >= Loc; + Tokens, [&](const syntax::Token &Tok) { return Tok.location() < Loc; }); + bool AcceptRight = Right != Tokens.end() && Right->location() <= Loc; + bool AcceptLeft = + Right != Tokens.begin() && (Right - 1)->endLocation() >= Loc; return llvm::makeArrayRef(Right - (AcceptLeft ? 1 : 0), Right + (AcceptRight ? 1 : 0)); } +llvm::ArrayRef +syntax::spelledTokensTouching(SourceLocation Loc, + const syntax::TokenBuffer &Tokens) { + return spelledTokensTouching( + Loc, Tokens.spelledTokens(Tokens.sourceManager().getFileID(Loc))); +} + const syntax::Token * syntax::spelledIdentifierTouching(SourceLocation Loc, - const syntax::TokenBuffer &Tokens) { + llvm::ArrayRef Tokens) { for (const syntax::Token &Tok : spelledTokensTouching(Loc, Tokens)) { if (Tok.kind() == tok::identifier) return &Tok; @@ -278,6 +284,13 @@ return nullptr; } +const syntax::Token * +syntax::spelledIdentifierTouching(SourceLocation Loc, + const syntax::TokenBuffer &Tokens) { + return spelledIdentifierTouching( + Loc, Tokens.spelledTokens(Tokens.sourceManager().getFileID(Loc))); +} + std::vector TokenBuffer::macroExpansions(FileID FID) const { auto FileIt = Files.find(FID);