Skip to content

Add code completion for C++20 keywords. #107982

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

Merged
merged 22 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ Hover
Code completion
^^^^^^^^^^^^^^^

- Added completion for C++20 keywords.

Code actions
^^^^^^^^^^^^

Expand Down
9 changes: 9 additions & 0 deletions clang/lib/Parse/ParseDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,15 @@ Decl *Parser::ParseExportDeclaration() {
assert(Tok.is(tok::kw_export));
SourceLocation ExportLoc = ConsumeToken();

if (Tok.is(tok::code_completion)) {
cutOffParsing();
Actions.CodeCompletion().CodeCompleteOrdinaryName(
getCurScope(), PP.isIncrementalProcessingEnabled()
? SemaCodeCompletion::PCC_TopLevelOrExpression
: SemaCodeCompletion::PCC_Namespace);
return nullptr;
}

ParseScope ExportScope(this, Scope::DeclScope);
Decl *ExportDecl = Actions.ActOnStartExportDecl(
getCurScope(), ExportLoc,
Expand Down
128 changes: 128 additions & 0 deletions clang/lib/Sema/SemaCodeComplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1836,6 +1836,9 @@ static void AddTypeSpecifierResults(const LangOptions &LangOpts,
Builder.AddChunk(CodeCompletionString::CK_RightParen);
Results.AddResult(Result(Builder.TakeString()));
}

if (LangOpts.Char8 || LangOpts.CPlusPlus20)
Results.AddResult(Result("char8_t", CCP_Type));
} else
Results.AddResult(Result("__auto_type", CCP_Type));

Expand Down Expand Up @@ -1888,6 +1891,9 @@ AddStorageSpecifiers(SemaCodeCompletion::ParserCompletionContext CCC,
Results.AddResult(Result("constexpr"));
Results.AddResult(Result("thread_local"));
}

if (LangOpts.CPlusPlus20)
Results.AddResult(Result("constinit"));
}

static void
Expand All @@ -1911,6 +1917,9 @@ AddFunctionSpecifiers(SemaCodeCompletion::ParserCompletionContext CCC,
case SemaCodeCompletion::PCC_Template:
if (LangOpts.CPlusPlus || LangOpts.C99)
Results.AddResult(Result("inline"));

if (LangOpts.CPlusPlus20)
Results.AddResult(Result("consteval"));
break;

case SemaCodeCompletion::PCC_ObjCInstanceVariableList:
Expand Down Expand Up @@ -2186,6 +2195,69 @@ AddOrdinaryNameResults(SemaCodeCompletion::ParserCompletionContext CCC,
} else {
Results.AddResult(Result("template", CodeCompletionResult::RK_Keyword));
}

if (SemaRef.getLangOpts().CPlusPlus20 &&
SemaRef.getLangOpts().CPlusPlusModules) {
clang::Module *CurrentModule = SemaRef.getCurrentModule();
if (SemaRef.CurContext->isTranslationUnit()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we restrict that using getCurrentModule()->Kind @ChuanqiXu9 ?

Copy link
Member

Choose a reason for hiding this comment

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

i think we can't do it here. Since we don't the kind of the current module before we see module declarations.

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, exactly.
If we haven't see module;, we should propose module; and export module
If we have seen module; - we should not propose it again
If we have seem export module, we should not propose import

Copy link
Contributor Author

@16bit-ykiko 16bit-ykiko Oct 11, 2024

Choose a reason for hiding this comment

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

I push a new commit and try to make completion for module related keywords more context-sensitive.

And found something strange: https://godbolt.org/z/YM9dhEKKe

module;

export module M;
         ^

If I try to run code completion at ^, only get a compiler error.

<source>:3:1: error: export declaration can only be used within a module purview
    3 | export mo<U+0000>dule M;

The error shouldn't occur, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

It only happens during completion so there is probably a bug with that https://godbolt.org/z/7M8EPodoP

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made some modification to ParseExportDeclaration to fix the problem.

/// Global module fragment can only be declared in the beginning of
/// the file. CurrentModule should be null in this case.
if (!CurrentModule) {
// module;
Builder.AddTypedTextChunk("module");
Builder.AddChunk(CodeCompletionString::CK_SemiColon);
Builder.AddChunk(CodeCompletionString::CK_VerticalSpace);
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) {
// export module;
// module name;
Builder.AddTypedTextChunk("module");
Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
Builder.AddPlaceholderChunk("name");
Builder.AddChunk(CodeCompletionString::CK_SemiColon);
Builder.AddChunk(CodeCompletionString::CK_VerticalSpace);
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) {
// import name;
Builder.AddTypedTextChunk("import");
Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
Builder.AddPlaceholderChunk("name");
Builder.AddChunk(CodeCompletionString::CK_SemiColon);
Builder.AddChunk(CodeCompletionString::CK_VerticalSpace);
Results.AddResult(Result(Builder.TakeString()));
}

if (CurrentModule &&
(CurrentModule->Kind == Module::ModuleInterfaceUnit ||
CurrentModule->Kind == Module::ModulePartitionInterface)) {
// module: private;
Builder.AddTypedTextChunk("module");
Builder.AddChunk(CodeCompletionString::CK_Colon);
Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
Builder.AddTypedTextChunk("private");
Builder.AddChunk(CodeCompletionString::CK_SemiColon);
Builder.AddChunk(CodeCompletionString::CK_VerticalSpace);
Results.AddResult(Result(Builder.TakeString()));
}
}

// export
if (!CurrentModule ||
CurrentModule->Kind != Module::ModuleKind::PrivateModuleFragment)
Results.AddResult(Result("export", CodeCompletionResult::RK_Keyword));
}
}

