diff --git a/clang-tools-extra/clangd/CollectMacros.h b/clang-tools-extra/clangd/CollectMacros.h --- a/clang-tools-extra/clangd/CollectMacros.h +++ b/clang-tools-extra/clangd/CollectMacros.h @@ -9,10 +9,13 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_COLLECTEDMACROS_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_COLLECTEDMACROS_H +#include "AST.h" #include "Protocol.h" #include "SourceCode.h" +#include "index/SymbolID.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Lex/PPCallbacks.h" +#include "llvm/ADT/DenseMap.h" #include namespace clang { @@ -22,7 +25,11 @@ llvm::StringSet<> Names; // Instead of storing SourceLocation, we have to store the token range because // SourceManager from preamble is not available when we build the AST. - std::vector Ranges; + llvm::DenseMap> MacroRefs; + // Somtimes it is not possible to compute the SymbolID for the Macro, e.g. a + // reference to an undefined macro. Store them separately, e.g. for semantic + // highlighting. + std::vector UnknownMacros; }; /// Collects macro references (e.g. definitions, expansions) in the main file. @@ -81,7 +88,11 @@ if (auto Range = getTokenRange(SM, LangOpts, Loc)) { Out.Names.insert(MacroNameTok.getIdentifierInfo()->getName()); - Out.Ranges.push_back(*Range); + if (auto SID = + getSymbolID(MacroNameTok.getIdentifierInfo()->getName(), MI, SM)) + Out.MacroRefs[*SID].push_back(*Range); + else + Out.UnknownMacros.push_back(*Range); } } const SourceManager &SM; diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp --- a/clang-tools-extra/clangd/SemanticHighlighting.cpp +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp @@ -311,9 +311,14 @@ if (auto Kind = kindForReference(R)) Builder.addToken(R.NameLoc, *Kind); }); - // Add highlightings for macro expansions. - for (const auto &M : AST.getMacros().Ranges) + // Add highlightings for macro references. + for (const auto &SIDToRefs : AST.getMacros().MacroRefs) { + for (const auto &M : SIDToRefs.second) + Builder.addToken({HighlightingKind::Macro, M}); + } + for (const auto &M : AST.getMacros().UnknownMacros) Builder.addToken({HighlightingKind::Macro, M}); + return std::move(Builder).collect(); } diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -895,6 +895,7 @@ } auto Loc = SM.getMacroArgExpandedLocation( getBeginningOfIdentifier(Pos, SM, AST.getASTContext().getLangOpts())); + // TODO: should we handle macros, too? // We also show references to the targets of using-decls, so we include // DeclRelation::Underlying. @@ -915,8 +916,7 @@ MainFileRefs.end()); for (const auto &Ref : MainFileRefs) { if (auto Range = - getTokenRange(AST.getASTContext().getSourceManager(), - AST.getASTContext().getLangOpts(), Ref.Loc)) { + getTokenRange(SM, AST.getASTContext().getLangOpts(), Ref.Loc)) { Location Result; Result.range = *Range; Result.uri = URIForFile::canonicalize(*MainFilePath, *MainFilePath); diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -30,6 +30,7 @@ ClangdTests.cpp CodeCompleteTests.cpp CodeCompletionStringsTests.cpp + CollectMacrosTests.cpp ContextTests.cpp DexTests.cpp DiagnosticsTests.cpp diff --git a/clang-tools-extra/clangd/unittests/CollectMacrosTests.cpp b/clang-tools-extra/clangd/unittests/CollectMacrosTests.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/CollectMacrosTests.cpp @@ -0,0 +1,93 @@ +#include "Annotations.h" +#include "CollectMacros.h" +#include "Matchers.h" +#include "TestTU.h" +#include "llvm/Support/ScopedPrinter.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +using testing::UnorderedElementsAreArray; + +// Assumes that the ranges are named from 1 to N for some N. +Matcher>> +AreMacroRefsFrom(const Annotations &Test) { + std::vector>> Expected; + for (int I = 1;; I++) { + const auto Refs = Test.ranges(llvm::to_string(I)); + if (Refs.empty()) + break; + Expected.push_back(UnorderedElementsAreArray(Refs)); + } + return UnorderedElementsAreArray(Expected); +} + +std::vector> collectKnownReferences(MainFileMacros M) { + std::vector> Result; + for (const auto &SIDToRef : M.MacroRefs) { + Result.emplace_back(SIDToRef.second.begin(), SIDToRef.second.end()); + } + return Result; +} + +TEST(CollectMainFileMacros, SelectedMacros) { + // References of the same symbol must have the ranges with the same + // name(integer). If there are N different symbols then they must be named + // from 1 to N. Macros for which SymbolID cannot be computed must be named + // "Unknown". + const char *Tests[] = { + R"cpp(// Macros: Cursor on definition. + #define $1[[FOO]](x,y) (x + y) + int main() { int x = $1[[FOO]]($1[[FOO]](3, 4), $1[[FOO]](5, 6)); } + )cpp", + R"cpp(// Multiple definitions. + #define $1[[abc]] 1 + int func1() { int a = $1[[abc]];} + #undef $1[[abc]] + + #define $2[[abc]] 2 + int func2() { int a = $2[[abc]];} + #undef $2[[abc]] + )cpp", + R"cpp( + #ifdef $Unknown[[UNDEFINED]] + #endif + )cpp", + R"cpp( + #ifndef $Unknown[[abc]] + #define $1[[abc]] + #ifdef $1[[abc]] + #endif + #endif + )cpp", + R"cpp( + // Macros from token concatenations not included. + #define $1[[CONCAT]](X) X##A() + #define $2[[PREPEND]](X) MACRO##X() + #define $3[[MACROA]]() 123 + int B = $1[[CONCAT]](MACRO); + int D = $2[[PREPEND]](A) + )cpp", + R"cpp( + // FIXME: Macro names in a definition are not detected. + #define $1[[MACRO_ARGS2]](X, Y) X Y + #define $2[[FOO]] BAR + #define $3[[BAR]] 1 + int A = $2[[FOO]]; + )cpp"}; + for (const char *Test : Tests) { + Annotations T(Test); + auto AST = TestTU::withCode(T.code()).build(); + EXPECT_THAT(AST.getMacros().UnknownMacros, + UnorderedElementsAreArray(T.ranges("Unknown"))) + << Test; + EXPECT_THAT(collectKnownReferences(AST.getMacros()), AreMacroRefsFrom(T)) + << Test; + } +} +} // namespace +} // namespace clangd +} // namespace clang \ No newline at end of file diff --git a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp --- a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp @@ -260,6 +260,15 @@ // Includes macro expansions in arguments that are expressions ^assert(0 <= ^BAR); } + + #ifdef ^UNDEFINED + #endif + + #define ^MULTIPLE_DEFINITION 1 + #undef ^MULTIPLE_DEFINITION + + #define ^MULTIPLE_DEFINITION 2 + #undef ^MULTIPLE_DEFINITION )cpp"); auto TU = TestTU::withCode(TestCase.code()); TU.HeaderCode = R"cpp( @@ -274,7 +283,11 @@ )cpp"; ParsedAST AST = TU.build(); std::vector MacroExpansionPositions; - for (const auto &R : AST.getMacros().Ranges) + for (const auto &SIDToRefs : AST.getMacros().MacroRefs) { + for (const auto &R : SIDToRefs.second) + MacroExpansionPositions.push_back(R.start); + } + for (const auto &R : AST.getMacros().UnknownMacros) MacroExpansionPositions.push_back(R.start); EXPECT_THAT(MacroExpansionPositions, testing::UnorderedElementsAreArray(TestCase.points()));