[clang-tools-extra] [clangd] Add option to fuzzy-match macros in code-complete (PR #169880)
Mythreya Kuricheti via cfe-commits
cfe-commits at lists.llvm.org
Fri Nov 28 18:04:09 PST 2025
https://github.com/MythreyaK updated https://github.com/llvm/llvm-project/pull/169880
>From ddcdd9328804c49cda512611f2dc14c4f59e0d9b Mon Sep 17 00:00:00 2001
From: Mythreya <git at mythreya.dev>
Date: Thu, 27 Nov 2025 21:47:01 -0800
Subject: [PATCH 1/4] [clangd] Fuzzy-match some macros
Allow results from macros that do not have leading
or trailing underscores.
---
clang-tools-extra/clangd/CodeComplete.cpp | 16 +++++++++----
.../clangd/unittests/CodeCompleteTests.cpp | 23 +++++++++++++++++++
2 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp
index e4df7581f1315..e2566be6b9cb8 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -1435,7 +1435,8 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
Clang->setCodeCompletionConsumer(Consumer.release());
if (Input.Preamble.RequiredModules)
- Input.Preamble.RequiredModules->adjustHeaderSearchOptions(Clang->getHeaderSearchOpts());
+ Input.Preamble.RequiredModules->adjustHeaderSearchOptions(
+ Clang->getHeaderSearchOpts());
SyntaxOnlyAction Action;
if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {
@@ -2037,13 +2038,18 @@ class CodeCompleteFlow {
}
std::optional<float> fuzzyScore(const CompletionCandidate &C) {
- // Macros can be very spammy, so we only support prefix completion.
- if (((C.SemaResult &&
+ const auto IsMacroResult =
+ ((C.SemaResult &&
C.SemaResult->Kind == CodeCompletionResult::RK_Macro) ||
(C.IndexResult &&
- C.IndexResult->SymInfo.Kind == index::SymbolKind::Macro)) &&
- !C.Name.starts_with_insensitive(Filter->pattern()))
+ C.IndexResult->SymInfo.Kind == index::SymbolKind::Macro));
+
+ // macros with leading and trailing underscore are probably spammy
+ if (IsMacroResult && (C.Name.starts_with_insensitive("_") ||
+ C.Name.ends_with_insensitive("_"))) {
return std::nullopt;
+ }
+
return Filter->match(C.Name);
}
diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index e2bdb0fe46e37..c46eb719a36f8 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -4692,6 +4692,29 @@ TEST(CompletionTest, ListExplicitObjectOverloads) {
}
}
+TEST(CompletionTest, FuzzyMatchMacro) {
+ auto Results = completions(R"cpp(
+ #define gl_foo() 42
+ #define _gl_foo() 42
+ int gl_frob();
+
+ int main() {
+ int x = glf^
+ }
+ )cpp");
+
+ {
+ for (const auto &Res : Results.Completions) {
+ fprintf(stderr, "Name: [%s] Snippet [%s], Signature: [%s] Score %f\n",
+ Res.Name.c_str(), Res.SnippetSuffix.c_str(),
+ Res.Signature.c_str(), Res.Score.Total);
+ }
+ }
+
+ EXPECT_THAT(Results.Completions,
+ ElementsAre(named("gl_frob"), named("gl_foo")));
+}
+
} // namespace
} // namespace clangd
} // namespace clang
>From 3fa86b7fe0933c7bb1730a3589a079bbe98a3770 Mon Sep 17 00:00:00 2001
From: Mythreya <git at mythreya.dev>
Date: Thu, 27 Nov 2025 22:42:56 -0800
Subject: [PATCH 2/4] Add config options + tests
---
clang-tools-extra/clangd/ClangdServer.cpp | 1 +
clang-tools-extra/clangd/CodeComplete.cpp | 23 +++++++++++++++----
clang-tools-extra/clangd/CodeComplete.h | 5 ++++
clang-tools-extra/clangd/Config.h | 8 +++++++
clang-tools-extra/clangd/ConfigCompile.cpp | 20 ++++++++++++----
clang-tools-extra/clangd/ConfigFragment.h | 6 +++++
clang-tools-extra/clangd/ConfigYAML.cpp | 4 ++++
.../clangd/unittests/CodeCompleteTests.cpp | 22 ++++++++++--------
.../clangd/unittests/ConfigYAMLTests.cpp | 14 +++++++++++
9 files changed, 86 insertions(+), 17 deletions(-)
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index ac1e9aa5f0ff1..f1a87dd12d905 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -458,6 +458,7 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
CodeCompleteOpts.InsertIncludes =
Config::current().Completion.HeaderInsertion;
CodeCompleteOpts.CodePatterns = Config::current().Completion.CodePatterns;
+ CodeCompleteOpts.MacroFilter = Config::current().Completion.MacroFilter;
// 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 e2566be6b9cb8..0358455ff1f75 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -2038,19 +2038,34 @@ class CodeCompleteFlow {
}
std::optional<float> fuzzyScore(const CompletionCandidate &C) {
+ using MacroFilterPolicy = Config::MacroFilterPolicy;
+
const auto IsMacroResult =
((C.SemaResult &&
C.SemaResult->Kind == CodeCompletionResult::RK_Macro) ||
(C.IndexResult &&
C.IndexResult->SymInfo.Kind == index::SymbolKind::Macro));
+ if (!IsMacroResult)
+ return Filter->match(C.Name);
+
// macros with leading and trailing underscore are probably spammy
- if (IsMacroResult && (C.Name.starts_with_insensitive("_") ||
- C.Name.ends_with_insensitive("_"))) {
- return std::nullopt;
+ switch (Opts.MacroFilter) {
+ case MacroFilterPolicy::ExactPrefix:
+ if (C.Name.starts_with_insensitive(Filter->pattern()))
+ return Filter->match(C.Name);
+ else
+ return std::nullopt;
+ case MacroFilterPolicy::FuzzyMatch:
+ if (!C.Name.starts_with_insensitive("_") &&
+ !C.Name.ends_with_insensitive("_"))
+ return Filter->match(C.Name);
+ else
+ return std::nullopt;
}
+ llvm_unreachable("Unhandled MacroFilter option in fuzzyScore.");
- return Filter->match(C.Name);
+ return std::nullopt;
}
CodeCompletion::Scores
diff --git a/clang-tools-extra/clangd/CodeComplete.h b/clang-tools-extra/clangd/CodeComplete.h
index 1cf3b41119043..cde22a8212e6a 100644
--- a/clang-tools-extra/clangd/CodeComplete.h
+++ b/clang-tools-extra/clangd/CodeComplete.h
@@ -114,6 +114,11 @@ struct CodeCompleteOptions {
/// Whether to suggest code patterns & snippets or not in completion
Config::CodePatternsPolicy CodePatterns = Config::CodePatternsPolicy::All;
+ /// Filter macros using an exact prefix, or with a fuzzy match. In both cases,
+ /// macros with leading or trailing underscores are strictly filtered
+ Config::MacroFilterPolicy MacroFilter =
+ Config::MacroFilterPolicy::ExactPrefix;
+
/// Whether to use the clang parser, or fallback to text-based completion
/// (using identifiers in the current file and symbol indexes).
enum CodeCompletionParse {
diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h
index 01997cee08515..bc58c831e4e89 100644
--- a/clang-tools-extra/clangd/Config.h
+++ b/clang-tools-extra/clangd/Config.h
@@ -157,6 +157,12 @@ struct Config {
None // Suggest none of the code patterns and snippets
};
+ enum class MacroFilterPolicy {
+ ExactPrefix, // Suggest macros if the prefix matches exactly
+ FuzzyMatch, // Fuzzy-match macros if they do not have "_" as prefix or
+ // suffix
+ };
+
/// Configures code completion feature.
struct {
/// Whether code completion includes results that are not visible in current
@@ -168,6 +174,8 @@ struct Config {
HeaderInsertionPolicy HeaderInsertion = HeaderInsertionPolicy::IWYU;
/// Enables code patterns & snippets suggestions
CodePatternsPolicy CodePatterns = CodePatternsPolicy::All;
+ /// Controls how macros are filtered
+ MacroFilterPolicy MacroFilter = MacroFilterPolicy::ExactPrefix;
} Completion;
/// Configures hover feature.
diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp
index 18e31809aa7c7..2b41949d6d05c 100644
--- a/clang-tools-extra/clangd/ConfigCompile.cpp
+++ b/clang-tools-extra/clangd/ConfigCompile.cpp
@@ -564,10 +564,10 @@ struct FragmentCompiler {
auto Fast = isFastTidyCheck(Str);
if (!Fast.has_value()) {
diag(Warning,
- llvm::formatv(
- "Latency of clang-tidy check '{0}' is not known. "
- "It will only run if ClangTidy.FastCheckFilter is Loose or None",
- Str)
+ llvm::formatv("Latency of clang-tidy check '{0}' is not known. "
+ "It will only run if ClangTidy.FastCheckFilter is "
+ "Loose or None",
+ Str)
.str(),
Arg.Range);
} else if (!*Fast) {
@@ -719,6 +719,18 @@ struct FragmentCompiler {
C.Completion.CodePatterns = *Val;
});
}
+
+ if (F.MacroFilter) {
+ if (auto Val =
+ compileEnum<Config::MacroFilterPolicy>("MacroFilter",
+ *F.MacroFilter)
+ .map("ExactPrefix", Config::MacroFilterPolicy::ExactPrefix)
+ .map("FuzzyMatch", Config::MacroFilterPolicy::FuzzyMatch)
+ .value())
+ Out.Apply.push_back([Val](const Params &, Config &C) {
+ C.Completion.MacroFilter = *Val;
+ });
+ }
}
void compile(Fragment::HoverBlock &&F) {
diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h
index 2afeb36574b21..7604fe4e24c97 100644
--- a/clang-tools-extra/clangd/ConfigFragment.h
+++ b/clang-tools-extra/clangd/ConfigFragment.h
@@ -354,6 +354,12 @@ struct Fragment {
/// All => enable all code patterns and snippets suggestion
/// None => disable all code patterns and snippets suggestion
std::optional<Located<std::string>> CodePatterns;
+ /// How to filter macros before offering them as suggestions
+ /// Values are Config::MacroFilterPolicy:
+ /// ExactPrefix: Suggest macros if the prefix matches exactly
+ /// FuzzyMatch: Fuzzy-match macros if they do not have "_" as prefix or
+ /// suffix
+ std::optional<Located<std::string>> MacroFilter;
};
CompletionBlock Completion;
diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp
index 392cf19b05a55..7b6993620fb8c 100644
--- a/clang-tools-extra/clangd/ConfigYAML.cpp
+++ b/clang-tools-extra/clangd/ConfigYAML.cpp
@@ -255,6 +255,10 @@ class Parser {
if (auto CodePatterns = scalarValue(N, "CodePatterns"))
F.CodePatterns = *CodePatterns;
});
+ Dict.handle("MacroFilter", [&](Node &N) {
+ if (auto MacroFilter = scalarValue(N, "MacroFilter"))
+ F.MacroFilter = *MacroFilter;
+ });
Dict.parse(N);
}
diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index c46eb719a36f8..640f2af9507f0 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -4693,7 +4693,7 @@ TEST(CompletionTest, ListExplicitObjectOverloads) {
}
TEST(CompletionTest, FuzzyMatchMacro) {
- auto Results = completions(R"cpp(
+ const auto *const Code = R"cpp(
#define gl_foo() 42
#define _gl_foo() 42
int gl_frob();
@@ -4701,18 +4701,22 @@ TEST(CompletionTest, FuzzyMatchMacro) {
int main() {
int x = glf^
}
- )cpp");
+ )cpp";
{
- for (const auto &Res : Results.Completions) {
- fprintf(stderr, "Name: [%s] Snippet [%s], Signature: [%s] Score %f\n",
- Res.Name.c_str(), Res.SnippetSuffix.c_str(),
- Res.Signature.c_str(), Res.Score.Total);
- }
+ CodeCompleteOptions Opts{};
+ auto Results = completions(Code, {}, Opts);
+ EXPECT_THAT(Results.Completions, ElementsAre(named("gl_frob")));
}
- EXPECT_THAT(Results.Completions,
- ElementsAre(named("gl_frob"), named("gl_foo")));
+ {
+ CodeCompleteOptions Opts{};
+ Opts.MacroFilter = Config::MacroFilterPolicy::FuzzyMatch;
+
+ auto Results = completions(Code, {}, Opts);
+ EXPECT_THAT(Results.Completions,
+ ElementsAre(named("gl_frob"), named("gl_foo")));
+ }
}
} // namespace
diff --git a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp
index c332dcc417fe1..264cb453b413c 100644
--- a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp
@@ -228,6 +228,20 @@ TEST(ParseYAML, CodePatterns) {
EXPECT_THAT(Results[0].Completion.CodePatterns, llvm::ValueIs(val("None")));
}
+TEST(ParseYAML, MacroFilter) {
+ CapturedDiags Diags;
+ Annotations YAML(R"yaml(
+ Completion:
+ MacroFilter: FuzzyMatch
+ )yaml");
+ auto Results =
+ Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
+ ASSERT_THAT(Diags.Diagnostics, IsEmpty());
+ ASSERT_EQ(Results.size(), 1u);
+ EXPECT_THAT(Results[0].Completion.MacroFilter,
+ llvm::ValueIs(val("FuzzyMatch")));
+}
+
TEST(ParseYAML, Hover) {
CapturedDiags Diags;
Annotations YAML(R"yaml(
>From 2099979bc6ebed9aee7f69cc2f9c5b13a3f1fb15 Mon Sep 17 00:00:00 2001
From: Mythreya <git at mythreya.dev>
Date: Thu, 27 Nov 2025 23:53:55 -0800
Subject: [PATCH 3/4] Tweak test
---
clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index 640f2af9507f0..1bec601ef1d36 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -4705,6 +4705,8 @@ TEST(CompletionTest, FuzzyMatchMacro) {
{
CodeCompleteOptions Opts{};
+ EXPECT_EQ(Opts.MacroFilter, Config::MacroFilterPolicy::ExactPrefix);
+
auto Results = completions(Code, {}, Opts);
EXPECT_THAT(Results.Completions, ElementsAre(named("gl_frob")));
}
>From fa487258a5f01573b96017c96bb2e25a2c794cba Mon Sep 17 00:00:00 2001
From: Mythreya <git at mythreya.dev>
Date: Fri, 28 Nov 2025 18:03:37 -0800
Subject: [PATCH 4/4] code-review: update release notes
---
clang-tools-extra/docs/ReleaseNotes.rst | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index a6f80e3721db1..aa0f9f0508a15 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -102,6 +102,10 @@ Hover
Code completion
^^^^^^^^^^^^^^^
+- Added a new ``MacroFilter`` configuration option to ``Completion`` to
+ allow fuzzy-matching with the ``FuzzyMatch`` option when suggesting
+ macros. ``ExactPrefix`` is the default, which retains previous
+ behavior of suggesting macros which match the prefix exactly.
Code actions
^^^^^^^^^^^^
More information about the cfe-commits
mailing list