Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[clangd] Add HeaderInsertion yaml config option #128503

Conversation

MythreyaK
Copy link
Contributor

This is the yaml config equivalent of --header-insertion CLI

Fix for issue

Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot
Copy link
Member

llvmbot commented Feb 24, 2025

@llvm/pr-subscribers-clangd

@llvm/pr-subscribers-clang-tools-extra

Author: Mythreya (MythreyaK)

Changes

This is the yaml config equivalent of --header-insertion CLI

Fix for issue


Full diff: https://github.com/llvm/llvm-project/pull/128503.diff

9 Files Affected:

  • (modified) clang-tools-extra/clangd/ClangdServer.cpp (+1)
  • (modified) clang-tools-extra/clangd/CodeComplete.cpp (+2-1)
  • (modified) clang-tools-extra/clangd/CodeComplete.h (+2-4)
  • (modified) clang-tools-extra/clangd/Config.h (+9)
  • (modified) clang-tools-extra/clangd/ConfigCompile.cpp (+11)
  • (modified) clang-tools-extra/clangd/ConfigFragment.h (+8)
  • (modified) clang-tools-extra/clangd/ConfigYAML.cpp (+4)
  • (modified) clang-tools-extra/clangd/tool/ClangdMain.cpp (+2-2)
  • (modified) clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp (+2-1)
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 52be15d3da936..9027ae855e409 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -455,6 +455,7 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
     CodeCompleteOpts.MainFileSignals = IP->Signals;
     CodeCompleteOpts.AllScopes = Config::current().Completion.AllScopes;
     CodeCompleteOpts.ArgumentLists = Config::current().Completion.ArgumentLists;
