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

Viktoriia Bakalova via Phabricator via cfe-commits cfe-commits at lists.llvm.org
Tue Feb 14 08:52:54 PST 2023


VitaNuo added a comment.

Thank you for the comments! I've addressed (almost) all of them. In some places, I'm not so happy about how the code has become very nested, but I don't have ideas on how to make it better atm.



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


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:516
+
+  std::vector<include_cleaner::SymbolReference> Macros =
+      collectMacroReferences(AST);
----------------
kadircet wrote:
> 
Why would you use `auto` here? The return type is not obvious from the function call.

The style guide says: "types that you and your reviewer experience as unnecessary clutter will very often provide useful information to others. For example, you can assume that the return type of `make_unique<Foo>()` is obvious, but the return type of `MyWidgetFactory()` probably isn't." 
(http://go/cstyle#Type_deduction)


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:547
+convertIncludes(const SourceManager &SM,
+                std::vector<Inclusion> MainFileIncludes) {
+  include_cleaner::Includes Includes;
----------------
kadircet wrote:
> you can just pass an `llvm::ArrayRef<Inclusion>` to prevent a copy
By preventing a copy, do you mean that the construction of `llvm::ArrayRef<Inclusion>` will only copy a pointer to the data rather than the whole vector? AFAIU `const std::vector<Inclusion>&` should be even better then, no copies involved. CMIIW.


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:550
+  for (const Inclusion &Inc : MainFileIncludes) {
+    llvm::ErrorOr<const FileEntry *> ResolvedOrError =
+        SM.getFileManager().getFile(Inc.Resolved);
----------------
kadircet wrote:
> 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));
> ```
Thanks.
It seems like `std::string` does not have a `trim` or a `starts_with` method, so AFAIU I still need to call the `llvm::StringRef` constructor.




================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:570
+computeMissingIncludes(ParsedAST &AST) {
+  std::vector<include_cleaner::SymbolReference> Macros =
+      collectMacroReferences(AST);
----------------
kadircet wrote:
> nit: `auto Macros = ..`
Same as above. I am not sure about the benefit of `auto` here..


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:577
+      convertIncludes(AST.getSourceManager(), MainFileIncludes);
+  std::string FileName =
+      AST.getSourceManager()
----------------
kadircet wrote:
> you can use `AST.tuPath()`
Thanks, but this actually seems unused. It was some debugging artefact too.. :(


================
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());
----------------
kadircet wrote:
> looks like debugging artifact?
sorry.


================
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) {
----------------
kadircet wrote:
> nit: you can check whether `Ref.RT` is `Explicit` at the top, and bail out early.
This seems obsolete after merging the missing and unused includes analyses together. There is no obvious place to insert the check.


================
Comment at: clang-tools-extra/clangd/IncludeCleaner.cpp:597
+              H.physical() == MainFile) {
+            Satisfied = true;
+          }
----------------
kadircet wrote:
> nit: you can just `break` after satisfying the include (same below)
Not if the unused and missing include analyses are merged together.


================
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(),
----------------
kadircet wrote:
> 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.
Thank you. There seem to be a couple of indirection levels involved, so I hope I got it (somewhat) right with all the checking.


================
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});
----------------
kadircet wrote:
> `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.
Ok, thank you. Please have a look at the current version, hopefully it makes sense.


================
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";
----------------
kadircet wrote:
> 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.
Thanks for the tips!


================
Comment at: clang-tools-extra/clangd/ParsedAST.cpp:695
+    auto MissingHeadersDiags =
+        issueMissingIncludesDiagnostics(Result, Inputs.Contents);
     Result.Diags->insert(Result.Diags->end(),
----------------
kadircet wrote:
> 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;
> }
> ```
All right, it's probably a good idea to turn these two analyses into one.
The resulting code is somewhat tangled, though, and I am not sure how to make it simpler. Let's hope you have more ideas or are happy with the current state.


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