Skip to content
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

[C23] Implement N3018: The constexpr specifier for object definitions #73099

Merged
merged 27 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1d70b77
[C23] Implement N3018: The constexpr specifier for object definitions
Fznamznon Sep 19, 2023
bbb801d
Fix format, apply comments
Fznamznon Nov 23, 2023
c520581
Merge branch 'main' into N3018-1
Fznamznon Nov 29, 2023
9b4a5ea
Apply some of the feedback:
Fznamznon Nov 29, 2023
d14018a
Update a release note
Fznamznon Nov 29, 2023
85ad720
Remove unused messages
Fznamznon Nov 29, 2023
5d8c3b1
Merge branch 'main' into N3018-1
Fznamznon Nov 29, 2023
1951550
Merge branch 'main' into N3018-1
Fznamznon Nov 30, 2023
83d9677
Update comments
Fznamznon Nov 30, 2023
1e8fae1
Merge branch 'main' into N3018-1
Fznamznon Nov 30, 2023
0d24ada
Use getCodeUnit
Fznamznon Dec 4, 2023
36e57af
Merge branch 'main' into N3018-1
Fznamznon Dec 6, 2023
6b740ae
Apply suggestions
Fznamznon Dec 6, 2023
9770050
Merge branch 'main' into N3018-1
Fznamznon Dec 12, 2023
40f7d86
Add test improvement
Fznamznon Dec 12, 2023
975f2aa
Merge branch 'main' into N3018-1
Fznamznon Feb 2, 2024
bc0745c
Target clang 19
Fznamznon Feb 2, 2024
5ca0a18
Merge branch 'main' into N3018-1
Fznamznon Feb 19, 2024
c97f203
Merge branch 'main' into N3018-1
Fznamznon Feb 20, 2024
cdec1b7
Evaluate initializers in C++ fashion only for C23 constexpr vars
Fznamznon Feb 20, 2024
10ef7df
Apply review feedback
Fznamznon Feb 26, 2024
764f0dc
Merge branch 'main' into N3018-1
Fznamznon Feb 26, 2024
62ab5ef
Minors to the tests
Fznamznon Feb 26, 2024
76b4b54
Merge branch 'main' into N3018-1
Fznamznon Mar 1, 2024
1ea05d5
Merge branch 'main' into N3018-1
Fznamznon Mar 5, 2024
dff61e0
Implement NaN cases
Fznamznon Mar 5, 2024
d4b0beb
Apply a minor
Fznamznon Mar 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ C23 Feature Support
macros typically exposed from ``<inttypes.h>``, such as ``PRIb8``.
(`#81896: <https://github.com/llvm/llvm-project/issues/81896>`_).

