https://github.com/mikomikotaishi created 
https://github.com/llvm/llvm-project/pull/218841

Closes clangd/clangd#2626.

This PR adds single-token completions for just the words `import` and `module` 
of type `CompletionItemKind.Keyword` (done at the same place `export` is 
offered), and allows them to have the required icons in the suggestions list.

>From a57c36f0607f4dc6d387d88b86e630a9f3a69cd4 Mon Sep 17 00:00:00 2001
From: Toyosatomimi no Miko <[email protected]>
Date: Wed, 26 Aug 2026 01:44:46 -0400
Subject: [PATCH] [clang][CodeComplete] Add bare 'module' and 'import' keyword
 completions

---
 .../clangd/unittests/CodeCompleteTests.cpp    | 20 +++++++++
 clang/docs/ReleaseNotes.md                    |  5 +++
 clang/lib/Sema/SemaCodeComplete.cpp           | 43 +++++++++++++------
 clang/test/CodeCompletion/keywords-cxx20.cpp  | 17 ++++++++
 4 files changed, 71 insertions(+), 14 deletions(-)

diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp 
b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index 4c1cab7b11e60..a56cd56e26278 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -861,6 +861,26 @@ TEST(CompletionTest, Kinds) {
       Contains(AllOf(named("Red"), kind(CompletionItemKind::EnumMember))));
 }
 
