diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -11996,6 +11996,7 @@ void CodeCompleteLambdaIntroducer(Scope *S, LambdaIntroducer &Intro, bool AfterAmpersand); + void CodeCompleteAfterFunctionEquals(Declarator &D); void CodeCompleteObjCAtDirective(Scope *S); void CodeCompleteObjCAtVisibility(Scope *S); diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -1898,46 +1898,52 @@ } // Check to see if we have a function *definition* which must have a body. - if (D.isFunctionDeclarator() && - // Look at the next token to make sure that this isn't a function - // declaration. We have to check this because __attribute__ might be the - // start of a function definition in GCC-extended K&R C. - !isDeclarationAfterDeclarator()) { - - // Function definitions are only allowed at file scope and in C++ classes. - // The C++ inline method definition case is handled elsewhere, so we only - // need to handle the file scope definition case. - if (Context == DeclaratorContext::FileContext) { - if (isStartOfFunctionDefinition(D)) { - if (DS.getStorageClassSpec() == DeclSpec::SCS_typedef) { - Diag(Tok, diag::err_function_declared_typedef); - - // Recover by treating the 'typedef' as spurious. - DS.ClearStorageClassSpecs(); - } + if (D.isFunctionDeclarator()) { + if (Tok.is(tok::equal) && NextToken().is(tok::code_completion)) { + Actions.CodeCompleteAfterFunctionEquals(D); + cutOffParsing(); + return nullptr; + } + // Look at the next token to make sure that this isn't a function + // declaration. We have to check this because __attribute__ might be the + // start of a function definition in GCC-extended K&R C. + if (!isDeclarationAfterDeclarator()) { + + // Function definitions are only allowed at file scope and in C++ classes. + // The C++ inline method definition case is handled elsewhere, so we only + // need to handle the file scope definition case. + if (Context == DeclaratorContext::FileContext) { + if (isStartOfFunctionDefinition(D)) { + if (DS.getStorageClassSpec() == DeclSpec::SCS_typedef) { + Diag(Tok, diag::err_function_declared_typedef); + + // Recover by treating the 'typedef' as spurious. + DS.ClearStorageClassSpecs(); + } - Decl *TheDecl = - ParseFunctionDefinition(D, ParsedTemplateInfo(), &LateParsedAttrs); - return Actions.ConvertDeclToDeclGroup(TheDecl); - } + Decl *TheDecl = ParseFunctionDefinition(D, ParsedTemplateInfo(), + &LateParsedAttrs); + return Actions.ConvertDeclToDeclGroup(TheDecl); + } - if (isDeclarationSpecifier()) { - // If there is an invalid declaration specifier right after the - // function prototype, then we must be in a missing semicolon case - // where this isn't actually a body. Just fall through into the code - // that handles it as a prototype, and let the top-level code handle - // the erroneous declspec where it would otherwise expect a comma or - // semicolon. + if (isDeclarationSpecifier()) { + // If there is an invalid declaration specifier right after the + // function prototype, then we must be in a missing semicolon case + // where this isn't actually a body. Just fall through into the code + // that handles it as a prototype, and let the top-level code handle + // the erroneous declspec where it would otherwise expect a comma or + // semicolon. + } else { + Diag(Tok, diag::err_expected_fn_body); + SkipUntil(tok::semi); + return nullptr; + } } else { - Diag(Tok, diag::err_expected_fn_body); - SkipUntil(tok::semi); - return nullptr; - } - } else { - if (Tok.is(tok::l_brace)) { - Diag(Tok, diag::err_function_definition_not_allowed); - SkipMalformedDecl(); - return nullptr; + if (Tok.is(tok::l_brace)) { + Diag(Tok, diag::err_function_definition_not_allowed); + SkipMalformedDecl(); + return nullptr; + } } } } diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -2713,6 +2713,11 @@ DefinitionKind = FDK_Defaulted; else if (KW.is(tok::kw_delete)) DefinitionKind = FDK_Deleted; + else if (KW.is(tok::code_completion)) { + Actions.CodeCompleteAfterFunctionEquals(DeclaratorInfo); + cutOffParsing(); + return nullptr; + } } } DeclaratorInfo.setFunctionDefinitionKind(DefinitionKind); diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -24,11 +24,13 @@ #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Type.h" #include "clang/Basic/CharInfo.h" +#include "clang/Basic/OperatorKinds.h" #include "clang/Basic/Specifiers.h" #include "clang/Lex/HeaderSearch.h" #include "clang/Lex/MacroInfo.h" #include "clang/Lex/Preprocessor.h" #include "clang/Sema/CodeCompleteConsumer.h" +#include "clang/Sema/DeclSpec.h" #include "clang/Sema/Designator.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Overload.h" @@ -6266,6 +6268,53 @@ Results.data(), Results.size()); } +void Sema::CodeCompleteAfterFunctionEquals(Declarator &D) { + if (!LangOpts.CPlusPlus11) + return; + ResultBuilder Results(*this, CodeCompleter->getAllocator(), + CodeCompleter->getCodeCompletionTUInfo(), + CodeCompletionContext::CCC_Other); + auto ShouldAddDefault = [&D, this]() { + if (!D.isFunctionDeclarator()) + return false; + auto &Id = D.getName(); + if (Id.getKind() == UnqualifiedIdKind::IK_DestructorName) + return true; + // FIXME(liuhui): Ideally, we should check the constructor parameter list to + // verify that it is the default, copy or move constructor? + if (Id.getKind() == UnqualifiedIdKind::IK_ConstructorName && + D.getFunctionTypeInfo().NumParams <= 1) + return true; + if (Id.getKind() == UnqualifiedIdKind::IK_OperatorFunctionId) { + auto Op = Id.OperatorFunctionId.Operator; + // FIXME(liuhui): Ideally, we should check the function parameter list to + // verify that it is the copy or move assignment? + if (Op == OverloadedOperatorKind::OO_Equal) + return true; + if (LangOpts.CPlusPlus20 && + (Op == OverloadedOperatorKind::OO_EqualEqual || + Op == OverloadedOperatorKind::OO_ExclaimEqual || + Op == OverloadedOperatorKind::OO_Less || + Op == OverloadedOperatorKind::OO_LessEqual || + Op == OverloadedOperatorKind::OO_Greater || + Op == OverloadedOperatorKind::OO_GreaterEqual || + Op == OverloadedOperatorKind::OO_Spaceship)) + return true; + } + return false; + }; + + Results.EnterNewScope(); + if (ShouldAddDefault()) + Results.AddResult("default"); + // FIXME(liuhui): Ideally, we should only provide `delete` completion for the + // first function declaration. + Results.AddResult("delete"); + Results.ExitScope(); + HandleCodeCompleteResults(this, CodeCompleter, Results.getCompletionContext(), + Results.data(), Results.size()); +} + /// Macro that optionally prepends an "@" to the string literal passed in via /// Keyword, depending on whether NeedAt is true or false. #define OBJC_AT_KEYWORD_NAME(NeedAt, Keyword) ((NeedAt) ? "@" Keyword : Keyword) diff --git a/clang/test/CodeCompletion/after-function-equals.cpp b/clang/test/CodeCompletion/after-function-equals.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeCompletion/after-function-equals.cpp @@ -0,0 +1,47 @@ +struct A { + A() = default; + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:2:9 -std=gnu++11 %s -o - | FileCheck -check-prefix=CHECK-CC1 %s + // CHECK-CC1: COMPLETION: default + // CHECK-CC1-NEXT: COMPLETION: delete + + A(const A &) = default; + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:7:18 -std=gnu++11 %s -o - | FileCheck -check-prefix=CHECK-CC2 %s + // CHECK-CC2: COMPLETION: default + // CHECK-CC2-NEXT: COMPLETION: delete + + A(const A &, int) = delete; + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:12:23 -std=gnu++11 %s -o - | FileCheck -check-prefix=CHECK-CC3 %s + // CHECK-CC3-NOT: COMPLETION: default + // CHECK-CC3: COMPLETION: delete + + A(A &&); + + A &operator=(const A &) = default; + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:19:29 -std=gnu++11 %s -o - | FileCheck -check-prefix=CHECK-CC4 %s + // CHECK-CC4: COMPLETION: default + // CHECK-CC4-NEXT: COMPLETION: delete + + bool operator==(const A &) const = delete; + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:24:38 -std=gnu++11 %s -o - | FileCheck -check-prefix=CHECK-CC5 %s + // CHECK-CC5-NOT: COMPLETION: default + // CHECK-CC5: COMPLETION: delete + + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:24:38 -std=gnu++20 %s -o - | FileCheck -check-prefix=CHECK-CC6 %s + // CHECK-CC6: COMPLETION: default + // CHECK-CC6-NEXT: COMPLETION: delete + + void test() = delete; + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:33:17 -std=gnu++11 %s -o - | FileCheck -check-prefix=CHECK-CC7 %s + // CHECK-CC7-NOT: COMPLETION: default + // CHECK-CC7: COMPLETION: delete +}; + +A::A(A &&) = default; +// RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:39:14 -std=gnu++11 %s -o - | FileCheck -check-prefix=CHECK-CC8 %s +// CHECK-CC8: COMPLETION: default +// CHECK-CC8-NEXT: COMPLETION: delete + +void test() = delete; +// RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:44:15 -std=gnu++11 %s -o - | FileCheck -check-prefix=CHECK-CC9 %s +// CHECK-CC9-NOT: COMPLETION: default +// CHECK-CC9: COMPLETION: delete \ No newline at end of file