[PATCH] D143496: [clangd] Add support for missing includes analysis.

Kadir Cetinkaya via Phabricator via cfe-commits cfe-commits at lists.llvm.org
Thu Feb 9 03:33:19 PST 2023


kadircet added inline comments.


================
Comment at: clang-tools-extra/clangd/Config.h:91
 
+  enum class MissingIncludesPolicy {
+    /// Diagnose missing includes.
----------------
rather than duplicating, what about renaming `UnusedIncludesPolicy` to `IncludesPolicy` and use it for both `UnusedIncludes` and `MissingIncludes` options below?


================
Comment at: clang-tools-extra/clangd/ConfigFragment.h:224
 
-    /// Controls how clangd will correct "unnecessary #include directives.
+    /// Controls how clangd will correct "unnecessary" #include directives.
     /// clangd can warn if a header is `#include`d but not used, and suggest
----------------
nit: i'd rather drop the quotes completely to match your description of MissingIncludes below.


================
Comment at: clang-tools-extra/clangd/ConfigFragment.h:238
 
+    /// Controls how clangd handles missing #include directives.
+    /// clangd can warn if a header for a symbol is not `#include`d (missing),
----------------



================
Comment at: clang-tools-extra/clangd/ConfigFragment.h:239-240
+    /// Controls how clangd handles missing #include directives.
+    /// clangd can warn if a header for a symbol is not `#include`d (missing),
+    /// and suggest adding it.
+    ///
----------------



================
Comment at: clang-tools-extra/clangd/ConfigFragment.h:242
+    ///
+    /// Strict means a header is missing if it is not *directly #include'd.
+    /// The file might still compile if the header is included transitively.
----------------



================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:516
+
+  std::vector<include_cleaner::SymbolReference> Macros =
+      collectMacroReferences(AST);
----------------



================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:547
+convertIncludes(const SourceManager &SM,
+                std::vector<Inclusion> MainFileIncludes) {
+  include_cleaner::Includes Includes;
----------------
you can just pass an `llvm::ArrayRef<Inclusion>` to prevent a copy


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:550
+  for (const Inclusion &Inc : MainFileIncludes) {
+    llvm::ErrorOr<const FileEntry *> ResolvedOrError =
+        SM.getFileManager().getFile(Inc.Resolved);
----------------
you can re-write this as:
```
include_cleaner::Include TransformedInc;
TransformedInc.Spelled = Inc.Written.trim("\"<>");
TransformedInc.HashLocation = SM.getComposedLoc(SM.getMainFileID(), Inc.HashOffset); // we should actually convert this from a SourceLocation to offset in include_cleaner::Include as well
TransformedInc.Line = Inc.HashLine;
TransformedInc.Angled = WrittenRef.starts_with("<");
if(auto FE = SM.getFileManager().getFile(Inc.Resolved))
  TransformedInc.Resolved = *FE;
Includes.add(std::move(TransformedInc));
```


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:570
+computeMissingIncludes(ParsedAST &AST) {
+  std::vector<include_cleaner::SymbolReference> Macros =
+      collectMacroReferences(AST);
----------------
nit: `auto Macros = ..`


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:572
+      collectMacroReferences(AST);
+  std::vector<Inclusion> MainFileIncludes =
+      AST.getIncludeStructure().MainFileIncludes;
----------------
no need for copying to vector here, `const auto& MainFileIncludes = ...`


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:577
+      convertIncludes(AST.getSourceManager(), MainFileIncludes);
+  std::string FileName =
+      AST.getSourceManager()
----------------
you can use `AST.tuPath()`


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:582
+          .str();
+  if (FileName.find("foo") != std::string::npos) {
+    vlog("Include cleaner includes: {0}", IncludeCleanerIncludes.all().size());
----------------
looks like debugging artifact?


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:593
+          llvm::ArrayRef<include_cleaner::Header> Providers) {
+        bool Satisfied = false;
+        for (const include_cleaner::Header &H : Providers) {
----------------
nit: you can check whether `Ref.RT` is `Explicit` at the top, and bail out early.


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:597
+              H.physical() == MainFile) {
+            Satisfied = true;
+          }
----------------
nit: you can just `break` after satisfying the include (same below)


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:605
+            Ref.RT == include_cleaner::RefType::Explicit) {
+          std::string SpelledHeader = include_cleaner::spellHeader(
+              Providers.front(), AST.getPreprocessor().getHeaderSearchInfo(),
----------------
clangd has some header spelling customizations. so we should actually be doing this through `URI::includeSpelling(URI::create(getCanonicalPath(Providers.front().physical(), SM)))` first, and fall back to `spellHeader` if it fails for physical header providers to make sure we're consistent.

this is used by $EMPLOYER$'s integration to always spell includes relative to depot root, rather than certain include search paths.


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:609
+          std::pair<FileID, unsigned int> FileIDAndOffset =
+              AST.getSourceManager().getDecomposedLoc(Ref.RefLocation);
+          Missing.push_back({SpelledHeader, FileIDAndOffset.second});
----------------
`RefLocation` is not necessarily spelled token location, e.g. it might be pointing at a macro expansion.

you can make use of `TokenBuffer` inside `ParsedAST` to go from this expanded location to a token spelled inside the main file, e.g. `Tokens.spelledForExpanded(Tokens.expandedTokens(Ref.RefLocation));` and use the full spelled token range afterwards for diagnostic location.


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:661
+    D.Fixes.back().Message = "#include " + Missing.first;
+    D.Fixes.back().Edits.emplace_back();
+    D.Fixes.back().Edits.back().newText = "#include " + Missing.first + "\n";
----------------
we've got some tooling library to generate these edits, see https://github.com/llvm/llvm-project/blob/main/clang/include/clang/Tooling/Inclusions/HeaderIncludes.h#L76. that way they'll be placed in "correct" position among the existing includes. you can then use [replacementToEdit](https://github.com/llvm/llvm-project/blob/main/clang-tools-extra/clangd/SourceCode.h#L148) to convert into a clangd edit.


================
Comment at: clang-tools-extra/clangd/ParsedAST.cpp:695
+    auto MissingHeadersDiags =
+        issueMissingIncludesDiagnostics(Result, Inputs.Contents);
     Result.Diags->insert(Result.Diags->end(),
----------------
instead of introducing a new function here and duplicating logic what about something like:
```
auto IncludeDiags = issueIncludeDiagnostics(...);
```

in IncludeCleaner.cpp:
```
struct IncludeCleanerFindings {
   std::vector<Inclusion*> Unused;
   llvm::StringMap<std::vector<Range>> MissingIncludes; // Map from missing header spelling to ranges of references.
};

IncludeCleanerFindings computeIncludeCleanerFindings(AST) {
   auto Incs = convertIncludes();
   walkUsed(...., {
        // Update Missing includes, mark relevant main file includes as used.
   });
   // Create the unused includes set
   return Findings;
}


std::vector<Diag> issueIncludeDiagnostics(...) {
  std::vector<Inclusion*> UnusedIncludes;
  llvm::StringMap<std::vector</*Location of reference*/clangd::Range>> MissingIncludes;
  if (UnusedIncludePolicy is Strict) {
     UnusedIncludes = computeUnusedIncludes(...);
  } else if(UnusedIncludesPolicy is Experiment || MissingIncludesPolicy is Strict) {
     auto Findings = computeIncludeCleanerFindings(AST);
     if(UnusedIncludesPolicy is Experiment) {
           UnusedIncludes = std::move(Findings.Unused);
     }
     if(MissingIncludesPolicy is Strict) {
            MissingIncludes = std::move(Findings.Missing);
     }
  }
  for(auto &Inc: UnusedIncludes) {
       ... // create unused include diag.
  }
  for(auto &Missing: MissingIncludes) {
       // create missing include diag.
  }
  return Diags;
}
```


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D143496/new/

https://reviews.llvm.org/D143496



More information about the cfe-commits mailing list