diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -12,9 +12,11 @@ #include "CodeCompletionStrings.h" #include "Config.h" #include "FindTarget.h" +#include "IncludeCleaner.h" #include "ParsedAST.h" #include "Selection.h" #include "SourceCode.h" +#include "clang-include-cleaner/Types.h" #include "index/SymbolCollector.h" #include "support/Markup.h" #include "clang/AST/ASTContext.h" @@ -1105,6 +1107,21 @@ HI.Definition = URIForFile::canonicalize(Inc.Resolved, AST.tuPath()).file().str(); HI.DefinitionLanguage = ""; + + // TODO(bakalova) Continue here. Identify a good place to insert symbol refs. + // Definition is not a good place. + std::vector MacroReferences = + collectMacroReferences(AST); + CachedIncludeCleaner IncludeCleaner(AST, MacroReferences); + llvm::DenseSet SymbolRefs = + IncludeCleaner.computeUsedSymbols(Inc); + if (!SymbolRefs.empty()) { + HI.Definition.append("\nUsed symbols:\n"); + } + for (UsedSymbol Ref : SymbolRefs) { + HI.Definition.append(Ref.Name); + HI.Definition.append("\n"); + } return HI; } diff --git a/clang-tools-extra/clangd/IncludeCleaner.h b/clang-tools-extra/clangd/IncludeCleaner.h --- a/clang-tools-extra/clangd/IncludeCleaner.h +++ b/clang-tools-extra/clangd/IncludeCleaner.h @@ -21,9 +21,11 @@ #include "Config.h" #include "Headers.h" #include "ParsedAST.h" +#include "clang-include-cleaner/Types.h" #include "index/CanonicalIncludes.h" #include "clang/Basic/SourceLocation.h" #include "clang/Tooling/Inclusions/StandardLibrary.h" +#include "llvm/ADT/DenseMapInfo.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/StringSet.h" @@ -38,6 +40,11 @@ llvm::DenseSet Stdlib; }; +struct UsedSymbol { + std::string Name; + SourceLocation Location; +}; + class CachedIncludeCleaner { public: @@ -50,6 +57,7 @@ void run(); std::vector> computeMissingIncludes(); std::vector computeUnusedIncludesExperimental(); + llvm::DenseSet computeUsedSymbols(const Inclusion &Inclusion); private: ParsedAST &AST; @@ -143,4 +151,29 @@ } // namespace clangd } // namespace clang +namespace llvm { +template <> struct llvm::DenseMapInfo { + static clang::clangd::UsedSymbol getEmptyKey() { + constexpr clang::SourceLocation::UIntTy Zero = 0; + return clang::clangd::UsedSymbol{ + "", clang::SourceLocation::getFromRawEncoding(~Zero)}; + } + + static clang::clangd::UsedSymbol getTombstoneKey() { + constexpr clang::SourceLocation::UIntTy Zero = 0; + return clang::clangd::UsedSymbol{ + "", clang::SourceLocation::getFromRawEncoding(~Zero - 1)}; + } + + static unsigned getHashValue(clang::clangd::UsedSymbol Sym) { + return std::hash{}(Sym.Name); + } + + static bool isEqual(clang::clangd::UsedSymbol LHS, + clang::clangd::UsedSymbol RHS) { + return LHS.Name == RHS.Name; + } +}; +} // namespace llvm + #endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_INCLUDECLEANER_H diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp --- a/clang-tools-extra/clangd/IncludeCleaner.cpp +++ b/clang-tools-extra/clangd/IncludeCleaner.cpp @@ -27,6 +27,7 @@ #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/Syntax/Tokens.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" @@ -212,6 +213,56 @@ return getUnused(AST, Used, /*ReferencedPublicHeaders*/ {}); } +// TODO(bakalova) Add unit test +llvm::DenseSet +CachedIncludeCleaner::computeUsedSymbols(const Inclusion &Inclusion) { + if (Refs.empty()) { + run(); + } + llvm::DenseSet UsedSymbols; + // TODO(bakalova) Avoid double loop by adding a cache by header? + for (unsigned I = 0; I < Refs.size(); ++I) { + include_cleaner::SymbolReference Ref{Refs[I]}; + std::string Name; + SourceLocation Location{Ref.RefLocation}; + // TODO(bakalova) Name is not enough. Can I get more context? Check other hovers to see how + // they get more stuff (from a Decl?) + switch(Ref.Target.kind()) { + case include_cleaner::Symbol::Declaration: + Name = cast(Ref.Target.declaration()) + .getDeclName() + .getAsString(); + break; + case include_cleaner::Symbol::Macro: + Name = Ref.Target.macro().Name->getName(); + break; + } + llvm::SmallVector Providers = ProviderRefs[I]; + for (const include_cleaner::Header &H : Providers) { + switch (H.kind()) { + case include_cleaner::Header::Physical: + if (H.physical()->tryGetRealPathName().contains( + llvm::StringRef(Inclusion.Written).trim("\"<>"))) { + UsedSymbols.insert({Name, Location}); + } + break; + case include_cleaner::Header::Standard: + if (Inclusion.Written == H.standard().name()) { + UsedSymbols.insert({Name, Location}); + } + break; + // TODO(bakalova) does the verbatim thing include quotes or not? + case include_cleaner::Header::Verbatim: + if (Inclusion.Written == H.verbatim()) { + UsedSymbols.insert({Name, Location}); + } + break; + } + } + } + return UsedSymbols; +} + include_cleaner::Includes CachedIncludeCleaner::convertIncludes(const SourceManager &SM, std::vector MainFileIncludes) {