Skip to content

Commit

Permalink
[Clang][C++2b] P2242R3: Non-literal variables [...] in constexpr
Browse files Browse the repository at this point in the history
Allow goto, labelled statements as well as `static`, `thread_local`, and
non-literal variables in `constexpr` functions.

As specified. for all of the above (except labelled statements) constant
evaluation of the construct still fails.

For `constexpr` bodies, the proposal is implemented with diagnostics as
a language extension in older language modes. For determination of
whether a lambda body satisfies the requirements for a constexpr
function, the proposal is implemented only in C++2b mode to retain the
semantics of older modes for programs conforming to them.

Reviewed By: aaron.ballman, hubert.reinterpretcast, erichkeane

Differential Revision: https://reviews.llvm.org/D111400
  • Loading branch information
cor3ntin committed Mar 22, 2022
1 parent bafbae2 commit 683e83c
Show file tree
Hide file tree
Showing 13 changed files with 452 additions and 60 deletions.
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Expand Up @@ -147,6 +147,7 @@ C++2b Feature Support

- Implemented `P2128R6: Multidimensional subscript operator <https://wg21.link/P2128R6>`_.
- Implemented `P0849R8: auto(x): decay-copy in the language <https://wg21.link/P0849R8>`_.
- Implemented `P2242R3: Non-literal variables (and labels and gotos) in constexpr functions <https://wg21.link/P2242R3>`_.

CUDA Language Changes in Clang
------------------------------
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticASTKinds.td
Expand Up @@ -65,6 +65,8 @@ def note_consteval_address_accessible : Note<
"is not a constant expression">;
def note_constexpr_uninitialized : Note<
"%select{|sub}0object of type %1 is not initialized">;
def note_constexpr_static_local : Note<
"control flows through the definition of a %select{static|thread_local}0 variable">;
def note_constexpr_subobject_declared_here : Note<
"subobject declared here">;
def note_constexpr_array_index : Note<"cannot refer to element %0 of "
Expand Down
21 changes: 17 additions & 4 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -2693,6 +2693,13 @@ def warn_cxx17_compat_constexpr_body_invalid_stmt : Warning<
"use of this statement in a constexpr %select{function|constructor}0 "
"is incompatible with C++ standards before C++20">,
InGroup<CXXPre20Compat>, DefaultIgnore;
def ext_constexpr_body_invalid_stmt_cxx2b : ExtWarn<
"use of this statement in a constexpr %select{function|constructor}0 "
"is a C++2b extension">, InGroup<CXX2b>;
def warn_cxx20_compat_constexpr_body_invalid_stmt : Warning<
"use of this statement in a constexpr %select{function|constructor}0 "
"is incompatible with C++ standards before C++2b">,
InGroup<CXXPre2bCompat>, DefaultIgnore;
def ext_constexpr_type_definition : ExtWarn<
"type definition in a constexpr %select{function|constructor}0 "
"is a C++14 extension">, InGroup<CXX14>;
Expand All @@ -2710,12 +2717,18 @@ def warn_cxx11_compat_constexpr_local_var : Warning<
"variable declaration in a constexpr %select{function|constructor}0 "
"is incompatible with C++ standards before C++14">,
InGroup<CXXPre14Compat>, DefaultIgnore;
def err_constexpr_local_var_static : Error<
"%select{static|thread_local}1 variable not permitted in a constexpr "
"%select{function|constructor}0">;
def ext_constexpr_static_var : ExtWarn<
"definition of a %select{static|thread_local}1 variable "
"in a constexpr %select{function|constructor}0 "
"is a C++2b extension">, InGroup<CXX2b>;
def warn_cxx20_compat_constexpr_static_var : Warning<
"definition of a %select{static|thread_local}1 variable "
"in a constexpr %select{function|constructor}0 "
"is incompatible with C++ standards before C++2b">,
InGroup<CXXPre2bCompat>, DefaultIgnore;
def err_constexpr_local_var_non_literal_type : Error<
"variable of non-literal type %1 cannot be defined in a constexpr "
"%select{function|constructor}0">;
"%select{function|constructor}0 before C++2b">;
def ext_constexpr_local_var_no_init : ExtWarn<
"uninitialized variable in a constexpr %select{function|constructor}0 "
"is a C++20 extension">, InGroup<CXX20>;
Expand Down
17 changes: 17 additions & 0 deletions clang/lib/AST/ExprConstant.cpp
Expand Up @@ -5003,6 +5003,18 @@ static EvalStmtResult EvaluateSwitch(StmtResult &Result, EvalInfo &Info,
llvm_unreachable("Invalid EvalStmtResult!");
}

