From 940249d8175d8db209f3e6e4557aaa6f1d99c00f Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 4 Sep 2019 20:30:37 +0000 Subject: [c++20] P1143R2: Add support for the C++20 'constinit' keyword. This is mostly the same as the [[clang::require_constant_initialization]] attribute, but has a couple of additional syntactic and semantic restrictions. In passing, I added a warning for the attribute form being added after we have already seen the initialization of the variable (but before we see the definition); that case previously slipped between the cracks and the attribute was silently ignored. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@370972 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/AST/Decl.h | 8 + include/clang/Basic/Attr.td | 20 +-- include/clang/Basic/DiagnosticCommonKinds.td | 12 +- include/clang/Basic/DiagnosticParseKinds.td | 3 +- include/clang/Basic/DiagnosticSemaKinds.td | 34 +++- include/clang/Basic/Specifiers.h | 3 +- include/clang/Basic/TokenKinds.def | 1 + lib/AST/Decl.cpp | 16 ++ lib/Frontend/InitPreprocessor.cpp | 4 +- lib/Parse/ParseDecl.cpp | 12 +- lib/Parse/ParseDeclCXX.cpp | 2 + lib/Parse/ParseTentative.cpp | 1 + lib/Sema/DeclSpec.cpp | 15 +- lib/Sema/SemaDecl.cpp | 172 +++++++++++++++++---- lib/Sema/SemaDeclAttr.cpp | 4 +- lib/Sema/SemaType.cpp | 4 +- test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p1.cpp | 55 +++++++ test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p2.cpp | 8 + test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p3.cpp | 6 + test/FixIt/fixit-c++2a.cpp | 38 ++++- test/Lexer/cxx-features.cpp | 4 + test/Lexer/cxx2a_keyword_as_cxx17.cpp | 2 + test/Misc/pragma-attribute-cxx.cpp | 4 +- ...pragma-attribute-supported-attributes-list.test | 2 +- test/Parser/cxx0x-decl.cpp | 19 ++- .../attr-require-constant-initialization.cpp | 11 ++ www/cxx_status.html | 2 +- 27 files changed, 385 insertions(+), 77 deletions(-) create mode 100644 test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p1.cpp create mode 100644 test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p2.cpp create mode 100644 test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p3.cpp diff --git a/include/clang/AST/Decl.h b/include/clang/AST/Decl.h index e593dafb5f..0d9da7d0dc 100644 --- a/include/clang/AST/Decl.h +++ b/include/clang/AST/Decl.h @@ -1226,6 +1226,14 @@ public: void setInit(Expr *I); + /// Get the initializing declaration of this variable, if any. This is + /// usually the definition, except that for a static data member it can be + /// the in-class declaration. + VarDecl *getInitializingDeclaration(); + const VarDecl *getInitializingDeclaration() const { + return const_cast(this)->getInitializingDeclaration(); + } + /// Determine whether this variable's value might be usable in a /// constant expression, according to the relevant language standard. /// This only checks properties of the declaration, and does not check diff --git a/include/clang/Basic/Attr.td b/include/clang/Basic/Attr.td index ac223bd06a..fbf26ec9b9 100644 --- a/include/clang/Basic/Attr.td +++ b/include/clang/Basic/Attr.td @@ -911,6 +911,17 @@ def Const : InheritableAttr { let Documentation = [Undocumented]; } +def ConstInit : InheritableAttr { + // This attribute does not have a C [[]] spelling because it requires the + // CPlusPlus language option. + let Spellings = [Keyword<"constinit">, + Clang<"require_constant_initialization", 0>]; + let Subjects = SubjectList<[GlobalVar], ErrorDiag>; + let Accessors = [Accessor<"isConstinit", [Keyword<"constinit">]>]; + let Documentation = [RequireConstantInitDocs]; + let LangOpts = [CPlusPlus]; +} + def Constructor : InheritableAttr { let Spellings = [GCC<"constructor">]; let Args = [DefaultIntArgument<"Priority", 65535>]; @@ -1935,15 +1946,6 @@ def ReqdWorkGroupSize : InheritableAttr { let Documentation = [Undocumented]; } -def RequireConstantInit : InheritableAttr { - // This attribute does not have a C [[]] spelling because it requires the - // CPlusPlus language option. - let Spellings = [Clang<"require_constant_initialization", 0>]; - let Subjects = SubjectList<[GlobalVar], ErrorDiag>; - let Documentation = [RequireConstantInitDocs]; - let LangOpts = [CPlusPlus]; -} - def WorkGroupSizeHint : InheritableAttr { // Does not have a [[]] spelling because it is an OpenCL-related attribute. let Spellings = [GNU<"work_group_size_hint">]; diff --git a/include/clang/Basic/DiagnosticCommonKinds.td b/include/clang/Basic/DiagnosticCommonKinds.td index f12ee1ba92..8196a4601c 100644 --- a/include/clang/Basic/DiagnosticCommonKinds.td +++ b/include/clang/Basic/DiagnosticCommonKinds.td @@ -12,6 +12,11 @@ let Component = "Common" in { +// Substitutions. + +def select_constexpr_spec_kind : TextSubstitution< + "%select{|constexpr|consteval|constinit}0">; + // Basic. def fatal_too_many_errors @@ -112,6 +117,10 @@ def err_attribute_not_type_attr : Error< "%0 attribute cannot be applied to types">; def err_enum_template : Error<"enumeration cannot be a template">; +def warn_cxx20_compat_consteval : Warning< + "'consteval' specifier is incompatible with C++ standards before C++20">, + InGroup, DefaultIgnore; + } let CategoryName = "Nullability Issue" in { @@ -177,9 +186,6 @@ def ext_cxx11_longlong : Extension< def warn_cxx98_compat_longlong : Warning< "'long long' is incompatible with C++98">, InGroup, DefaultIgnore; -def warn_cxx20_compat_consteval : Warning< - "consteval is incompatible with C++ standards before C++20">, - InGroup, DefaultIgnore; def err_integer_literal_too_large : Error< "integer literal is too large to be represented in any %select{signed |}0" "integer type">; diff --git a/include/clang/Basic/DiagnosticParseKinds.td b/include/clang/Basic/DiagnosticParseKinds.td index c0b0f444df..a8bc1421ce 100644 --- a/include/clang/Basic/DiagnosticParseKinds.td +++ b/include/clang/Basic/DiagnosticParseKinds.td @@ -358,7 +358,8 @@ def err_typename_invalid_storageclass : Error< def err_typename_invalid_functionspec : Error< "type name does not allow function specifier to be specified">; def err_typename_invalid_constexpr : Error< - "type name does not allow %select{constexpr|consteval}0 specifier to be specified">; + "type name does not allow %sub{select_constexpr_spec_kind}0 specifier " + "to be specified">; def err_typename_identifiers_only : Error< "typename is allowed for identifiers only">; diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 80f9439c20..278bcae3dc 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -2343,18 +2343,18 @@ def warn_cxx14_compat_constexpr_not_const : Warning< "in C++14; add 'const' to avoid a change in behavior">, InGroup>; def err_invalid_constexpr : Error< - "%select{function parameter|typedef|non-static data member}0 " - "cannot be %select{constexpr|consteval}1">; + "%select{function parameter|typedef}0 " + "cannot be %sub{select_constexpr_spec_kind}1">; def err_invalid_constexpr_member : Error<"non-static data member cannot be " "constexpr%select{; did you intend to make it %select{const|static}0?|}1">; def err_constexpr_tag : Error< "%select{class|struct|interface|union|enum}0 " - "cannot be marked %select{constexpr|consteval}1">; + "cannot be marked %sub{select_constexpr_spec_kind}1">; def err_constexpr_dtor : Error< - "destructor cannot be marked %select{constexpr|consteval}0">; + "destructor cannot be marked %sub{select_constexpr_spec_kind}0">; def err_constexpr_wrong_decl_kind : Error< - "%select{constexpr|consteval}0 can only be used " - "in %select{variable and |}0function declarations">; + "%sub{select_constexpr_spec_kind}0 can only be used " + "in %select{|variable and function|function|variable}0 declarations">; def err_invalid_constexpr_var_decl : Error< "constexpr variable declaration must be a definition">; def err_constexpr_static_mem_var_requires_init : Error< @@ -7514,10 +7514,30 @@ def note_inequality_comparison_to_or_assign : Note< def err_incomplete_type_used_in_type_trait_expr : Error< "incomplete type %0 used in type trait expression">; +// C++20 constinit and require_constant_initialization attribute +def warn_cxx20_compat_constinit : Warning< + "'constinit' specifier is incompatible with C++ standards before C++20">, + InGroup, DefaultIgnore; +def err_constinit_local_variable : Error< + "local variable cannot be declared 'constinit'">; def err_require_constant_init_failed : Error< "variable does not have a constant initializer">; def note_declared_required_constant_init_here : Note< - "required by 'require_constant_initialization' attribute here">; + "required by %select{'require_constant_initialization' attribute|" + "'constinit' specifier}0 here">; +def ext_constinit_missing : ExtWarn< + "'constinit' specifier missing on initializing declaration of %0">, + InGroup>; +def note_constinit_specified_here : Note<"variable declared constinit here">; +def err_constinit_added_too_late : Error< + "'constinit' specifier added after initialization of variable">; +def warn_require_const_init_added_too_late : Warning< + "'require_constant_initialization' attribute added after initialization " + "of variable">, InGroup; +def note_constinit_missing_here : Note< + "add the " + "%select{'require_constant_initialization' attribute|'constinit' specifier}0 " + "to the initializing declaration here">; def err_dimension_expr_not_constant_integer : Error< "dimension expression does not evaluate to a constant unsigned int">; diff --git a/include/clang/Basic/Specifiers.h b/include/clang/Basic/Specifiers.h index d1236e798e..fad97a26d9 100644 --- a/include/clang/Basic/Specifiers.h +++ b/include/clang/Basic/Specifiers.h @@ -32,7 +32,8 @@ namespace clang { enum ConstexprSpecKind { CSK_unspecified, CSK_constexpr, - CSK_consteval + CSK_consteval, + CSK_constinit }; /// Specifies the width of a type, e.g., short, long, or long long. diff --git a/include/clang/Basic/TokenKinds.def b/include/clang/Basic/TokenKinds.def index 5221fcec92..0a8d27ec2e 100644 --- a/include/clang/Basic/TokenKinds.def +++ b/include/clang/Basic/TokenKinds.def @@ -389,6 +389,7 @@ MODULES_KEYWORD(import) // C++20 keywords. CXX2A_KEYWORD(char8_t , CHAR8SUPPORT) CXX2A_KEYWORD(consteval , 0) +CXX2A_KEYWORD(constinit , 0) // C11 Extension KEYWORD(_Float16 , KEYALL) diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 21dd542583..6c0c828146 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -2220,6 +2220,22 @@ Stmt **VarDecl::getInitAddress() { return Init.getAddrOfPtr1(); } +VarDecl *VarDecl::getInitializingDeclaration() { + VarDecl *Def = nullptr; + for (auto I : redecls()) { + if (I->hasInit()) + return I; + + if (I->isThisDeclarationADefinition()) { + if (isStaticDataMember()) + return I; + else + Def = I; + } + } + return Def; +} + bool VarDecl::isOutOfLine() const { if (Decl::isOutOfLine()) return true; diff --git a/lib/Frontend/InitPreprocessor.cpp b/lib/Frontend/InitPreprocessor.cpp index 6eafe81e82..95d9f62c60 100644 --- a/lib/Frontend/InitPreprocessor.cpp +++ b/lib/Frontend/InitPreprocessor.cpp @@ -541,8 +541,10 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts, Builder.defineMacro("__cpp_template_template_args", "201611L"); // C++20 features. - if (LangOpts.CPlusPlus2a) + if (LangOpts.CPlusPlus2a) { Builder.defineMacro("__cpp_conditional_explicit", "201806L"); + Builder.defineMacro("__cpp_constinit", "201907L"); + } if (LangOpts.Char8) Builder.defineMacro("__cpp_char8_t", "201811L"); Builder.defineMacro("__cpp_impl_destroying_delete", "201806L"); diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index c4c5045d2f..d66c539056 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -2514,7 +2514,7 @@ void Parser::ParseSpecifierQualifierList(DeclSpec &DS, AccessSpecifier AS, // Issue diagnostic and remove constexpr specifier if present. if (DS.hasConstexprSpecifier() && DSC != DeclSpecContext::DSC_condition) { Diag(DS.getConstexprSpecLoc(), diag::err_typename_invalid_constexpr) - << (DS.getConstexprSpecifier() == CSK_consteval); + << DS.getConstexprSpecifier(); DS.ClearConstexprSpec(); } } @@ -3653,15 +3653,16 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, isInvalid = DS.setModulePrivateSpec(Loc, PrevSpec, DiagID); break; - // constexpr + // constexpr, consteval, constinit specifiers case tok::kw_constexpr: isInvalid = DS.SetConstexprSpec(CSK_constexpr, Loc, PrevSpec, DiagID); break; - - // consteval case tok::kw_consteval: isInvalid = DS.SetConstexprSpec(CSK_consteval, Loc, PrevSpec, DiagID); break; + case tok::kw_constinit: + isInvalid = DS.SetConstexprSpec(CSK_constinit, Loc, PrevSpec, DiagID); + break; // type-specifier case tok::kw_short: @@ -5080,8 +5081,9 @@ bool Parser::isDeclarationSpecifier(bool DisambiguatingWithExpression) { case tok::annot_decltype: case tok::kw_constexpr: - // C++20 consteval. + // C++20 consteval and constinit. case tok::kw_consteval: + case tok::kw_constinit: // C11 _Atomic case tok::kw__Atomic: diff --git a/lib/Parse/ParseDeclCXX.cpp b/lib/Parse/ParseDeclCXX.cpp index cf03ed6379..529c09a50e 100644 --- a/lib/Parse/ParseDeclCXX.cpp +++ b/lib/Parse/ParseDeclCXX.cpp @@ -1313,6 +1313,8 @@ bool Parser::isValidAfterTypeSpecifier(bool CouldBeBitfield) { case tok::kw_mutable: // struct foo {...} mutable x; case tok::kw_thread_local: // struct foo {...} thread_local x; case tok::kw_constexpr: // struct foo {...} constexpr x; + case tok::kw_consteval: // struct foo {...} consteval x; + case tok::kw_constinit: // struct foo {...} constinit x; // As shown above, type qualifiers and storage class specifiers absolutely // can occur after class specifiers according to the grammar. However, // almost no one actually writes code like this. If we see one of these, diff --git a/lib/Parse/ParseTentative.cpp b/lib/Parse/ParseTentative.cpp index 8e56e0c696..2eee15b7e0 100644 --- a/lib/Parse/ParseTentative.cpp +++ b/lib/Parse/ParseTentative.cpp @@ -1408,6 +1408,7 @@ Parser::isCXXDeclarationSpecifier(Parser::TPResult BracedCastResult, case tok::kw_typedef: case tok::kw_constexpr: case tok::kw_consteval: + case tok::kw_constinit: // storage-class-specifier case tok::kw_register: case tok::kw_static: diff --git a/lib/Sema/DeclSpec.cpp b/lib/Sema/DeclSpec.cpp index 77e5eb0956..639231c872 100644 --- a/lib/Sema/DeclSpec.cpp +++ b/lib/Sema/DeclSpec.cpp @@ -569,6 +569,7 @@ const char *DeclSpec::getSpecifierName(ConstexprSpecKind C) { case CSK_unspecified: return "unspecified"; case CSK_constexpr: return "constexpr"; case CSK_consteval: return "consteval"; + case CSK_constinit: return "constinit"; } llvm_unreachable("Unknown ConstexprSpecKind"); } @@ -1036,13 +1037,9 @@ bool DeclSpec::setModulePrivateSpec(SourceLocation Loc, const char *&PrevSpec, bool DeclSpec::SetConstexprSpec(ConstexprSpecKind ConstexprKind, SourceLocation Loc, const char *&PrevSpec, unsigned &DiagID) { - if (getConstexprSpecifier() != CSK_unspecified) { - if (getConstexprSpecifier() == CSK_consteval || ConstexprKind == CSK_consteval) - return BadSpecifier(ConstexprKind, getConstexprSpecifier(), PrevSpec, DiagID); - DiagID = diag::warn_duplicate_declspec; - PrevSpec = "constexpr"; - return true; - } + if (getConstexprSpecifier() != CSK_unspecified) + return BadSpecifier(ConstexprKind, getConstexprSpecifier(), PrevSpec, + DiagID); ConstexprSpecifier = ConstexprKind; ConstexprLoc = Loc; return false; @@ -1291,8 +1288,10 @@ void DeclSpec::Finish(Sema &S, const PrintingPolicy &Policy) { << (TypeSpecType == TST_char16 ? "char16_t" : "char32_t"); if (getConstexprSpecifier() == CSK_constexpr) S.Diag(ConstexprLoc, diag::warn_cxx98_compat_constexpr); - if (getConstexprSpecifier() == CSK_consteval) + else if (getConstexprSpecifier() == CSK_consteval) S.Diag(ConstexprLoc, diag::warn_cxx20_compat_consteval); + else if (getConstexprSpecifier() == CSK_constinit) + S.Diag(ConstexprLoc, diag::warn_cxx20_compat_constinit); // C++ [class.friend]p6: // No storage-class-specifier shall appear in the decl-specifier-seq // of a friend declaration. diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index a96f0d28e8..a2708223c1 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -2661,6 +2661,60 @@ static void checkNewAttributesAfterDef(Sema &S, Decl *New, const Decl *Old) { } } +static void diagnoseMissingConstinit(Sema &S, const VarDecl *InitDecl, + const ConstInitAttr *CIAttr, + bool AttrBeforeInit) { + SourceLocation InsertLoc = InitDecl->getInnerLocStart(); + + // Figure out a good way to write this specifier on the old declaration. + // FIXME: We should just use the spelling of CIAttr, but we don't preserve + // enough of the attribute list spelling information to extract that without + // heroics. + std::string SuitableSpelling; + if (S.getLangOpts().CPlusPlus2a) + SuitableSpelling = + S.PP.getLastMacroWithSpelling(InsertLoc, {tok::kw_constinit}); + if (SuitableSpelling.empty() && S.getLangOpts().CPlusPlus11) + SuitableSpelling = S.PP.getLastMacroWithSpelling( + InsertLoc, + {tok::l_square, tok::l_square, S.PP.getIdentifierInfo("clang"), + tok::coloncolon, + S.PP.getIdentifierInfo("require_constant_initialization"), + tok::r_square, tok::r_square}); + if (SuitableSpelling.empty()) + SuitableSpelling = S.PP.getLastMacroWithSpelling( + InsertLoc, + {tok::kw___attribute, tok::l_paren, tok::r_paren, + S.PP.getIdentifierInfo("require_constant_initialization"), + tok::r_paren, tok::r_paren}); + if (SuitableSpelling.empty() && S.getLangOpts().CPlusPlus2a) + SuitableSpelling = "constinit"; + if (SuitableSpelling.empty() && S.getLangOpts().CPlusPlus11) + SuitableSpelling = "[[clang::require_constant_initialization]]"; + if (SuitableSpelling.empty()) + SuitableSpelling = "__attribute__((require_constant_initialization))"; + SuitableSpelling += " "; + + if (AttrBeforeInit) { + // extern constinit int a; + // int a = 0; // error (missing 'constinit'), accepted as extension + assert(CIAttr->isConstinit() && "should not diagnose this for attribute"); + S.Diag(InitDecl->getLocation(), diag::ext_constinit_missing) + << InitDecl << FixItHint::CreateInsertion(InsertLoc, SuitableSpelling); + S.Diag(CIAttr->getLocation(), diag::note_constinit_specified_here); + } else { + // int a = 0; + // constinit extern int a; // error (missing 'constinit') + S.Diag(CIAttr->getLocation(), + CIAttr->isConstinit() ? diag::err_constinit_added_too_late + : diag::warn_require_const_init_added_too_late) + << FixItHint::CreateRemoval(SourceRange(CIAttr->getLocation())); + S.Diag(InitDecl->getLocation(), diag::note_constinit_missing_here) + << CIAttr->isConstinit() + << FixItHint::CreateInsertion(InsertLoc, SuitableSpelling); + } +} + /// mergeDeclAttributes - Copy attributes from the Old decl to the New one. void Sema::mergeDeclAttributes(NamedDecl *New, Decl *Old, AvailabilityMergeKind AMK) { @@ -2673,6 +2727,41 @@ void Sema::mergeDeclAttributes(NamedDecl *New, Decl *Old, if (!Old->hasAttrs() && !New->hasAttrs()) return; + // [dcl.constinit]p1: + // If the [constinit] specifier is applied to any declaration of a + // variable, it shall be applied to the initializing declaration. + const auto *OldConstInit = Old->getAttr(); + const auto *NewConstInit = New->getAttr(); + if (bool(OldConstInit) != bool(NewConstInit)) { + const auto *OldVD = cast(Old); + auto *NewVD = cast(New); + + // Find the initializing declaration. Note that we might not have linked + // the new declaration into the redeclaration chain yet. + const VarDecl *InitDecl = OldVD->getInitializingDeclaration(); + if (!InitDecl && + (NewVD->hasInit() || NewVD->isThisDeclarationADefinition())) + InitDecl = NewVD; + + if (InitDecl == NewVD) { + // This is the initializing declaration. If it would inherit 'constinit', + // that's ill-formed. (Note that we do not apply this to the attribute + // form). + if (OldConstInit && OldConstInit->isConstinit()) + diagnoseMissingConstinit(*this, NewVD, OldConstInit, + /*AttrBeforeInit=*/true); + } else if (NewConstInit) { + // This is the first time we've been told that this declaration should + // have a constant initializer. If we already saw the initializing + // declaration, this is too late. + if (InitDecl && InitDecl != NewVD) { + diagnoseMissingConstinit(*this, InitDecl, NewConstInit, + /*AttrBeforeInit=*/false); + NewVD->dropAttr(); + } + } + } + // Attributes declared post-definition are currently ignored. checkNewAttributesAfterDef(*this, New, Old); @@ -4315,13 +4404,13 @@ Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS, DeclSpec &DS, // and definitions of functions and variables. // C++2a [dcl.constexpr]p1: The consteval specifier shall be applied only to // the declaration of a function or function template - bool IsConsteval = DS.getConstexprSpecifier() == CSK_consteval; if (Tag) Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_tag) - << GetDiagnosticTypeSpecifierID(DS.getTypeSpecType()) << IsConsteval; + << GetDiagnosticTypeSpecifierID(DS.getTypeSpecType()) + << DS.getConstexprSpecifier(); else Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_wrong_decl_kind) - << IsConsteval; + << DS.getConstexprSpecifier(); // Don't emit warnings after this error. return TagD; } @@ -5776,7 +5865,7 @@ Sema::ActOnTypedefDeclarator(Scope* S, Declarator& D, DeclContext* DC, << getLangOpts().CPlusPlus17; if (D.getDeclSpec().hasConstexprSpecifier()) Diag(D.getDeclSpec().getConstexprSpecLoc(), diag::err_invalid_constexpr) - << 1 << (D.getDeclSpec().getConstexprSpecifier() == CSK_consteval); + << 1 << D.getDeclSpec().getConstexprSpecifier(); if (D.getName().Kind != UnqualifiedIdKind::IK_Identifier) { if (D.getName().Kind == UnqualifiedIdKind::IK_DeductionGuideName) @@ -6671,19 +6760,6 @@ NamedDecl *Sema::ActOnVariableDeclarator( if (TemplateParamLists.size() > VDTemplateParamLists) NewVD->setTemplateParameterListsInfo( Context, TemplateParamLists.drop_back(VDTemplateParamLists)); - - if (D.getDeclSpec().hasConstexprSpecifier()) { - NewVD->setConstexpr(true); - // C++1z [dcl.spec.constexpr]p1: - // A static data member declared with the constexpr specifier is - // implicitly an inline variable. - if (NewVD->isStaticDataMember() && getLangOpts().CPlusPlus17) - NewVD->setImplicitlyInline(); - if (D.getDeclSpec().getConstexprSpecifier() == CSK_consteval) - Diag(D.getDeclSpec().getConstexprSpecLoc(), - diag::err_constexpr_wrong_decl_kind) - << /*consteval*/ 1; - } } if (D.getDeclSpec().isInlineSpecified()) { @@ -6749,6 +6825,36 @@ NamedDecl *Sema::ActOnVariableDeclarator( NewVD->setTSCSpec(TSCS); } + switch (D.getDeclSpec().getConstexprSpecifier()) { + case CSK_unspecified: + break; + + case CSK_consteval: + Diag(D.getDeclSpec().getConstexprSpecLoc(), + diag::err_constexpr_wrong_decl_kind) + << D.getDeclSpec().getConstexprSpecifier(); + LLVM_FALLTHROUGH; + + case CSK_constexpr: + NewVD->setConstexpr(true); + // C++1z [dcl.spec.constexpr]p1: + // A static data member declared with the constexpr specifier is + // implicitly an inline variable. + if (NewVD->isStaticDataMember() && getLangOpts().CPlusPlus17) + NewVD->setImplicitlyInline(); + break; + + case CSK_constinit: + if (!NewVD->hasGlobalStorage()) + Diag(D.getDeclSpec().getConstexprSpecLoc(), + diag::err_constinit_local_variable); + else + NewVD->addAttr(::new (Context) ConstInitAttr( + SourceRange(D.getDeclSpec().getConstexprSpecLoc()), Context, + ConstInitAttr::Keyword_constinit)); + break; + } + // C99 6.7.4p3 // An inline definition of a function with external linkage shall // not contain a definition of a modifiable object with static or @@ -7989,7 +8095,7 @@ static StorageClass getFunctionStorageClass(Sema &SemaRef, Declarator &D) { return SC_None; } -static FunctionDecl* CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, +static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, DeclContext *DC, QualType &R, TypeSourceInfo *TInfo, StorageClass SC, @@ -8021,7 +8127,16 @@ static FunctionDecl* CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, } ExplicitSpecifier ExplicitSpecifier = D.getDeclSpec().getExplicitSpecifier(); + ConstexprSpecKind ConstexprKind = D.getDeclSpec().getConstexprSpecifier(); + if (ConstexprKind == CSK_constinit) { + SemaRef.Diag(D.getDeclSpec().getConstexprSpecLoc(), + diag::err_constexpr_wrong_decl_kind) + << ConstexprKind; + ConstexprKind = CSK_unspecified; + D.getMutableDeclSpec().ClearConstexprSpec(); + } + // Check that the return type is not an abstract class type. // For record types, this is done by the AbstractClassUsageDiagnoser once // the class has been completely parsed. @@ -8452,7 +8567,6 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC, bool isInline = D.getDeclSpec().isInlineSpecified(); bool isVirtual = D.getDeclSpec().isVirtualSpecified(); bool hasExplicit = D.getDeclSpec().hasExplicitSpecifier(); - ConstexprSpecKind ConstexprKind = D.getDeclSpec().getConstexprSpecifier(); isFriend = D.getDeclSpec().isFriendSpecified(); if (isFriend && !isInline && D.isFunctionDefinition()) { // C++ [class.friend]p5 @@ -8651,7 +8765,8 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC, } } - if (ConstexprKind != CSK_unspecified) { + if (ConstexprSpecKind ConstexprKind = + D.getDeclSpec().getConstexprSpecifier()) { // C++11 [dcl.constexpr]p2: constexpr functions and constexpr constructors // are implicitly inline. NewFD->setImplicitlyInline(); @@ -8659,9 +8774,10 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC, // C++11 [dcl.constexpr]p3: functions declared constexpr are required to // be either constructors or to return a literal type. Therefore, // destructors cannot be declared constexpr. - if (isa(NewFD)) + if (isa(NewFD)) { Diag(D.getDeclSpec().getConstexprSpecLoc(), diag::err_constexpr_dtor) - << (ConstexprKind == CSK_consteval); + << ConstexprKind; + } } // If __module_private__ was specified, mark the function accordingly. @@ -12043,17 +12159,17 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) { // Don't emit further diagnostics about constexpr globals since they // were just diagnosed. - if (!var->isConstexpr() && GlobalStorage && - var->hasAttr()) { + if (!var->isConstexpr() && GlobalStorage && var->hasAttr()) { // FIXME: Need strict checking in C++03 here. bool DiagErr = getLangOpts().CPlusPlus11 ? !var->checkInitIsICE() : !checkConstInit(); if (DiagErr) { - auto attr = var->getAttr(); + auto *Attr = var->getAttr(); Diag(var->getLocation(), diag::err_require_constant_init_failed) << Init->getSourceRange(); - Diag(attr->getLocation(), diag::note_declared_required_constant_init_here) - << attr->getRange(); + Diag(Attr->getLocation(), + diag::note_declared_required_constant_init_here) + << Attr->getRange() << Attr->isConstinit(); if (getLangOpts().CPlusPlus11) { APValue Value; SmallVector Notes; @@ -12546,7 +12662,7 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D) { << getLangOpts().CPlusPlus17; if (DS.hasConstexprSpecifier()) Diag(DS.getConstexprSpecLoc(), diag::err_invalid_constexpr) - << 0 << (D.getDeclSpec().getConstexprSpecifier() == CSK_consteval); + << 0 << D.getDeclSpec().getConstexprSpecifier(); DiagnoseFunctionSpecifiers(DS); diff --git a/lib/Sema/SemaDeclAttr.cpp b/lib/Sema/SemaDeclAttr.cpp index 2a72f7d5c6..2890f909dc 100644 --- a/lib/Sema/SemaDeclAttr.cpp +++ b/lib/Sema/SemaDeclAttr.cpp @@ -7057,8 +7057,8 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, case ParsedAttr::AT_VecTypeHint: handleVecTypeHint(S, D, AL); break; - case ParsedAttr::AT_RequireConstantInit: - handleSimpleAttribute(S, D, AL); + case ParsedAttr::AT_ConstInit: + handleSimpleAttribute(S, D, AL); break; case ParsedAttr::AT_InitPriority: handleInitPriorityAttr(S, D, AL); diff --git a/lib/Sema/SemaType.cpp b/lib/Sema/SemaType.cpp index 4b16a09307..f9c36e0183 100644 --- a/lib/Sema/SemaType.cpp +++ b/lib/Sema/SemaType.cpp @@ -5146,9 +5146,9 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state, // C++0x [dcl.constexpr]p9: // A constexpr specifier used in an object declaration declares the object // as const. - if (D.getDeclSpec().hasConstexprSpecifier() && T->isObjectType()) { + if (D.getDeclSpec().getConstexprSpecifier() == CSK_constexpr && + T->isObjectType()) T.addConst(); - } // If there was an ellipsis in the declarator, the declaration declares a // parameter pack whose type may be a pack expansion type. diff --git a/test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p1.cpp b/test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p1.cpp new file mode 100644 index 0000000000..170c8c7be0 --- /dev/null +++ b/test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p1.cpp @@ -0,0 +1,55 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s + +constinit int a; +constinit thread_local int b; +constinit static int c; + +void f() { + constinit static int a; + constinit thread_local int b; + constinit int c; // expected-error {{local variable cannot be declared 'constinit'}} +} + +namespace missing { + int a; // expected-note {{add the 'constinit' specifier}} + extern constinit int a; // expected-error {{added after initialization}} + + // We allow inheriting 'constinit' from a forward declaration as an extension. + extern constinit int b; // expected-note {{here}} + int b; // expected-warning {{'constinit' specifier missing}} +} + +struct S { + static constinit int a; // expected-note {{here}} + static constinit constexpr int b; // expected-error {{cannot combine with previous}} expected-note {{here}} + static constinit const int c = 1; + static constinit const int d = 1; +}; +int S::a; // expected-warning {{'constinit' specifier missing}} +int S::b; // expected-warning {{'constinit' specifier missing}} +const int S::c; +inline const int S::d; + +struct T { + static int a; + static constexpr int b = 1; // expected-note {{add the 'constinit' specifier}} + static const int c = 1; // expected-note {{add the 'constinit' specifier}} + static const int d = 1; // expected-note {{add the 'constinit' specifier}} +}; +constinit int T::a; +constinit const int T::b; // expected-error {{'constinit' specifier added after initialization}} +constinit const int T::c; // expected-error {{'constinit' specifier added after initialization}} +constinit inline const int T::d; // expected-error {{'constinit' specifier added after initialization}} + +constinit void g() {} // expected-error {{constinit can only be used in variable declarations}} + +// (These used to trigger crashes.) +void h(); +constinit void h(); // expected-error {{constinit can only be used in variable declarations}} +constexpr void i(); // expected-note {{here}} +constinit void i(); // expected-error {{non-constexpr declaration of 'i' follows constexpr declaration}} +// expected-error@-1 {{constinit can only be used in variable declarations}} + +typedef constinit int type; // expected-error {{typedef cannot be constinit}} +using type = constinit int; // expected-error {{type name does not allow constinit specifier}} +auto q() -> int constinit; // expected-error {{type name does not allow constinit specifier}} diff --git a/test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p2.cpp b/test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p2.cpp new file mode 100644 index 0000000000..22ac7a191b --- /dev/null +++ b/test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p2.cpp @@ -0,0 +1,8 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s + +int f(); // expected-note 2{{declared here}} + +constinit int a; +constinit int b = f(); // expected-error {{does not have a constant initializer}} expected-note {{required by}} expected-note {{non-constexpr function 'f'}} +extern constinit int c; // expected-note {{here}} expected-note {{required by}} +int c = f(); // expected-warning {{missing}} expected-error {{does not have a constant initializer}} expected-note {{non-constexpr function 'f'}} diff --git a/test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p3.cpp b/test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p3.cpp new file mode 100644 index 0000000000..0baea03e4b --- /dev/null +++ b/test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p3.cpp @@ -0,0 +1,6 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s + +const char *g() { return "dynamic initialization"; } // expected-note {{declared here}} +constexpr const char *f(bool b) { return b ? "constant initialization" : g(); } // expected-note {{non-constexpr function 'g'}} +constinit const char *c = f(true); +constinit const char *d = f(false); // expected-error {{does not have a constant initializer}} expected-note 2{{}} diff --git a/test/FixIt/fixit-c++2a.cpp b/test/FixIt/fixit-c++2a.cpp index c97bb7ae61..6fe05dabf0 100644 --- a/test/FixIt/fixit-c++2a.cpp +++ b/test/FixIt/fixit-c++2a.cpp @@ -1,7 +1,8 @@ -// RUN: %clang_cc1 -verify -std=c++2a %s +// RUN: %clang_cc1 -verify -std=c++2a -pedantic-errors %s // RUN: cp %s %t // RUN: not %clang_cc1 -x c++ -std=c++2a -fixit %t -// RUN: %clang_cc1 -Wall -pedantic -x c++ -std=c++2a %t +// RUN: %clang_cc1 -Wall -pedantic-errors -x c++ -std=c++2a %t +// RUN: cat %t | FileCheck %s /* This is a test of the various code modification hints that only apply in C++2a. */ @@ -13,3 +14,36 @@ template void init_capture_pack(T ...a) { [&...a]{}; // expected-error {{must appear after the name}} [...&a]{}; // expected-error {{must appear after the name}} } + +namespace constinit_mismatch { + extern thread_local constinit int a; // expected-note {{declared constinit here}} + thread_local int a = 123; // expected-error {{'constinit' specifier missing on initializing declaration of 'a'}} + // CHECK: {{^}} constinit thread_local int a = 123; + + int b = 123; // expected-note {{add the 'constinit' specifier}} + extern constinit int b; // expected-error {{'constinit' specifier added after initialization of variable}} + // CHECK: {{^}} extern int b; + + template struct X { + template static constinit int n; // expected-note {{constinit}} + }; + template template + int X::n = 123; // expected-error {{missing}} + // CHECK: {{^}} constinit int X::n = 123; + +#define ABSL_CONST_INIT [[clang::require_constant_initialization]] + extern constinit int c; // expected-note {{constinit}} + int c; // expected-error {{missing}} + // CHECK: {{^}} ABSL_CONST_INIT int c; + +#define MY_CONST_INIT constinit + extern constinit int d; // expected-note {{constinit}} + int d; // expected-error {{missing}} + // CHECK: {{^}} MY_CONST_INIT int d; +#undef MY_CONST_INIT + + extern constinit int e; // expected-note {{constinit}} + int e; // expected-error {{missing}} + // CHECK: {{^}} ABSL_CONST_INIT int e; +#undef ABSL_CONST_INIT +} diff --git a/test/Lexer/cxx-features.cpp b/test/Lexer/cxx-features.cpp index a8ef6291f4..cda6f888cb 100644 --- a/test/Lexer/cxx-features.cpp +++ b/test/Lexer/cxx-features.cpp @@ -34,6 +34,10 @@ #error "wrong value for __cpp_char8_t" #endif +#if check(constinit, 0, 0, 0, 0, 201907) +#error "wrong value for __cpp_constinit" +#endif + #if check(impl_destroying_delete, 201806, 201806, 201806, 201806, 201806) #error "wrong value for __cpp_impl_destroying_delete" #endif diff --git a/test/Lexer/cxx2a_keyword_as_cxx17.cpp b/test/Lexer/cxx2a_keyword_as_cxx17.cpp index d2ed7d3380..a2e86931e9 100644 --- a/test/Lexer/cxx2a_keyword_as_cxx17.cpp +++ b/test/Lexer/cxx2a_keyword_as_cxx17.cpp @@ -11,3 +11,5 @@ int co_yield = 0; // expected-warning {{'co_yield' is a keyword in C++2a}} int char8_t = 0; // expected-warning {{'char8_t' is a keyword in C++2a}} int concept = 0; // expected-warning {{'concept' is a keyword in C++2a}} int requires = 0; // expected-warning {{'requires' is a keyword in C++2a}} +int consteval = 0; // expected-warning {{'consteval' is a keyword in C++2a}} +int constinit = 0; // expected-warning {{'constinit' is a keyword in C++2a}} diff --git a/test/Misc/pragma-attribute-cxx.cpp b/test/Misc/pragma-attribute-cxx.cpp index a8a3bdebc6..38b025e476 100644 --- a/test/Misc/pragma-attribute-cxx.cpp +++ b/test/Misc/pragma-attribute-cxx.cpp @@ -87,14 +87,14 @@ void testLambdaMethod() { int testCI1 = 1; // CHECK-LABEL: VarDecl{{.*}} testCI1 // CHECK-NEXT: IntegerLiteral -// CHECK-NEXT: RequireConstantInitAttr +// CHECK-NEXT: ConstInitAttr #pragma clang attribute pop int testNoCI = 0; // CHECK-LABEL: VarDecl{{.*}} testNoCI // CHECK-NEXT: IntegerLiteral -// CHECK-NOT: RequireConstantInitAttr +// CHECK-NOT: ConstInitAttr // Check support for CXX11 style attributes #pragma clang attribute push ([[noreturn]], apply_to = function) diff --git a/test/Misc/pragma-attribute-supported-attributes-list.test b/test/Misc/pragma-attribute-supported-attributes-list.test index 83b8b9f0b9..311ba1a857 100644 --- a/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/test/Misc/pragma-attribute-supported-attributes-list.test @@ -38,6 +38,7 @@ // CHECK-NEXT: CarriesDependency (SubjectMatchRule_variable_is_parameter, SubjectMatchRule_objc_method, SubjectMatchRule_function) // CHECK-NEXT: Cold (SubjectMatchRule_function) // CHECK-NEXT: Common (SubjectMatchRule_variable) +// CHECK-NEXT: ConstInit (SubjectMatchRule_variable_is_global) // CHECK-NEXT: Constructor (SubjectMatchRule_function) // CHECK-NEXT: Consumable (SubjectMatchRule_record) // CHECK-NEXT: ConsumableAutoCast (SubjectMatchRule_record) @@ -124,7 +125,6 @@ // CHECK-NEXT: Pointer (SubjectMatchRule_record_not_is_union) // CHECK-NEXT: RenderScriptKernel (SubjectMatchRule_function) // CHECK-NEXT: ReqdWorkGroupSize (SubjectMatchRule_function) -// CHECK-NEXT: RequireConstantInit (SubjectMatchRule_variable_is_global) // CHECK-NEXT: Restrict (SubjectMatchRule_function) // CHECK-NEXT: ReturnTypestate (SubjectMatchRule_function, SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: ReturnsNonNull (SubjectMatchRule_objc_method, SubjectMatchRule_function) diff --git a/test/Parser/cxx0x-decl.cpp b/test/Parser/cxx0x-decl.cpp index 7bd82e8220..2f219ac87f 100644 --- a/test/Parser/cxx0x-decl.cpp +++ b/test/Parser/cxx0x-decl.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -verify -fsyntax-only -std=c++11 -pedantic-errors -triple x86_64-linux-gnu %s +// RUN: %clang_cc1 -verify -fsyntax-only -std=c++2a -pedantic-errors -triple x86_64-linux-gnu %s // Make sure we know these are legitimate commas and not typos for ';'. namespace Commas { @@ -108,14 +108,25 @@ namespace UsingDeclAttrs { } namespace DuplicateSpecifier { - constexpr constexpr int f(); // expected-warning {{duplicate 'constexpr' declaration specifier}} - constexpr int constexpr a = 0; // expected-warning {{duplicate 'constexpr' declaration specifier}} + constexpr constexpr int f(); // expected-error {{duplicate 'constexpr' declaration specifier}} + constexpr int constexpr a = 0; // expected-error {{duplicate 'constexpr' declaration specifier}} struct A { friend constexpr int constexpr friend f(); // expected-warning {{duplicate 'friend' declaration specifier}} \ - // expected-warning {{duplicate 'constexpr' declaration specifier}} + // expected-error {{duplicate 'constexpr' declaration specifier}} friend struct A friend; // expected-warning {{duplicate 'friend'}} expected-error {{'friend' must appear first}} }; + + constinit constexpr int n1 = 0; // expected-error {{cannot combine with previous 'constinit'}} + constexpr constinit int n2 = 0; // expected-error {{cannot combine with previous 'constexpr'}} + constinit constinit int n3 = 0; // expected-error {{duplicate 'constinit' declaration specifier}} + + consteval constexpr int f1(); // expected-error {{cannot combine with previous 'consteval'}} + constexpr consteval int f2(); // expected-error {{cannot combine with previous 'constexpr'}} + consteval consteval int f3(); // expected-error {{duplicate 'consteval' declaration specifier}} + + constinit consteval int wat = 0; // expected-error {{cannot combine with previous 'constinit'}} + consteval constinit int huh(); // expected-error {{cannot combine with previous 'consteval'}} } namespace ColonColonDecltype { diff --git a/test/SemaCXX/attr-require-constant-initialization.cpp b/test/SemaCXX/attr-require-constant-initialization.cpp index 2dd72ea6db..12bae81fd2 100644 --- a/test/SemaCXX/attr-require-constant-initialization.cpp +++ b/test/SemaCXX/attr-require-constant-initialization.cpp @@ -300,6 +300,17 @@ ATTR TestCtor t(42); // expected-error {{variable does not have a constant ATTR const char *foo[] = {"abc", "def"}; ATTR PODType bar[] = {{}, {123, 456}}; + +namespace AttrAddedTooLate { + struct A { + static const int n = 0; // expected-note {{here}} + }; + ATTR const int A::n; // expected-warning {{added after initialization}} + + int m = 0; // expected-note {{here}} + extern ATTR int m; // expected-warning {{added after initialization}} +} + #elif defined(TEST_TWO) // Test for duplicate warnings struct NotC { constexpr NotC(void *) {} diff --git a/www/cxx_status.html b/www/cxx_status.html index a3e567824d..151192b2ff 100755 --- a/www/cxx_status.html +++ b/www/cxx_status.html @@ -1135,7 +1135,7 @@ as the draft C++2a standard evolves. constinit P1143R2 - No + SVN -- cgit v1.2.1