+TEST(CompletionTest, ModulesKeywords) {
+  // Bare 'module' and 'import' keywords are offered alongside the snippet
+  // forms ('module;', 'module name;', 'import name;').
+  Annotations ModuleTest("mod^");
+  auto TU = TestTU::withCode(ModuleTest.code());
+  TU.ExtraArgs = {"-std=c++20"};
+  auto Results = completions(TU, ModuleTest.point());
+  EXPECT_THAT(Results.Completions,
+              AllOf(has("module", CompletionItemKind::Keyword),
+                    has("module", CompletionItemKind::Snippet)));
+
+  Annotations ImportTest("imp^");
+  TU = TestTU::withCode(ImportTest.code());
+  TU.ExtraArgs = {"-std=c++20"};
+  Results = completions(TU, ImportTest.point());
+  EXPECT_THAT(Results.Completions,
+              AllOf(has("import", CompletionItemKind::Keyword),
+                    has("import", CompletionItemKind::Snippet)));
+}
+
 TEST(CompletionTest, NoDuplicates) {
   auto Results = completions(
       R"cpp(
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 332e0bfdb3a8b..afc41f105ebdb 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -659,6 +659,11 @@ features cannot lower the translation-unit ABI level;
 
 ### Code Completion
 
+- Added bare `module` and `import` keyword completions for C++20 modules, in
+  addition to the existing `module;`, `module name;` and `import name;` snippet
+  completions. This allows LSP clients such as clangd to present them with a
+  keyword icon. 
([clangd/clangd#2626](https://github.com/clangd/clangd/issues/2626))
+
 ### Static Analyzer
 
 #### Crash and bug fixes
diff --git a/clang/lib/Sema/SemaCodeComplete.cpp 
b/clang/lib/Sema/SemaCodeComplete.cpp
index b906a67f4e45e..c7d8ddfb2353c 100644
--- a/clang/lib/Sema/SemaCodeComplete.cpp
+++ b/clang/lib/Sema/SemaCodeComplete.cpp
@@ -2304,7 +2304,32 @@ 
AddOrdinaryNameResults(SemaCodeCompletion::ParserCompletionContext CCC,
         if (SemaRef.CurContext->isTranslationUnit()) {
           /// Global module fragment can only be declared in the beginning of
           /// the file. CurrentModule should be null in this case.
-          if (!CurrentModule) {
+          bool CanGlobalModuleFragment = !CurrentModule;
+          /// Named module should be declared in the beginning of the file,
+          /// or after the global module fragment.
+          bool CanNamedModule =
+              !CurrentModule ||
+              CurrentModule->Kind == Module::ExplicitGlobalModuleFragment ||
+              CurrentModule->Kind == Module::ImplicitGlobalModuleFragment;
+          /// Import can occur in non module file or after the named module
+          /// declaration.
+          bool CanImport =
+              !CurrentModule ||
+              CurrentModule->Kind == Module::ModuleInterfaceUnit ||
+              CurrentModule->Kind == Module::ModulePartitionInterface;
+          bool CanPrivateFragment =
+              CurrentModule &&
+              (CurrentModule->Kind == Module::ModuleInterfaceUnit ||
+               CurrentModule->Kind == Module::ModulePartitionInterface);
+
+          // Bare 'module' and 'import' keywords, in addition to the pattern
+          // results below, so that clients can render them as keywords.
+          if (CanGlobalModuleFragment || CanNamedModule || CanPrivateFragment)
+            Results.AddResult(Result("module", CCP_Keyword));
+          if (CanImport)
+            Results.AddResult(Result("import", CCP_Keyword));
+
+          if (CanGlobalModuleFragment) {
             // module;
             Builder.AddTypedTextChunk("module");
             Builder.AddChunk(CodeCompletionString::CK_SemiColon);
@@ -2312,11 +2337,7 @@ 
AddOrdinaryNameResults(SemaCodeCompletion::ParserCompletionContext CCC,
             Results.AddResult(Result(Builder.TakeString()));
           }
 
-          /// Named module should be declared in the beginning of the file,
-          /// or after the global module fragment.
-          if (!CurrentModule ||
-              CurrentModule->Kind == Module::ExplicitGlobalModuleFragment ||
-              CurrentModule->Kind == Module::ImplicitGlobalModuleFragment) {
+          if (CanNamedModule) {
             // export module;
             // module name;
             Builder.AddTypedTextChunk("module");
@@ -2327,11 +2348,7 @@ 
AddOrdinaryNameResults(SemaCodeCompletion::ParserCompletionContext CCC,
             Results.AddResult(Result(Builder.TakeString()));
           }
 
-          /// Import can occur in non module file or after the named module
-          /// declaration.
-          if (!CurrentModule ||
-              CurrentModule->Kind == Module::ModuleInterfaceUnit ||
-              CurrentModule->Kind == Module::ModulePartitionInterface) {
+          if (CanImport) {
             // import name;
             Builder.AddTypedTextChunk("import");
             Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
@@ -2341,9 +2358,7 @@ 
AddOrdinaryNameResults(SemaCodeCompletion::ParserCompletionContext CCC,
             Results.AddResult(Result(Builder.TakeString()));
           }
 
-          if (CurrentModule &&
-              (CurrentModule->Kind == Module::ModuleInterfaceUnit ||
-               CurrentModule->Kind == Module::ModulePartitionInterface)) {
+          if (CanPrivateFragment) {
             // module: private;
             Builder.AddTypedTextChunk("module");
             Builder.AddChunk(CodeCompletionString::CK_Colon);
diff --git a/clang/test/CodeCompletion/keywords-cxx20.cpp 
b/clang/test/CodeCompletion/keywords-cxx20.cpp
index 612c3c0045e39..b258b2e94b54d 100644
--- a/clang/test/CodeCompletion/keywords-cxx20.cpp
+++ b/clang/test/CodeCompletion/keywords-cxx20.cpp
@@ -14,15 +14,32 @@ int f(){ co_test test; return 1; }
 module: private;
 
 // RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:1:3 %s | FileCheck 
--check-prefix=CHECK-MODULE1 %s
+// CHECK-MODULE1: COMPLETION: module
 // CHECK-MODULE1: module;
 // CHECK-MODULE1: module <#name#>;
 
 // RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:3:11 %s | FileCheck 
--check-prefix=CHECK-MODULE2 %s
+// CHECK-MODULE2: COMPLETION: module
 // CHECK-MODULE2: module <#name#>;
 
 // RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:14:3 %s | FileCheck 
--check-prefix=CHECK-MODULE3 %s
+// CHECK-MODULE3: COMPLETION: module
 // CHECK-MODULE3: module: private;
 
+// RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:1:1 %s | FileCheck 
--check-prefix=CHECK-TOPLEVEL %s
+// CHECK-TOPLEVEL: COMPLETION: export
+// CHECK-TOPLEVEL: COMPLETION: import
+// CHECK-TOPLEVEL: import <#name#>;
+// CHECK-TOPLEVEL: COMPLETION: module
+// CHECK-TOPLEVEL: module;
+// CHECK-TOPLEVEL: module <#name#>;
+
+// RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:14:1 %s | FileCheck 
--check-prefix=CHECK-INTERFACE %s
+// CHECK-INTERFACE: COMPLETION: import
+// CHECK-INTERFACE: import <#name#>;
+// CHECK-INTERFACE: COMPLETION: module
+// CHECK-INTERFACE: module: private;
+
 // RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:3:3 %s | FileCheck 
--check-prefix=CHECK-EXPORT %s
 // CHECK-EXPORT: export
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to