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