+    CodeCompleteOpts.InsertIncludes = Config::current().HeaderInsertion.Policy;
     // FIXME(ibiryukov): even if Preamble is non-null, we may want to check
     // both the old and the new version in case only one of them matches.
     CodeCompleteResult Result = clangd::codeComplete(
diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp
index a8182ce98ebe0..5db8eeaee1027 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -294,7 +294,8 @@ struct CompletionCandidate {
   std::optional<llvm::StringRef>
   headerToInsertIfAllowed(const CodeCompleteOptions &Opts,
                           CodeCompletionContext::Kind ContextKind) const {
-    if (Opts.InsertIncludes == CodeCompleteOptions::NeverInsert ||
+    if (Opts.InsertIncludes ==
+            CodeCompleteOptions::IncludeInsertion::NeverInsert ||
         RankedIncludeHeaders.empty() ||
         !contextAllowsHeaderInsertion(ContextKind))
       return std::nullopt;
diff --git a/clang-tools-extra/clangd/CodeComplete.h b/clang-tools-extra/clangd/CodeComplete.h
index bb2ebd9478645..8df0637d8aae6 100644
--- a/clang-tools-extra/clangd/CodeComplete.h
+++ b/clang-tools-extra/clangd/CodeComplete.h
@@ -71,10 +71,8 @@ struct CodeCompleteOptions {
   /// Whether to present doc comments as plain-text or markdown.
   MarkupKind DocumentationFormat = MarkupKind::PlainText;
 
-  enum IncludeInsertion {
-    IWYU,
-    NeverInsert,
-  } InsertIncludes = IncludeInsertion::IWYU;
+  using IncludeInsertion = Config::HeaderInsertionPolicy;
+  Config::HeaderInsertionPolicy InsertIncludes = IncludeInsertion::IWYU;
 
   /// Whether include insertions for Objective-C code should use #import instead
   /// of #include.
diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h
index 586d031d58481..eb33f1b8c79ae 100644
--- a/clang-tools-extra/clangd/Config.h
+++ b/clang-tools-extra/clangd/Config.h
@@ -151,6 +151,15 @@ struct Config {
     ArgumentListsPolicy ArgumentLists = ArgumentListsPolicy::FullPlaceholders;
   } Completion;
 
+  enum class HeaderInsertionPolicy {
+    IWYU,       // Include what you use
+    NeverInsert // Never insert headers as part of code completion
+  };
+
+  struct {
+    HeaderInsertionPolicy Policy = HeaderInsertionPolicy::IWYU;
+  } HeaderInsertion;
+
   /// Configures hover feature.
   struct {
     /// Whether hover show a.k.a type.
diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp
index aa2561e081047..fb02b1baaebc4 100644
--- a/clang-tools-extra/clangd/ConfigCompile.cpp
+++ b/clang-tools-extra/clangd/ConfigCompile.cpp
@@ -685,6 +685,17 @@ struct FragmentCompiler {
           C.Completion.ArgumentLists = *Val;
         });
     }
+    if (F.HeaderInsertion) {
+      if (auto Val =
+              compileEnum<Config::HeaderInsertionPolicy>("HeaderInsertion",
+                                                         *F.HeaderInsertion)
+                  .map("IWYU", Config::HeaderInsertionPolicy::IWYU)
+                  .map("Never", Config::HeaderInsertionPolicy::NeverInsert)
+                  .value())
+        Out.Apply.push_back([Val](const Params &, Config &C) {
+          C.HeaderInsertion.Policy = *Val;
+        });
+    }
   }
 
   void compile(Fragment::HoverBlock &&F) {
diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h
index 9535b20253b13..c4c7515c5c25e 100644
--- a/clang-tools-extra/clangd/ConfigFragment.h
+++ b/clang-tools-extra/clangd/ConfigFragment.h
@@ -333,6 +333,14 @@ struct Fragment {
     ///   Delimiters: empty pair of delimiters "()" or "<>"
     ///   FullPlaceholders: full name of both type and parameter
     std::optional<Located<std::string>> ArgumentLists;
+    /// Add #include directives when accepting code completions. Config
+    /// equivalent of the CLI option '--header-insertion'
+    /// Valid values are enum Config::HeaderInsertionPolicy values:
+    ///   "IWYU": Include what you use. Insert the owning header for top-level
+    ///     symbols, unless the header is already directly included or the
+    ///     symbol is forward-declared
+    ///   "NeverInsert": Never insert headers
+    std::optional<Located<std::string>> HeaderInsertion;
   };
   CompletionBlock Completion;
 
diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp
index 95cc5c1f9f1cf..34fe61afddadf 100644
--- a/clang-tools-extra/clangd/ConfigYAML.cpp
+++ b/clang-tools-extra/clangd/ConfigYAML.cpp
@@ -241,6 +241,10 @@ class Parser {
       if (auto ArgumentLists = scalarValue(N, "ArgumentLists"))
         F.ArgumentLists = *ArgumentLists;
     });
+    Dict.handle("HeaderInsertion", [&](Node &N) {
+      if (auto HeaderInsertion = scalarValue(N, "HeaderInsertion"))
+        F.HeaderInsertion = *HeaderInsertion;
+    });
     Dict.parse(N);
   }
 
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index 714891703b6f3..44fd1d450ec64 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -257,13 +257,13 @@ opt<CodeCompleteOptions::IncludeInsertion> HeaderInsertion{
     desc("Add #include directives when accepting code completions"),
     init(CodeCompleteOptions().InsertIncludes),
     values(
-        clEnumValN(CodeCompleteOptions::IWYU, "iwyu",
+        clEnumValN(CodeCompleteOptions::IncludeInsertion::IWYU, "iwyu",
                    "Include what you use. "
                    "Insert the owning header for top-level symbols, unless the "
                    "header is already directly included or the symbol is "
                    "forward-declared"),
         clEnumValN(
-            CodeCompleteOptions::NeverInsert, "never",
+            CodeCompleteOptions::IncludeInsertion::NeverInsert, "never",
             "Never insert #include directives as part of code completion")),
 };
 
diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index b12f8275b8a26..042ad73b8a280 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -882,7 +882,8 @@ TEST(CompletionTest, IncludeInsertionPreprocessorIntegrationTests) {
               ElementsAre(AllOf(named("X"), insertInclude("\"bar.h\""))));
   // Can be disabled via option.
   CodeCompleteOptions NoInsertion;
-  NoInsertion.InsertIncludes = CodeCompleteOptions::NeverInsert;
+  NoInsertion.InsertIncludes =
+      CodeCompleteOptions::IncludeInsertion::NeverInsert;
   Results = completions(TU, Test.point(), {Sym}, NoInsertion);
   EXPECT_THAT(Results.Completions,
               ElementsAre(AllOf(named("X"), Not(insertInclude()))));

@MythreyaK MythreyaK force-pushed the mythreyak/cland-header-insertion-yaml-config branch from 193219f to 8a0714c Compare February 24, 2025 12:43
@MythreyaK
Copy link
Contributor Author

Example config

Completion:
    HeaderInsertion: Never

@MythreyaK
Copy link
Contributor Author

MythreyaK commented Feb 24, 2025

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

Adding @HighCommander4 as reviewer.

No new tests yet since I wasn't sure about it.

@MythreyaK
Copy link
Contributor Author

Ping

@HighCommander4 HighCommander4 self-requested a review March 5, 2025 05:42
@HighCommander4
Copy link
Collaborator

(Recording myself as reviewer. I have a backlog of several patches in my review queue, will get to this as time permits.)

Copy link
Collaborator

@HighCommander4 HighCommander4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the patch! The general approach here looks good.

One issue that I see with the current implementation is that it makes the command-line flag --header-insertion=never have no effect. That's because, while the value of the command-line flag will be copied into the code completion options here, that will then be overwritten in codeComplete() where we now do:

CodeCompleteOpts.InsertIncludes = Config::current().HeaderInsertion.Policy;

If the user didn't specify a HeaderInsertion config option, the value of Config::current().HeaderInsertion.Policy will be IWYU (the default specified in Config.h). and we overwrite the NeverInsert value with IWYU.

This would break users who have --header-insertion=never specified today and expect it to continue to work.

To get the command line flag to interact with the config option better, we need to handle it in FlagsConfigProvider, which gives the command-line flag the ability to influence the value of Config::current().HeaderInsertion.Policy.

We have a few options regarding the precedence, but I think the simplest to implement is to handle it similar to --background-index here: if the flag's value is NeverInsert, then write that into the config object, otherwise leave it alone.

The resulting behaviour will be:

  • If the user specifies --header-insertion=never on the command line, it takes precedence and disables header insertion globally, regardless of any config options.
  • If the user doesn't specify --header-insertion=never on the command line, the config file option is used if specified, falling backing to IWYU as the default.

I think that should work well enough in practice -- let me know what you think.

@MythreyaK
Copy link
Contributor Author

One issue that I see with the current implementation is that it makes the command-line flag --header-insertion=never have no effect.

Oh right! I completely forgot about that.

To get the command line flag to interact with the config option better, we need to handle it in FlagsConfigProvider, which gives the command-line flag the ability to influence the value of Config::current().HeaderInsertion.Policy.

We have a few options regarding the precedence, but I think the simplest to implement is to handle it similar to --background-index here: if the flag's value is NeverInsert, then write that into the config object, otherwise leave it alone.

Clarification, so if the user has IWYU in CLI, but never in the config, never is what will be used?

Since header insertion is a side effect of accepting of code completion proposal, it would make sense to have this option under Completion rather than at the top level.

Sounds good, I'll update my patch shortly.

@HighCommander4
Copy link
Collaborator

HighCommander4 commented Mar 10, 2025

To get the command line flag to interact with the config option better, we need to handle it in FlagsConfigProvider, which gives the command-line flag the ability to influence the value of Config::current().HeaderInsertion.Policy.
We have a few options regarding the precedence, but I think the simplest to implement is to handle it similar to --background-index here: if the flag's value is NeverInsert, then write that into the config object, otherwise leave it alone.

Clarification, so if the user has IWYU in CLI, but never in the config, never is what will be used?

Yes.

I realize that's asymmetric; unfortunately, LLVM's command-line argument framework doesn't allow us to distinguish between "the option's value is IWYU because the flag --header-insertion=iwyu appeared in the command line" and "the option's value is IWYU because no --header-insertion flag appeared in the command line and IWYU is the default".

In practice, I think this is fine because explicitly passing --header-insertion=iwyu is likely to be uncommon, since it's the default anyways.

@MythreyaK MythreyaK force-pushed the mythreyak/cland-header-insertion-yaml-config branch from 8a0714c to b9710d1 Compare March 17, 2025 05:59
Copy link
Collaborator

@HighCommander4 HighCommander4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This looks good modulo a couple of remaining nits.

@MythreyaK MythreyaK force-pushed the mythreyak/cland-header-insertion-yaml-config branch from b9710d1 to f2e4b19 Compare March 20, 2025 04:34
@HighCommander4 HighCommander4 merged commit 9cdbc47 into llvm:main Mar 20, 2025
9 of 10 checks passed
Copy link

@MythreyaK Congratulations on having your first Pull Request (PR) merged into the LLVM Project!

Your changes will be combined with recent changes from other authors, then tested by our build bots. If there is a problem with a build, you may receive a report in an email or a comment on this PR.

Please check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues.

How to do this, and the rest of the post-merge process, is covered in detail here.

If your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of LLVM development. You can fix your changes and open a new PR to merge them again.

If you don't get any reports, no action is required from you. Your changes are working as expected, well done!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants