172 changes: 144 additions & 28 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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<ConstInitAttr>();
const auto *NewConstInit = New->getAttr<ConstInitAttr>();
if (bool(OldConstInit) != bool(NewConstInit)) {
const auto *OldVD = cast<VarDecl>(Old);
auto *NewVD = cast<VarDecl>(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<ConstInitAttr>();
}
}
}

// Attributes declared post-definition are currently ignored.
checkNewAttributesAfterDef(*this, New, Old);

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -8651,17 +8765,19 @@ 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();

// 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<CXXDestructorDecl>(NewFD))
if (isa<CXXDestructorDecl>(NewFD)) {
Diag(D.getDeclSpec().getConstexprSpecLoc(), diag::err_constexpr_dtor)
<< (ConstexprKind == CSK_consteval);
<< ConstexprKind;
}
}

// If __module_private__ was specified, mark the function accordingly.
Expand Down Expand Up @@ -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<RequireConstantInitAttr>()) {
if (!var->isConstexpr() && GlobalStorage && var->hasAttr<ConstInitAttr>()) {
// FIXME: Need strict checking in C++03 here.
bool DiagErr = getLangOpts().CPlusPlus11
? !var->checkInitIsICE() : !checkConstInit();
if (DiagErr) {
auto attr = var->getAttr<RequireConstantInitAttr>();
auto *Attr = var->getAttr<ConstInitAttr>();
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<PartialDiagnosticAt, 8> Notes;
Expand Down Expand Up @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<RequireConstantInitAttr>(S, D, AL);
case ParsedAttr::AT_ConstInit:
handleSimpleAttribute<ConstInitAttr>(S, D, AL);
break;
case ParsedAttr::AT_InitPriority:
handleInitPriorityAttr(S, D, AL);
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/Sema/SemaType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
55 changes: 55 additions & 0 deletions clang/test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p1.cpp
Original file line number Diff line number Diff line change
@@ -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}}
8 changes: 8 additions & 0 deletions clang/test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p2.cpp
Original file line number Diff line number Diff line change
@@ -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'}}
6 changes: 6 additions & 0 deletions clang/test/CXX/dcl.dcl/dcl.spec/dcl.constinit/p3.cpp
Original file line number Diff line number Diff line change
@@ -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{{}}
38 changes: 36 additions & 2 deletions clang/test/FixIt/fixit-c++2a.cpp
Original file line number Diff line number Diff line change
@@ -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. */
Expand All @@ -13,3 +14,36 @@ template<typename ...T> 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<typename> struct X {
template<int> static constinit int n; // expected-note {{constinit}}
};
template<typename T> template<int N>
int X<T>::n = 123; // expected-error {{missing}}
// CHECK: {{^}} constinit int X<T>::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
}
4 changes: 4 additions & 0 deletions clang/test/Lexer/cxx-features.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions clang/test/Lexer/cxx2a_keyword_as_cxx17.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
4 changes: 2 additions & 2 deletions clang/test/Misc/pragma-attribute-cxx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 15 additions & 4 deletions clang/test/Parser/cxx0x-decl.cpp
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
11 changes: 11 additions & 0 deletions clang/test/SemaCXX/attr-require-constant-initialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,17 @@ ATTR TestCtor<NotC> 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 *) {}
Expand Down
2 changes: 1 addition & 1 deletion clang/www/cxx_status.html
Original file line number Diff line number Diff line change
Expand Up @@ -1135,7 +1135,7 @@ <h2 id="cxx20">C++2a implementation status</h2>
<tr>
<td><tt>constinit</tt></td>
<td><a href="http://wg21.link/p1143r2">P1143R2</a></td>
<td class="none" align="center">No</td>
<td class="svn" align="center">SVN</td>
</tr>
</table>

Expand Down