Skip to content

Commit aced81c

Browse files
authored
[C23] Implement N3018: The constexpr specifier for object definitions (#73099)
The implementation mostly reuses C++ code paths where possible, including narrowing check in order to provide diagnostic messages in case initializer for constexpr variable is not exactly representable in target type. The following won't work due to lack of support for other features: - Diagnosing of underspecified declarations involving constexpr - Constexpr attached to compound literals Also due to lack of support for char8_t some of examples with utf-8 strings don't work properly. Fixes #64742
1 parent 6cdf596 commit aced81c

File tree

15 files changed

+766
-39
lines changed

15 files changed

+766
-39
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ C23 Feature Support
143143
macros typically exposed from ``<inttypes.h>``, such as ``PRIb8``.
144144
(`#81896: <https://github.com/llvm/llvm-project/issues/81896>`_).
145145

146+
- Clang now supports `N3018 The constexpr specifier for object definitions`
147+
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3018.htm>`_.
148+
146149
Non-comprehensive list of changes in this release
147150
-------------------------------------------------
148151

clang/include/clang/AST/Expr.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1875,6 +1875,17 @@ class StringLiteral final
18751875
llvm_unreachable("Unsupported character width!");
18761876
}
18771877

1878+
// Get code unit but preserve sign info.
1879+
int64_t getCodeUnitS(size_t I, uint64_t BitWidth) const {
1880+
int64_t V = getCodeUnit(I);
1881+
if (isOrdinary() || isWide()) {
1882+
unsigned Width = getCharByteWidth() * BitWidth;
1883+
llvm::APInt AInt(Width, (uint64_t)V);
1884+
V = AInt.getSExtValue();
1885+
}
1886+
return V;
1887+
}
1888+
18781889
unsigned getByteLength() const { return getCharByteWidth() * getLength(); }
18791890
unsigned getLength() const { return *getTrailingObjects<unsigned>(); }
18801891
unsigned getCharByteWidth() const { return StringLiteralBits.CharByteWidth; }

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2946,6 +2946,18 @@ def warn_private_extern : Warning<
29462946
def note_private_extern : Note<
29472947
"use __attribute__((visibility(\"hidden\"))) attribute instead">;
29482948

2949+
// C23 constexpr
2950+
def err_c23_constexpr_not_variable : Error<
2951+
"'constexpr' can only be used in variable declarations">;
2952+
def err_c23_constexpr_invalid_type : Error<
2953+
"constexpr variable cannot have type %0">;
2954+
def err_c23_constexpr_init_not_representable : Error<
2955+
"constexpr initializer evaluates to %0 which is not exactly representable in type %1">;
2956+
def err_c23_constexpr_init_type_mismatch : Error<
2957+
"constexpr initializer for type %0 is of type %1">;
2958+
def err_c23_constexpr_pointer_not_null : Error<
2959+
"constexpr pointer initializer is not null">;
2960+
29492961
// C++ Concepts
29502962
def err_concept_decls_may_only_appear_in_global_namespace_scope : Error<
29512963
"concept declarations may only appear in global or namespace scope">;

clang/include/clang/Basic/TokenKinds.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ CXX11_KEYWORD(alignas , KEYC23)
393393
CXX11_UNARY_EXPR_OR_TYPE_TRAIT(alignof, AlignOf, KEYC23)
394394
CXX11_KEYWORD(char16_t , KEYNOMS18)
395395
CXX11_KEYWORD(char32_t , KEYNOMS18)
396-
CXX11_KEYWORD(constexpr , 0)
396+
CXX11_KEYWORD(constexpr , KEYC23)
397397
CXX11_KEYWORD(decltype , 0)
398398
CXX11_KEYWORD(noexcept , 0)
399399
CXX11_KEYWORD(nullptr , KEYC23)

clang/lib/AST/Decl.cpp

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2465,7 +2465,7 @@ bool VarDecl::mightBeUsableInConstantExpressions(const ASTContext &C) const {
24652465

24662466
// OpenCL permits const integral variables to be used in constant
24672467
// expressions, like in C++98.
2468-
if (!Lang.CPlusPlus && !Lang.OpenCL)
2468+
if (!Lang.CPlusPlus && !Lang.OpenCL && !Lang.C23)
24692469
return false;
24702470

24712471
// Function parameters are never usable in constant expressions.
@@ -2487,14 +2487,19 @@ bool VarDecl::mightBeUsableInConstantExpressions(const ASTContext &C) const {
24872487
if (!getType().isConstant(C) || getType().isVolatileQualified())
24882488
return false;
24892489

2490-
// In C++, const, non-volatile variables of integral or enumeration types
2491-
// can be used in constant expressions.
2492-
if (getType()->isIntegralOrEnumerationType())
2490+
// In C++, but not in C, const, non-volatile variables of integral or
2491+
// enumeration types can be used in constant expressions.
2492+
if (getType()->isIntegralOrEnumerationType() && !Lang.C23)
24932493
return true;
24942494

2495+
// C23 6.6p7: An identifier that is:
2496+
// ...
2497+
// - declared with storage-class specifier constexpr and has an object type,
2498+
// is a named constant, ... such a named constant is a constant expression
2499+
// with the type and value of the declared object.
24952500
// Additionally, in C++11, non-volatile constexpr variables can be used in
24962501
// constant expressions.
2497-
return Lang.CPlusPlus11 && isConstexpr();
2502+
return (Lang.CPlusPlus11 || Lang.C23) && isConstexpr();
24982503
}
24992504

25002505
bool VarDecl::isUsableInConstantExpressions(const ASTContext &Context) const {
@@ -2572,11 +2577,11 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes,
25722577
bool Result = Init->EvaluateAsInitializer(Eval->Evaluated, Ctx, this, Notes,
25732578
IsConstantInitialization);
25742579

2575-
// In C++, this isn't a constant initializer if we produced notes. In that
2580+
// In C++/C23, this isn't a constant initializer if we produced notes. In that
25762581
// case, we can't keep the result, because it may only be correct under the
25772582
// assumption that the initializer is a constant context.
2578-
if (IsConstantInitialization && Ctx.getLangOpts().CPlusPlus &&
2579-
!Notes.empty())
2583+
if (IsConstantInitialization &&
2584+
(Ctx.getLangOpts().CPlusPlus || Ctx.getLangOpts().C23) && !Notes.empty())
25802585
Result = false;
25812586

25822587
// Ensure the computed APValue is cleaned up later if evaluation succeeded,
@@ -2634,7 +2639,9 @@ bool VarDecl::checkForConstantInitialization(
26342639
// std::is_constant_evaluated()).
26352640
assert(!Eval->WasEvaluated &&
26362641
"already evaluated var value before checking for constant init");
2637-
assert(getASTContext().getLangOpts().CPlusPlus && "only meaningful in C++");
2642+
assert((getASTContext().getLangOpts().CPlusPlus ||
2643+
getASTContext().getLangOpts().C23) &&
2644+
"only meaningful in C++/C23");
26382645

26392646
assert(!getInit()->isValueDependent());
26402647

clang/lib/AST/ExprConstant.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4133,6 +4133,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
41334133
}
41344134

41354135
bool IsConstant = BaseType.isConstant(Info.Ctx);
4136+
bool ConstexprVar = false;
4137+
if (const auto *VD = dyn_cast_if_present<VarDecl>(
4138+
Info.EvaluatingDecl.dyn_cast<const ValueDecl *>()))
4139+
ConstexprVar = VD->isConstexpr();
41364140

41374141
// Unless we're looking at a local variable or argument in a constexpr call,
41384142
// the variable we're reading must be const.
@@ -4152,6 +4156,9 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
41524156
return CompleteObject();
41534157
} else if (VD->isConstexpr()) {
41544158
// OK, we can read this variable.
4159+
} else if (Info.getLangOpts().C23 && ConstexprVar) {
4160+
Info.FFDiag(E);
4161+
return CompleteObject();
41554162
} else if (BaseType->isIntegralOrEnumerationType()) {
41564163
if (!IsConstant) {
41574164
if (!IsAccess)
@@ -15826,7 +15833,8 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
1582615833
EStatus.Diag = &Notes;
1582715834

1582815835
EvalInfo Info(Ctx, EStatus,
15829-
(IsConstantInitialization && Ctx.getLangOpts().CPlusPlus)
15836+
(IsConstantInitialization &&
15837+
(Ctx.getLangOpts().CPlusPlus || Ctx.getLangOpts().C23))
1583015838
? EvalInfo::EM_ConstantExpression
1583115839
: EvalInfo::EM_ConstantFold);
1583215840
Info.setEvaluatingDecl(VD, Value);

clang/lib/Parse/ParseDecl.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4265,6 +4265,8 @@ void Parser::ParseDeclarationSpecifiers(
42654265

42664266
// constexpr, consteval, constinit specifiers
42674267
case tok::kw_constexpr:
4268+
if (getLangOpts().C23)
4269+
Diag(Tok, diag::warn_c23_compat_keyword) << Tok.getName();
42684270
isInvalid = DS.SetConstexprSpec(ConstexprSpecKind::Constexpr, Loc,
42694271
PrevSpec, DiagID);
42704272
break;

clang/lib/Sema/DeclSpec.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,6 +1377,20 @@ void DeclSpec::Finish(Sema &S, const PrintingPolicy &Policy) {
13771377
ThreadStorageClassSpec = TSCS_unspecified;
13781378
ThreadStorageClassSpecLoc = SourceLocation();
13791379
}
1380+
if (S.getLangOpts().C23 &&
1381+
getConstexprSpecifier() == ConstexprSpecKind::Constexpr) {
1382+
S.Diag(ConstexprLoc, diag::err_invalid_decl_spec_combination)
1383+
<< DeclSpec::getSpecifierName(getThreadStorageClassSpec())
1384+
<< SourceRange(getThreadStorageClassSpecLoc());
1385+
}
1386+
}
1387+
1388+
if (S.getLangOpts().C23 &&
1389+
getConstexprSpecifier() == ConstexprSpecKind::Constexpr &&
1390+
StorageClassSpec == SCS_extern) {
1391+
S.Diag(ConstexprLoc, diag::err_invalid_decl_spec_combination)
1392+
<< DeclSpec::getSpecifierName(getStorageClassSpec())
1393+
<< SourceRange(getStorageClassSpecLoc());
13801394
}
13811395

13821396
// If no type specifier was provided and we're parsing a language where

clang/lib/Sema/SemaDecl.cpp

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5147,6 +5147,8 @@ Decl *Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS,
51475147
Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_tag)
51485148
<< GetDiagnosticTypeSpecifierID(DS)
51495149
<< static_cast<int>(DS.getConstexprSpecifier());
5150+
else if (getLangOpts().C23)
5151+
Diag(DS.getConstexprSpecLoc(), diag::err_c23_constexpr_not_variable);
51505152
else
51515153
Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_wrong_decl_kind)
51525154
<< static_cast<int>(DS.getConstexprSpecifier());
@@ -8649,6 +8651,38 @@ static bool checkForConflictWithNonVisibleExternC(Sema &S, const T *ND,
86498651
return false;
86508652
}
86518653

8654+
static bool CheckC23ConstexprVarType(Sema &SemaRef, SourceLocation VarLoc,
8655+
QualType T) {
8656+
QualType CanonT = SemaRef.Context.getCanonicalType(T);
8657+
// C23 6.7.1p5: An object declared with storage-class specifier constexpr or
8658+
// any of its members, even recursively, shall not have an atomic type, or a
8659+
// variably modified type, or a type that is volatile or restrict qualified.
8660+
if (CanonT->isVariablyModifiedType()) {
8661+
SemaRef.Diag(VarLoc, diag::err_c23_constexpr_invalid_type) << T;
8662+
return true;
8663+
}
8664+
8665+
// Arrays are qualified by their element type, so get the base type (this
8666+
// works on non-arrays as well).
8667+
CanonT = SemaRef.Context.getBaseElementType(CanonT);
8668+
8669+
if (CanonT->isAtomicType() || CanonT.isVolatileQualified() ||
8670+
CanonT.isRestrictQualified()) {
8671+
SemaRef.Diag(VarLoc, diag::err_c23_constexpr_invalid_type) << T;
8672+
return true;
8673+
}
8674+
8675+
if (CanonT->isRecordType()) {
8676+
const RecordDecl *RD = CanonT->getAsRecordDecl();
8677+
if (llvm::any_of(RD->fields(), [&SemaRef, VarLoc](const FieldDecl *F) {
8678+
return CheckC23ConstexprVarType(SemaRef, VarLoc, F->getType());
8679+
}))
8680+
return true;
8681+
}
8682+
8683+
return false;
8684+
}
8685+
86528686
void Sema::CheckVariableDeclarationType(VarDecl *NewVD) {
86538687
// If the decl is already known invalid, don't check it.
86548688
if (NewVD->isInvalidDecl())
@@ -8899,6 +8933,12 @@ void Sema::CheckVariableDeclarationType(VarDecl *NewVD) {
88998933
return;
89008934
}
89018935

8936+
if (getLangOpts().C23 && NewVD->isConstexpr() &&
8937+
CheckC23ConstexprVarType(*this, NewVD->getLocation(), T)) {
8938+
NewVD->setInvalidDecl();
8939+
return;
8940+
}
8941+
89028942
if (NewVD->isConstexpr() && !T->isDependentType() &&
89038943
RequireLiteralType(NewVD->getLocation(), T,
89048944
diag::err_constexpr_var_non_literal)) {
@@ -9281,6 +9321,22 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D,
92819321
FunctionDecl *NewFD = nullptr;
92829322
bool isInline = D.getDeclSpec().isInlineSpecified();
92839323

9324+
ConstexprSpecKind ConstexprKind = D.getDeclSpec().getConstexprSpecifier();
9325+
if (ConstexprKind == ConstexprSpecKind::Constinit ||
9326+
(SemaRef.getLangOpts().C23 &&
9327+
ConstexprKind == ConstexprSpecKind::Constexpr)) {
9328+
9329+
if (SemaRef.getLangOpts().C23)
9330+
SemaRef.Diag(D.getDeclSpec().getConstexprSpecLoc(),
9331+
diag::err_c23_constexpr_not_variable);
9332+
else
9333+
SemaRef.Diag(D.getDeclSpec().getConstexprSpecLoc(),
9334+
diag::err_constexpr_wrong_decl_kind)
9335+
<< static_cast<int>(ConstexprKind);
9336+
ConstexprKind = ConstexprSpecKind::Unspecified;
9337+
D.getMutableDeclSpec().ClearConstexprSpec();
9338+
}
9339+
92849340
if (!SemaRef.getLangOpts().CPlusPlus) {
92859341
// Determine whether the function was written with a prototype. This is
92869342
// true when:
@@ -9314,15 +9370,6 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D,
93149370
}
93159371

93169372
ExplicitSpecifier ExplicitSpecifier = D.getDeclSpec().getExplicitSpecifier();
9317-
9318-
ConstexprSpecKind ConstexprKind = D.getDeclSpec().getConstexprSpecifier();
9319-
if (ConstexprKind == ConstexprSpecKind::Constinit) {
9320-
SemaRef.Diag(D.getDeclSpec().getConstexprSpecLoc(),
9321-
diag::err_constexpr_wrong_decl_kind)
9322-
<< static_cast<int>(ConstexprKind);
9323-
ConstexprKind = ConstexprSpecKind::Unspecified;
9324-
D.getMutableDeclSpec().ClearConstexprSpec();
9325-
}
93269373
Expr *TrailingRequiresClause = D.getTrailingRequiresClause();
93279374

93289375
SemaRef.CheckExplicitObjectMemberFunction(DC, D, Name, R);
@@ -13909,7 +13956,9 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, bool DirectInit) {
1390913956
VDecl->setStorageClass(SC_Extern);
1391013957

1391113958
// C99 6.7.8p4. All file scoped initializers need to be constant.
13912-
if (!getLangOpts().CPlusPlus && !VDecl->isInvalidDecl())
13959+
// Avoid duplicate diagnostics for constexpr variables.
13960+
if (!getLangOpts().CPlusPlus && !VDecl->isInvalidDecl() &&
13961+
!VDecl->isConstexpr())
1391313962
CheckForConstantInitializer(Init, DclT);
1391413963
}
1391513964

@@ -14520,17 +14569,21 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
1452014569
QualType baseType = Context.getBaseElementType(type);
1452114570
bool HasConstInit = true;
1452214571

14572+
if (getLangOpts().C23 && var->isConstexpr() && !Init)
14573+
Diag(var->getLocation(), diag::err_constexpr_var_requires_const_init)
14574+
<< var;
14575+
1452314576
// Check whether the initializer is sufficiently constant.
14524-
if (getLangOpts().CPlusPlus && !type->isDependentType() && Init &&
14525-
!Init->isValueDependent() &&
14577+
if ((getLangOpts().CPlusPlus || (getLangOpts().C23 && var->isConstexpr())) &&
14578+
!type->isDependentType() && Init && !Init->isValueDependent() &&
1452614579
(GlobalStorage || var->isConstexpr() ||
1452714580
var->mightBeUsableInConstantExpressions(Context))) {
1452814581
// If this variable might have a constant initializer or might be usable in
1452914582
// constant expressions, check whether or not it actually is now. We can't
1453014583
// do this lazily, because the result might depend on things that change
1453114584
// later, such as which constexpr functions happen to be defined.
1453214585
SmallVector<PartialDiagnosticAt, 8> Notes;
14533-
if (!getLangOpts().CPlusPlus11) {
14586+
if (!getLangOpts().CPlusPlus11 && !getLangOpts().C23) {
1453414587
// Prior to C++11, in contexts where a constant initializer is required,
1453514588
// the set of valid constant initializers is described by syntactic rules
1453614589
// in [expr.const]p2-6.

0 commit comments

Comments
 (0)