if (SemaRef.getLangOpts().ObjC)
Expand Down Expand Up @@ -2253,6 +2325,11 @@ AddOrdinaryNameResults(SemaCodeCompletion::ParserCompletionContext CCC,
[[fallthrough]];

case SemaCodeCompletion::PCC_Template:
if (SemaRef.getLangOpts().CPlusPlus20 &&
CCC == SemaCodeCompletion::PCC_Template)
Results.AddResult(Result("concept", CCP_Keyword));
[[fallthrough]];

case SemaCodeCompletion::PCC_MemberTemplate:
if (SemaRef.getLangOpts().CPlusPlus && Results.includeCodePatterns()) {
// template < parameters >
Expand All @@ -2265,6 +2342,11 @@ AddOrdinaryNameResults(SemaCodeCompletion::ParserCompletionContext CCC,
Results.AddResult(Result("template", CodeCompletionResult::RK_Keyword));
}

if (SemaRef.getLangOpts().CPlusPlus20 &&
(CCC == SemaCodeCompletion::PCC_Template ||
CCC == SemaCodeCompletion::PCC_MemberTemplate))
Results.AddResult(Result("requires", CCP_Keyword));

AddStorageSpecifiers(CCC, SemaRef.getLangOpts(), Results);
AddFunctionSpecifiers(CCC, SemaRef.getLangOpts(), Results);
break;
Expand Down Expand Up @@ -2486,6 +2568,14 @@ AddOrdinaryNameResults(SemaCodeCompletion::ParserCompletionContext CCC,
Builder.AddPlaceholderChunk("expression");
Builder.AddChunk(CodeCompletionString::CK_SemiColon);
Results.AddResult(Result(Builder.TakeString()));
// "co_return expression ;" for coroutines(C++20).
if (SemaRef.getLangOpts().CPlusPlus20) {
Builder.AddTypedTextChunk("co_return");
Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
Builder.AddPlaceholderChunk("expression");
Builder.AddChunk(CodeCompletionString::CK_SemiColon);
Results.AddResult(Result(Builder.TakeString()));
}
// When boolean, also add 'return true;' and 'return false;'.
if (ReturnType->isBooleanType()) {
Builder.AddTypedTextChunk("return true");
Expand Down Expand Up @@ -2706,6 +2796,44 @@ AddOrdinaryNameResults(SemaCodeCompletion::ParserCompletionContext CCC,
Builder.AddChunk(CodeCompletionString::CK_RightParen);
Results.AddResult(Result(Builder.TakeString()));
}

if (SemaRef.getLangOpts().CPlusPlus20) {
// co_await expression
Builder.AddTypedTextChunk("co_await");
Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
Builder.AddPlaceholderChunk("expression");
Results.AddResult(Result(Builder.TakeString()));

// co_yield expression
Builder.AddTypedTextChunk("co_yield");
Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
Builder.AddPlaceholderChunk("expression");
Results.AddResult(Result(Builder.TakeString()));

// requires (parameters) { requirements }
Builder.AddResultTypeChunk("bool");
Builder.AddTypedTextChunk("requires");
Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
Builder.AddChunk(CodeCompletionString::CK_LeftParen);
Builder.AddPlaceholderChunk("parameters");
Builder.AddChunk(CodeCompletionString::CK_RightParen);
Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
Builder.AddChunk(CodeCompletionString::CK_LeftBrace);
Builder.AddChunk(CodeCompletionString::CK_VerticalSpace);
Builder.AddPlaceholderChunk("requirements");
Builder.AddChunk(CodeCompletionString::CK_VerticalSpace);
Builder.AddChunk(CodeCompletionString::CK_RightBrace);
Results.AddResult(Result(Builder.TakeString()));

if (SemaRef.CurContext->isRequiresExprBody()) {
// requires expression ;
Builder.AddTypedTextChunk("requires");
Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
Builder.AddPlaceholderChunk("expression");
Builder.AddChunk(CodeCompletionString::CK_SemiColon);
Results.AddResult(Result(Builder.TakeString()));
}
}
}

if (SemaRef.getLangOpts().ObjC) {
Expand Down
57 changes: 57 additions & 0 deletions clang/test/CodeCompletion/keywords-cxx20.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module;

export module M;

export const char8_t x = 1;

template<typename T> requires true
const int y = requires { typename T::type; requires T::value; };

class co_test {};

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: 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: module <#name#>;

// RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:14:3 %s | FileCheck --check-prefix=CHECK-MODULE3 %s
// CHECK-MODULE3: module: private;

// RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:3:3 %s | FileCheck --check-prefix=CHECK-EXPORT %s
// CHECK-EXPORT: export

// RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:5:11 %s | FileCheck --check-prefix=CHECK-CONST %s
// CHECK-CONST: const
// CHECK-CONST: consteval
// CHECK-CONST: constexpr
// CHECK-CONST: constinit

// RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:5:19 %s | FileCheck --check-prefix=CHECK-CHAR %s
// CHECK-CHAR: char8_t

// RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:8:3 %s | FileCheck --check-prefix=CHECK-CONSTRAINT %s
// CHECK-CONSTRAINT: concept
// CHECK-CONSTRAINT: const
// CHECK-CONSTRAINT: consteval
// CHECK-CONSTRAINT: constexpr
// CHECK-CONSTRAINT: constinit

// RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:7:27 %s | FileCheck --check-prefix=CHECK-REQUIRES2 %s
// CHECK-REQUIRES2: requires

// RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:8:20 %s | FileCheck -check-prefix=CHECK-REQUIRE %s
// CHECK-REQUIRE: [#bool#]requires (<#parameters#>) {
// CHECK-REQUIRE: <#requirements#>
// CHECK-REQUIRE: }

// RUN: %clang_cc1 -std=c++20 -code-completion-at=%s:12:13 %s | FileCheck --check-prefix=CHECK-COROUTINE %s
// CHECK-COROUTINE: co_await <#expression#>
// CHECK-COROUTINE: co_return <#expression#>;
// CHECK-COROUTINE: co_yield <#expression#>