diff --git a/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Types.h b/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Types.h --- a/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Types.h +++ b/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Types.h @@ -64,7 +64,6 @@ struct Macro macro() const { return std::get(Storage); } private: - // FIXME: Add support for macros. // Order must match Kind enum! std::variant Storage; }; diff --git a/clang-tools-extra/include-cleaner/lib/AnalysisInternal.h b/clang-tools-extra/include-cleaner/lib/AnalysisInternal.h --- a/clang-tools-extra/include-cleaner/lib/AnalysisInternal.h +++ b/clang-tools-extra/include-cleaner/lib/AnalysisInternal.h @@ -24,7 +24,13 @@ #include "clang-include-cleaner/Record.h" #include "clang-include-cleaner/Types.h" #include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Inclusions/StandardLibrary.h" +#include "llvm/ADT/BitmaskEnum.h" #include "llvm/ADT/STLFunctionalExtras.h" +#include +#include +#include +#include namespace clang { class ASTContext; @@ -65,7 +71,6 @@ bool operator==(const SymbolLocation &RHS) const { return Storage == RHS.Storage; } - SourceLocation physical() const { return std::get(Storage); } tooling::stdlib::Symbol standard() const { return std::get(Storage); @@ -77,6 +82,18 @@ }; llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Header &); +/// Represents properties of a symbol provider. +/// FIXME: Expose in public API for decision making (ranking, ignoring, etc.). +enum class Hint : uint8_t { + /// Declaration for the symbol, which is provided by all locations. + Declaration = 0, + /// Complete definition for the symbol. + Complete = 1, + // FIXME: Add header based hints, e.g. IWYU mappings, header-symbol name. + LLVM_MARK_AS_BITMASK_ENUM(Complete), +}; +LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); + /// Finds the headers that provide the symbol location. // FIXME: expose signals llvm::SmallVector
findHeaders(const SymbolLocation &Loc, @@ -88,6 +105,10 @@ void writeHTMLReport(FileID File, llvm::ArrayRef Roots, ASTContext &Ctx, llvm::raw_ostream &OS); +using HintedLocation = std::pair; +/// A set of locations that provides the declaration. +std::vector locateSymbol(const Symbol &S); + } // namespace include_cleaner } // namespace clang diff --git a/clang-tools-extra/include-cleaner/lib/LocateSymbol.cpp b/clang-tools-extra/include-cleaner/lib/LocateSymbol.cpp --- a/clang-tools-extra/include-cleaner/lib/LocateSymbol.cpp +++ b/clang-tools-extra/include-cleaner/lib/LocateSymbol.cpp @@ -1,4 +1,4 @@ -//===--- LocateSymbol.cpp -------------------------------------------------===// +//===--- LocateSymbol.cpp - Find locations providing a symbol -------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -7,10 +7,55 @@ //===----------------------------------------------------------------------===// #include "AnalysisInternal.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Inclusions/StandardLibrary.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/raw_ostream.h" +#include +#include namespace clang::include_cleaner { +namespace { +// Looks for a visible definition of \p D. Returns nullptr if none is available, +// or there are possibly multiple definitions (e.g. namespaces). +const Decl *getDefinition(const Decl *D) { + if (const auto *TD = dyn_cast(D)) + return TD->getDefinition(); + if (const auto *VD = dyn_cast(D)) + return VD->getDefinition(); + if (const auto *FD = dyn_cast(D)) + return FD->getDefinition(); + if (const auto *CTD = dyn_cast(D)) + if (const auto *RD = CTD->getTemplatedDecl()) + return RD->getDefinition(); + if (isa(D) || isa(D) || + isa(D)) + return D; + return nullptr; +} + +std::vector locateDecl(const Decl &D) { + std::vector Result; + // FIXME: Should we also provide physical locations? + if (auto SS = tooling::stdlib::Recognizer()(&D)) + return {{*SS, Hint::Complete}}; + SourceLocation DefLoc; + if (auto *Def = getDefinition(&D)) { + DefLoc = Def->getLocation(); + Result.push_back({DefLoc, Hint::Complete}); + } + for (auto *Redecl : D.redecls()) { + if (Redecl->getLocation() != DefLoc) + Result.push_back({Redecl->getLocation(), Hint::Declaration}); + } + return Result; +} + +} // namespace llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const SymbolLocation &S) { switch (S.kind()) { @@ -28,4 +73,13 @@ llvm_unreachable("Unhandled Symbol kind"); } -} // namespace clang::include_cleaner \ No newline at end of file +std::vector locateSymbol(const Symbol &S) { + switch (S.kind()) { + case Symbol::Declaration: + return locateDecl(S.declaration()); + case Symbol::Macro: + return {{S.macro().Definition, Hint::Complete}}; + } +} + +} // namespace clang::include_cleaner diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp --- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp +++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp @@ -17,8 +17,11 @@ #include "clang/AST/TypeLoc.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" +#include "clang/Tooling/Inclusions/StandardLibrary.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Casting.h" +#include +#include namespace clang::include_cleaner { namespace { diff --git a/clang-tools-extra/include-cleaner/unittests/CMakeLists.txt b/clang-tools-extra/include-cleaner/unittests/CMakeLists.txt --- a/clang-tools-extra/include-cleaner/unittests/CMakeLists.txt +++ b/clang-tools-extra/include-cleaner/unittests/CMakeLists.txt @@ -7,6 +7,7 @@ add_unittest(ClangIncludeCleanerUnitTests ClangIncludeCleanerTests AnalysisTest.cpp FindHeadersTest.cpp + LocateSymbolTest.cpp RecordTest.cpp WalkASTTest.cpp ) diff --git a/clang-tools-extra/include-cleaner/unittests/LocateSymbolTest.cpp b/clang-tools-extra/include-cleaner/unittests/LocateSymbolTest.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/include-cleaner/unittests/LocateSymbolTest.cpp @@ -0,0 +1,96 @@ +//===--- LocateSymbolTest.cpp -------------------------------------- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "AnalysisInternal.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Testing/TestAST.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Casting.h" +#include "llvm/Testing/Support/Annotations.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include +#include +#include +#include + +namespace clang::include_cleaner { +namespace { +using testing::Pair; +using testing::UnorderedElementsAre; + +// Looks for decl with name \p SymbolName and performs locateSymbol on it. +// Expects all the locations marked in `Code` with the right annotation to be +// generated. +void testLocate(llvm::StringRef Code, llvm::StringRef SymbolName = "foo") { + llvm::Annotations Target(Code); + + TestInputs Inputs(Target.code()); + Inputs.ExtraArgs.push_back("-std=c++17"); + TestAST AST(Inputs); + const auto &SM = AST.sourceManager(); + + const NamedDecl *DeclToLocate; + struct MatchCB : public ast_matchers::MatchFinder::MatchCallback { + MatchCB(const NamedDecl *&Out) : Out(Out) {} + void run(const ast_matchers::MatchFinder::MatchResult &Result) override { + Out = Result.Nodes.getNodeAs("id"); + assert(Out); + Out = llvm::cast(Out->getCanonicalDecl()); + } + const NamedDecl *&Out; + } CB(DeclToLocate); + ast_matchers::MatchFinder Finder; + Finder.addMatcher( + ast_matchers::namedDecl(ast_matchers::unless(ast_matchers::isImplicit()), + ast_matchers::hasName(SymbolName)) + .bind("id"), + &CB); + Finder.matchAST(AST.context()); + ASSERT_TRUE(DeclToLocate); + std::vector> ReferencedOffsets; + for (auto Loc : locateSymbol(*DeclToLocate)) { + switch (Loc.first.kind()) { + case SymbolLocation::Physical: { + auto [FID, Offset] = + SM.getDecomposedLoc(SM.getFileLoc(Loc.first.physical())); + ASSERT_EQ(FID, SM.getMainFileID()); + ReferencedOffsets.push_back( + {Offset, + llvm::StringRef(bool(Loc.second & Hint::Complete) ? "$def" : "")}); + break; + } + case SymbolLocation::Standard: + EXPECT_TRUE(bool(Loc.second & Hint::Complete)); + EXPECT_EQ(Loc.first.standard().name(), SymbolName); + break; + } + } + llvm::sort(ReferencedOffsets); + auto AnnotatedCode = Target.code().str(); + for (auto [Offset, Annotation] : llvm::reverse(ReferencedOffsets)) + AnnotatedCode.insert(Offset, (Annotation + "^").str()); + EXPECT_EQ(Code, AnnotatedCode); +} + +TEST(LocateDecl, General) { + testLocate("struct ^foo; struct $def^foo {};"); + testLocate("namespace ns { void ^foo(); void $def^foo() {} }"); + testLocate("enum class ^foo; enum class $def^foo {};"); + // Check for stdlib matching. + testLocate("namespace std { struct vector; }", "vector"); +} + +} // namespace +} // namespace clang::include_cleaner diff --git a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp --- a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp @@ -7,12 +7,14 @@ //===----------------------------------------------------------------------===// #include "AnalysisInternal.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceLocation.h" #include "clang/Frontend/TextDiagnostic.h" #include "clang/Testing/TestAST.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" #include "llvm/Support/Error.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Testing/Support/Annotations.h" @@ -20,6 +22,7 @@ #include #include #include +#include #include namespace clang::include_cleaner {