- Clang now supports `N3018 The constexpr specifier for object definitions`
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3018.htm>`_.

Non-comprehensive list of changes in this release
-------------------------------------------------

Expand Down
11 changes: 11 additions & 0 deletions clang/include/clang/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -1875,6 +1875,17 @@ class StringLiteral final
llvm_unreachable("Unsupported character width!");
}

// Get code unit but preserve sign info.
int64_t getCodeUnitS(size_t I, uint64_t BitWidth) const {
AaronBallman marked this conversation as resolved.
Show resolved Hide resolved
int64_t V = getCodeUnit(I);
if (isOrdinary() || isWide()) {
unsigned Width = getCharByteWidth() * BitWidth;
llvm::APInt AInt(Width, (uint64_t)V);
V = AInt.getSExtValue();
}
return V;
}

unsigned getByteLength() const { return getCharByteWidth() * getLength(); }
unsigned getLength() const { return *getTrailingObjects<unsigned>(); }
unsigned getCharByteWidth() const { return StringLiteralBits.CharByteWidth; }
Expand Down
12 changes: 12 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -2946,6 +2946,18 @@ def warn_private_extern : Warning<
def note_private_extern : Note<
"use __attribute__((visibility(\"hidden\"))) attribute instead">;

// C23 constexpr
def err_c23_constexpr_not_variable : Error<
"'constexpr' can only be used in variable declarations">;
def err_c23_constexpr_invalid_type : Error<
"constexpr variable cannot have type %0">;
def err_c23_constexpr_init_not_representable : Error<
"constexpr initializer evaluates to %0 which is not exactly representable in type %1">;
def err_c23_constexpr_init_type_mismatch : Error<
"constexpr initializer for type %0 is of type %1">;
def err_c23_constexpr_pointer_not_null : Error<
"constexpr pointer initializer is not null">;

// C++ Concepts
def err_concept_decls_may_only_appear_in_global_namespace_scope : Error<
"concept declarations may only appear in global or namespace scope">;
Expand Down
2 changes: 1 addition & 1 deletion clang/include/clang/Basic/TokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ CXX11_KEYWORD(alignas , KEYC23)
CXX11_UNARY_EXPR_OR_TYPE_TRAIT(alignof, AlignOf, KEYC23)
CXX11_KEYWORD(char16_t , KEYNOMS18)
CXX11_KEYWORD(char32_t , KEYNOMS18)
CXX11_KEYWORD(constexpr , 0)
CXX11_KEYWORD(constexpr , KEYC23)
CXX11_KEYWORD(decltype , 0)
CXX11_KEYWORD(noexcept , 0)
CXX11_KEYWORD(nullptr , KEYC23)
Expand Down
25 changes: 16 additions & 9 deletions clang/lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2465,7 +2465,7 @@ bool VarDecl::mightBeUsableInConstantExpressions(const ASTContext &C) const {

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

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

// In C++, const, non-volatile variables of integral or enumeration types
// can be used in constant expressions.
if (getType()->isIntegralOrEnumerationType())
// In C++, but not in C, const, non-volatile variables of integral or
// enumeration types can be used in constant expressions.
if (getType()->isIntegralOrEnumerationType() && !Lang.C23)
AaronBallman marked this conversation as resolved.
Show resolved Hide resolved
return true;

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

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

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

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

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

Expand Down
10 changes: 9 additions & 1 deletion clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4131,6 +4131,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
}

bool IsConstant = BaseType.isConstant(Info.Ctx);
bool ConstexprVar = false;
if (const auto *VD = dyn_cast_if_present<VarDecl>(
Info.EvaluatingDecl.dyn_cast<const ValueDecl *>()))
ConstexprVar = VD->isConstexpr();
AaronBallman marked this conversation as resolved.
Show resolved Hide resolved

// Unless we're looking at a local variable or argument in a constexpr call,
// the variable we're reading must be const.
Expand All @@ -4150,6 +4154,9 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
return CompleteObject();
} else if (VD->isConstexpr()) {
// OK, we can read this variable.
} else if (Info.getLangOpts().C23 && ConstexprVar) {
Info.FFDiag(E);
return CompleteObject();
} else if (BaseType->isIntegralOrEnumerationType()) {
if (!IsConstant) {
if (!IsAccess)
Expand Down Expand Up @@ -15822,7 +15829,8 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
EStatus.Diag = &Notes;

EvalInfo Info(Ctx, EStatus,
(IsConstantInitialization && Ctx.getLangOpts().CPlusPlus)
(IsConstantInitialization &&
(Ctx.getLangOpts().CPlusPlus || Ctx.getLangOpts().C23))
? EvalInfo::EM_ConstantExpression
: EvalInfo::EM_ConstantFold);
Info.setEvaluatingDecl(VD, Value);
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4265,6 +4265,8 @@ void Parser::ParseDeclarationSpecifiers(

// constexpr, consteval, constinit specifiers
case tok::kw_constexpr:
if (getLangOpts().C23)
Diag(Tok, diag::warn_c23_compat_keyword) << Tok.getName();
isInvalid = DS.SetConstexprSpec(ConstexprSpecKind::Constexpr, Loc,
PrevSpec, DiagID);
break;
Expand Down
14 changes: 14 additions & 0 deletions clang/lib/Sema/DeclSpec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,20 @@ void DeclSpec::Finish(Sema &S, const PrintingPolicy &Policy) {
ThreadStorageClassSpec = TSCS_unspecified;
ThreadStorageClassSpecLoc = SourceLocation();
}
if (S.getLangOpts().C23 &&
getConstexprSpecifier() == ConstexprSpecKind::Constexpr) {
S.Diag(ConstexprLoc, diag::err_invalid_decl_spec_combination)
<< DeclSpec::getSpecifierName(getThreadStorageClassSpec())
<< SourceRange(getThreadStorageClassSpecLoc());
}
}

if (S.getLangOpts().C23 &&
AaronBallman marked this conversation as resolved.
Show resolved Hide resolved
getConstexprSpecifier() == ConstexprSpecKind::Constexpr &&
StorageClassSpec == SCS_extern) {
S.Diag(ConstexprLoc, diag::err_invalid_decl_spec_combination)
<< DeclSpec::getSpecifierName(getStorageClassSpec())
<< SourceRange(getStorageClassSpecLoc());
}

// If no type specifier was provided and we're parsing a language where
Expand Down
79 changes: 66 additions & 13 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5144,6 +5144,8 @@ Decl *Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS,
Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_tag)
<< GetDiagnosticTypeSpecifierID(DS)
<< static_cast<int>(DS.getConstexprSpecifier());
else if (getLangOpts().C23)
Diag(DS.getConstexprSpecLoc(), diag::err_c23_constexpr_not_variable);
AaronBallman marked this conversation as resolved.
Show resolved Hide resolved
else
Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_wrong_decl_kind)
<< static_cast<int>(DS.getConstexprSpecifier());
Expand Down Expand Up @@ -8646,6 +8648,38 @@ static bool checkForConflictWithNonVisibleExternC(Sema &S, const T *ND,
return false;
}

static bool CheckC23ConstexprVarType(Sema &SemaRef, SourceLocation VarLoc,
QualType T) {
QualType CanonT = SemaRef.Context.getCanonicalType(T);
// C23 6.7.1p5: An object declared with storage-class specifier constexpr or
// any of its members, even recursively, shall not have an atomic type, or a
// variably modified type, or a type that is volatile or restrict qualified.
if (CanonT->isVariablyModifiedType()) {
SemaRef.Diag(VarLoc, diag::err_c23_constexpr_invalid_type) << T;
return true;
}

// Arrays are qualified by their element type, so get the base type (this
// works on non-arrays as well).
CanonT = SemaRef.Context.getBaseElementType(CanonT);

if (CanonT->isAtomicType() || CanonT.isVolatileQualified() ||
CanonT.isRestrictQualified()) {
SemaRef.Diag(VarLoc, diag::err_c23_constexpr_invalid_type) << T;
return true;
}

if (CanonT->isRecordType()) {
const RecordDecl *RD = CanonT->getAsRecordDecl();
if (llvm::any_of(RD->fields(), [&SemaRef, VarLoc](const FieldDecl *F) {
return CheckC23ConstexprVarType(SemaRef, VarLoc, F->getType());
}))
return true;
}

return false;
}

void Sema::CheckVariableDeclarationType(VarDecl *NewVD) {
// If the decl is already known invalid, don't check it.
if (NewVD->isInvalidDecl())
Expand Down Expand Up @@ -8896,6 +8930,12 @@ void Sema::CheckVariableDeclarationType(VarDecl *NewVD) {
return;
}

if (getLangOpts().C23 && NewVD->isConstexpr() &&
CheckC23ConstexprVarType(*this, NewVD->getLocation(), T)) {
NewVD->setInvalidDecl();
return;
}

if (NewVD->isConstexpr() && !T->isDependentType() &&
RequireLiteralType(NewVD->getLocation(), T,
diag::err_constexpr_var_non_literal)) {
Expand Down Expand Up @@ -9278,6 +9318,22 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D,
FunctionDecl *NewFD = nullptr;
bool isInline = D.getDeclSpec().isInlineSpecified();

ConstexprSpecKind ConstexprKind = D.getDeclSpec().getConstexprSpecifier();
if (ConstexprKind == ConstexprSpecKind::Constinit ||
(SemaRef.getLangOpts().C23 &&
ConstexprKind == ConstexprSpecKind::Constexpr)) {

if (SemaRef.getLangOpts().C23)
SemaRef.Diag(D.getDeclSpec().getConstexprSpecLoc(),
diag::err_c23_constexpr_not_variable);
AaronBallman marked this conversation as resolved.
Show resolved Hide resolved
else
SemaRef.Diag(D.getDeclSpec().getConstexprSpecLoc(),
diag::err_constexpr_wrong_decl_kind)
<< static_cast<int>(ConstexprKind);
ConstexprKind = ConstexprSpecKind::Unspecified;
D.getMutableDeclSpec().ClearConstexprSpec();
}

if (!SemaRef.getLangOpts().CPlusPlus) {
// Determine whether the function was written with a prototype. This is
// true when:
Expand Down Expand Up @@ -9311,15 +9367,6 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D,
}

ExplicitSpecifier ExplicitSpecifier = D.getDeclSpec().getExplicitSpecifier();

ConstexprSpecKind ConstexprKind = D.getDeclSpec().getConstexprSpecifier();
if (ConstexprKind == ConstexprSpecKind::Constinit) {
SemaRef.Diag(D.getDeclSpec().getConstexprSpecLoc(),
diag::err_constexpr_wrong_decl_kind)
<< static_cast<int>(ConstexprKind);
ConstexprKind = ConstexprSpecKind::Unspecified;
D.getMutableDeclSpec().ClearConstexprSpec();
}
Expr *TrailingRequiresClause = D.getTrailingRequiresClause();

SemaRef.CheckExplicitObjectMemberFunction(DC, D, Name, R);
Expand Down Expand Up @@ -13906,7 +13953,9 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, bool DirectInit) {
VDecl->setStorageClass(SC_Extern);

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

Expand Down Expand Up @@ -14517,17 +14566,21 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
QualType baseType = Context.getBaseElementType(type);
bool HasConstInit = true;

if (getLangOpts().C23 && var->isConstexpr() && !Init)
Diag(var->getLocation(), diag::err_constexpr_var_requires_const_init)
<< var;
AaronBallman marked this conversation as resolved.
Show resolved Hide resolved

// Check whether the initializer is sufficiently constant.
if (getLangOpts().CPlusPlus && !type->isDependentType() && Init &&
!Init->isValueDependent() &&
if ((getLangOpts().CPlusPlus || (getLangOpts().C23 && var->isConstexpr())) &&
!type->isDependentType() && Init && !Init->isValueDependent() &&
(GlobalStorage || var->isConstexpr() ||
var->mightBeUsableInConstantExpressions(Context))) {
// If this variable might have a constant initializer or might be usable in
// constant expressions, check whether or not it actually is now. We can't
// do this lazily, because the result might depend on things that change
// later, such as which constexpr functions happen to be defined.
SmallVector<PartialDiagnosticAt, 8> Notes;
if (!getLangOpts().CPlusPlus11) {
if (!getLangOpts().CPlusPlus11 && !getLangOpts().C23) {
// Prior to C++11, in contexts where a constant initializer is required,
// the set of valid constant initializers is described by syntactic rules
// in [expr.const]p2-6.
Expand Down