static bool CheckLocalVariableDeclaration(EvalInfo &Info, const VarDecl *VD) {
// An expression E is a core constant expression unless the evaluation of E
// would evaluate one of the following: [C++2b] - a control flow that passes
// through a declaration of a variable with static or thread storage duration.
if (VD->isLocalVarDecl() && VD->isStaticLocal()) {
Info.CCEDiag(VD->getLocation(), diag::note_constexpr_static_local)
<< (VD->getTSCSpec() == TSCS_unspecified ? 0 : 1) << VD;
return false;
}
return true;
}

// Evaluate a statement.
static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
const Stmt *S, const SwitchCase *Case) {
Expand Down Expand Up @@ -5113,6 +5125,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
const DeclStmt *DS = cast<DeclStmt>(S);
for (const auto *D : DS->decls()) {
if (const auto *VD = dyn_cast<VarDecl>(D)) {
if (!CheckLocalVariableDeclaration(Info, VD))
return ESR_Failed;
if (VD->hasLocalStorage() && !VD->getInit())
if (!EvaluateVarDecl(Info, VD))
return ESR_Failed;
Expand Down Expand Up @@ -5156,6 +5170,9 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
case Stmt::DeclStmtClass: {
const DeclStmt *DS = cast<DeclStmt>(S);
for (const auto *D : DS->decls()) {
const VarDecl *VD = dyn_cast_or_null<VarDecl>(D);
if (VD && !CheckLocalVariableDeclaration(Info, VD))
return ESR_Failed;
// Each declaration initialization is its own full-expression.
FullExpressionRAII Scope(Info);
if (!EvaluateDecl(Info, D) && !Info.noteFailure())
Expand Down
9 changes: 5 additions & 4 deletions clang/lib/Frontend/InitPreprocessor.cpp
Expand Up @@ -561,10 +561,11 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
Builder.defineMacro("__cpp_unicode_literals", "200710L");
Builder.defineMacro("__cpp_user_defined_literals", "200809L");
Builder.defineMacro("__cpp_lambdas", "200907L");
Builder.defineMacro("__cpp_constexpr",
LangOpts.CPlusPlus20 ? "201907L" :
LangOpts.CPlusPlus17 ? "201603L" :
LangOpts.CPlusPlus14 ? "201304L" : "200704");
Builder.defineMacro("__cpp_constexpr", LangOpts.CPlusPlus2b ? "202110L"
: LangOpts.CPlusPlus20 ? "201907L"
: LangOpts.CPlusPlus17 ? "201603L"
: LangOpts.CPlusPlus14 ? "201304L"
: "200704");
Builder.defineMacro("__cpp_constexpr_in_decltype", "201711L");
Builder.defineMacro("__cpp_range_based_for",
LangOpts.CPlusPlus17 ? "201603L" : "200907");
Expand Down
63 changes: 43 additions & 20 deletions clang/lib/Sema/SemaDeclCXX.cpp
Expand Up @@ -1892,13 +1892,17 @@ static bool CheckConstexprDeclStmt(Sema &SemaRef, const FunctionDecl *Dcl,
if (VD->isStaticLocal()) {
if (Kind == Sema::CheckConstexprKind::Diagnose) {
SemaRef.Diag(VD->getLocation(),
diag::err_constexpr_local_var_static)
<< isa<CXXConstructorDecl>(Dcl)
<< (VD->getTLSKind() == VarDecl::TLS_Dynamic);
SemaRef.getLangOpts().CPlusPlus2b
? diag::warn_cxx20_compat_constexpr_static_var
: diag::ext_constexpr_static_var)
<< isa<CXXConstructorDecl>(Dcl)
<< (VD->getTLSKind() == VarDecl::TLS_Dynamic);
} else if (!SemaRef.getLangOpts().CPlusPlus2b) {
return false;
}
return false;
}
if (CheckLiteralType(SemaRef, Kind, VD->getLocation(), VD->getType(),
if (!SemaRef.LangOpts.CPlusPlus2b &&
CheckLiteralType(SemaRef, Kind, VD->getLocation(), VD->getType(),
diag::err_constexpr_local_var_non_literal_type,
isa<CXXConstructorDecl>(Dcl)))
return false;
Expand Down Expand Up @@ -2021,6 +2025,7 @@ static bool
CheckConstexprFunctionStmt(Sema &SemaRef, const FunctionDecl *Dcl, Stmt *S,
SmallVectorImpl<SourceLocation> &ReturnStmts,
SourceLocation &Cxx1yLoc, SourceLocation &Cxx2aLoc,
SourceLocation &Cxx2bLoc,
Sema::CheckConstexprKind Kind) {
// - its function-body shall be [...] a compound-statement that contains only
switch (S->getStmtClass()) {
Expand Down Expand Up @@ -2053,9 +2058,9 @@ CheckConstexprFunctionStmt(Sema &SemaRef, const FunctionDecl *Dcl, Stmt *S,
case Stmt::AttributedStmtClass:
// Attributes on a statement don't affect its formal kind and hence don't
// affect its validity in a constexpr function.
return CheckConstexprFunctionStmt(SemaRef, Dcl,
cast<AttributedStmt>(S)->getSubStmt(),
ReturnStmts, Cxx1yLoc, Cxx2aLoc, Kind);
return CheckConstexprFunctionStmt(
SemaRef, Dcl, cast<AttributedStmt>(S)->getSubStmt(), ReturnStmts,
Cxx1yLoc, Cxx2aLoc, Cxx2bLoc, Kind);

case Stmt::CompoundStmtClass: {
// C++1y allows compound-statements.
Expand All @@ -2065,7 +2070,7 @@ CheckConstexprFunctionStmt(Sema &SemaRef, const FunctionDecl *Dcl, Stmt *S,
CompoundStmt *CompStmt = cast<CompoundStmt>(S);
for (auto *BodyIt : CompStmt->body()) {
if (!CheckConstexprFunctionStmt(SemaRef, Dcl, BodyIt, ReturnStmts,
Cxx1yLoc, Cxx2aLoc, Kind))
Cxx1yLoc, Cxx2aLoc, Cxx2bLoc, Kind))
return false;
}
return true;
Expand All @@ -2078,11 +2083,11 @@ CheckConstexprFunctionStmt(Sema &SemaRef, const FunctionDecl *Dcl, Stmt *S,

IfStmt *If = cast<IfStmt>(S);
if (!CheckConstexprFunctionStmt(SemaRef, Dcl, If->getThen(), ReturnStmts,
Cxx1yLoc, Cxx2aLoc, Kind))
Cxx1yLoc, Cxx2aLoc, Cxx2bLoc, Kind))
return false;
if (If->getElse() &&
!CheckConstexprFunctionStmt(SemaRef, Dcl, If->getElse(), ReturnStmts,
Cxx1yLoc, Cxx2aLoc, Kind))
Cxx1yLoc, Cxx2aLoc, Cxx2bLoc, Kind))
return false;
return true;
}
Expand All @@ -2101,7 +2106,7 @@ CheckConstexprFunctionStmt(Sema &SemaRef, const FunctionDecl *Dcl, Stmt *S,
for (Stmt *SubStmt : S->children())
if (SubStmt &&
!CheckConstexprFunctionStmt(SemaRef, Dcl, SubStmt, ReturnStmts,
Cxx1yLoc, Cxx2aLoc, Kind))
Cxx1yLoc, Cxx2aLoc, Cxx2bLoc, Kind))
return false;
return true;

Expand All @@ -2116,7 +2121,18 @@ CheckConstexprFunctionStmt(Sema &SemaRef, const FunctionDecl *Dcl, Stmt *S,
for (Stmt *SubStmt : S->children())
if (SubStmt &&
!CheckConstexprFunctionStmt(SemaRef, Dcl, SubStmt, ReturnStmts,
Cxx1yLoc, Cxx2aLoc, Kind))
Cxx1yLoc, Cxx2aLoc, Cxx2bLoc, Kind))
return false;
return true;

case Stmt::LabelStmtClass:
case Stmt::GotoStmtClass:
if (Cxx2bLoc.isInvalid())
Cxx2bLoc = S->getBeginLoc();
for (Stmt *SubStmt : S->children())
if (SubStmt &&
!CheckConstexprFunctionStmt(SemaRef, Dcl, SubStmt, ReturnStmts,
Cxx1yLoc, Cxx2aLoc, Cxx2bLoc, Kind))
return false;
return true;

Expand All @@ -2129,17 +2145,17 @@ CheckConstexprFunctionStmt(Sema &SemaRef, const FunctionDecl *Dcl, Stmt *S,
for (Stmt *SubStmt : S->children()) {
if (SubStmt &&
!CheckConstexprFunctionStmt(SemaRef, Dcl, SubStmt, ReturnStmts,
Cxx1yLoc, Cxx2aLoc, Kind))
Cxx1yLoc, Cxx2aLoc, Cxx2bLoc, Kind))
return false;
}
return true;

case Stmt::CXXCatchStmtClass:
// Do not bother checking the language mode (already covered by the
// try block check).
if (!CheckConstexprFunctionStmt(SemaRef, Dcl,
cast<CXXCatchStmt>(S)->getHandlerBlock(),
ReturnStmts, Cxx1yLoc, Cxx2aLoc, Kind))
if (!CheckConstexprFunctionStmt(
SemaRef, Dcl, cast<CXXCatchStmt>(S)->getHandlerBlock(), ReturnStmts,
Cxx1yLoc, Cxx2aLoc, Cxx2bLoc, Kind))
return false;
return true;

Expand Down Expand Up @@ -2204,20 +2220,27 @@ static bool CheckConstexprFunctionBody(Sema &SemaRef, const FunctionDecl *Dcl,
//
// Note that walking the children here is enough to properly check for
// CompoundStmt and CXXTryStmt body.
SourceLocation Cxx1yLoc, Cxx2aLoc;
SourceLocation Cxx1yLoc, Cxx2aLoc, Cxx2bLoc;
for (Stmt *SubStmt : Body->children()) {
if (SubStmt &&
!CheckConstexprFunctionStmt(SemaRef, Dcl, SubStmt, ReturnStmts,
Cxx1yLoc, Cxx2aLoc, Kind))
Cxx1yLoc, Cxx2aLoc, Cxx2bLoc, Kind))
return false;
}

if (Kind == Sema::CheckConstexprKind::CheckValid) {
// If this is only valid as an extension, report that we don't satisfy the
// constraints of the current language.
if ((Cxx2aLoc.isValid() && !SemaRef.getLangOpts().CPlusPlus20) ||
if ((Cxx2bLoc.isValid() && !SemaRef.getLangOpts().CPlusPlus2b) ||
(Cxx2aLoc.isValid() && !SemaRef.getLangOpts().CPlusPlus20) ||
(Cxx1yLoc.isValid() && !SemaRef.getLangOpts().CPlusPlus17))
return false;
} else if (Cxx2bLoc.isValid()) {
SemaRef.Diag(Cxx2bLoc,
SemaRef.getLangOpts().CPlusPlus2b
? diag::warn_cxx20_compat_constexpr_body_invalid_stmt
: diag::ext_constexpr_body_invalid_stmt_cxx2b)
<< isa<CXXConstructorDecl>(Dcl);
} else if (Cxx2aLoc.isValid()) {
SemaRef.Diag(Cxx2aLoc,
SemaRef.getLangOpts().CPlusPlus20
Expand Down
26 changes: 15 additions & 11 deletions clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/dtor.cpp
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -std=c++2a -verify %s
// RUN: %clang_cc1 -std=c++2a -verify=expected,cxx2a %s
// RUN: %clang_cc1 -std=c++2b -verify=expected %s

// p3: if the function is a constructor or destructor, its class shall not have
// any virtual base classes;
Expand All @@ -9,40 +10,43 @@ namespace vbase {
};
}

// p3: its function-body shall not enclose
// -- a goto statement
// -- an identifier label
// -- a variable of non-literal type or of static or thread storage duration
namespace contents {
struct A {
constexpr ~A() {
goto x; // expected-error {{statement not allowed in constexpr function}}
return;
goto x; // cxx2a-warning {{use of this statement in a constexpr function is a C++2b extension}}
x: ;
}
};
struct B {
constexpr ~B() {
x: ; // expected-error {{statement not allowed in constexpr function}}
x:; // cxx2a-warning {{use of this statement in a constexpr function is a C++2b extension}}
}
};
struct Nonlit { Nonlit(); }; // expected-note {{not literal}}
struct Nonlit { // cxx2a-note {{'Nonlit' is not literal because}}
Nonlit();
};
struct C {
constexpr ~C() {
Nonlit nl; // expected-error {{non-literal}}
return;
Nonlit nl; // cxx2a-error {{variable of non-literal type 'contents::Nonlit' cannot be defined in a constexpr function before C++2b}}
}
};
struct D {
constexpr ~D() {
static int a; // expected-error {{static variable}}
return;
static int a; // cxx2a-warning {{definition of a static variable in a constexpr function is a C++2b extension}}
}
};
struct E {
constexpr ~E() {
thread_local int e; // expected-error {{thread_local variable}}
return;
thread_local int e; // cxx2a-warning {{definition of a thread_local variable in a constexpr function is a C++2b extension}}
}
};
struct F {
constexpr ~F() {
return;
extern int f;
}
};
Expand Down
55 changes: 55 additions & 0 deletions clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p3-2b.cpp
@@ -0,0 +1,55 @@
// RUN: %clang_cc1 -verify -std=c++2b -Wpre-c++2b-compat %s

constexpr int h(int n) {
if (!n)
return 0;
static const int m = n; // expected-warning {{definition of a static variable in a constexpr function is incompatible with C++ standards before C++2b}}
return m;
}

constexpr int i(int n) {
if (!n)
return 0;
thread_local const int m = n; // expected-warning {{definition of a thread_local variable in a constexpr function is incompatible with C++ standards before C++2b}}
return m;
}

constexpr int g() { // expected-error {{constexpr function never produces a constant expression}}
goto test; // expected-note {{subexpression not valid in a constant expression}} \
// expected-warning {{use of this statement in a constexpr function is incompatible with C++ standards before C++2b}}
test:
return 0;
}

constexpr void h() {
label:; // expected-warning {{use of this statement in a constexpr function is incompatible with C++ standards before C++2b}}
}

struct NonLiteral {
NonLiteral() {}
};

constexpr void non_literal() { // expected-error {{constexpr function never produces a constant expression}}
NonLiteral n; // expected-note {{non-literal type 'NonLiteral' cannot be used in a constant expression}}
}

constexpr void non_literal2(bool b) {
if (!b)
NonLiteral n;
}

constexpr int c_thread_local(int n) {
if (!n)
return 0;
static _Thread_local int a; // expected-warning {{definition of a static variable in a constexpr function is incompatible with C++ standards before C++2b}}
_Thread_local int b; // // expected-error {{'_Thread_local' variables must have global storage}}
return 0;
}

constexpr int gnu_thread_local(int n) {
if (!n)
return 0;
static __thread int a; // expected-warning {{definition of a static variable in a constexpr function is incompatible with C++ standards before C++2b}}
__thread int b; // expected-error {{'__thread' variables must have global storage}}
return 0;
}

0 comments on commit 683e83c

Please sign in to comment.