constexpr: semantic checking for constexpr variables.

We had an extension which allowed const static class members of floating-point type to have in-class initializers, 'as a C++0x extension'. However, C++0x does not allow this. The extension has been kept, and extended to all literal types in C++0x mode (with a fixit to add the 'constexpr' specifier).

llvm-svn: 140801
This commit is contained in:
Richard Smith 2011-09-29 19:11:37 +00:00
parent d71061298c
commit 2316cd8b79
11 changed files with 248 additions and 56 deletions

View File

@ -303,9 +303,7 @@ def NonGCC : DiagGroup<"non-gcc",
// A warning group for warnings about using C++0x features as extensions in
// earlier C++ versions.
def CXX0xStaticNonIntegralInitializer :
DiagGroup<"c++0x-static-nonintegral-init">;
def CXX0x : DiagGroup<"c++0x-extensions", [CXX0xStaticNonIntegralInitializer]>;
def CXX0x : DiagGroup<"c++0x-extensions">;
def DelegatingCtorCycles :
DiagGroup<"delegating-ctor-cycles">;

View File

@ -1186,7 +1186,15 @@ def err_constexpr_tag : Error<
def err_constexpr_dtor : Error<"destructor cannot be marked constexpr">;
def err_constexpr_no_declarators : Error<
"constexpr can only be used in variable and function declarations">;
def err_invalid_constexpr_var_decl : Error<
"constexpr variable declaration must be a definition">;
def err_constexpr_var_requires_init : Error<
"declaration of constexpr variable %0 requires an initializer">;
def err_constexpr_initialized_static_member : Error<
"definition of initialized static data member %0 cannot be marked constexpr">;
def err_constexpr_var_requires_const_init : Error<
"constexpr variable %0 must be initialized by a constant expression">;
// Objective-C++
def err_objc_decls_may_only_appear_in_global_scope : Error<
"Objective-C declarations may only appear in global scope">;
@ -4072,8 +4080,12 @@ def err_in_class_initializer_non_const : Error<
def err_in_class_initializer_bad_type : Error<
"static data member of type %0 must be initialized out of line">;
def ext_in_class_initializer_float_type : ExtWarn<
"in-class initializer for static data member of type %0 "
"is a C++0x extension">, InGroup<CXX0xStaticNonIntegralInitializer>;
"in-class initializer for static data member of type %0 not allowed, "
"accepted as an extension">, InGroup<DiagGroup<"static-member-init">>;
def ext_in_class_initializer_literal_type : ExtWarn<
"in-class initializer for static data member of type %0 requires "
"'constexpr' specifier, accepted as an extension">,
InGroup<DiagGroup<"static-member-init">>;
def err_in_class_initializer_non_constant : Error<
"in-class initializer is not a constant expression">;

View File

@ -3833,8 +3833,36 @@ Sema::ActOnVariableDeclarator(Scope *S, Declarator &D, DeclContext *DC,
}
if (D.getDeclSpec().isConstexprSpecified()) {
// FIXME: check this is a valid use of constexpr.
NewVD->setConstexpr(true);
// FIXME: once we know whether there's an initializer, apply this to
// static data members too.
if (!NewVD->isStaticDataMember() &&
!NewVD->isThisDeclarationADefinition()) {
// 'constexpr' is redundant and ill-formed on a non-defining declaration
// of a variable. Suggest replacing it with 'const' if appropriate.
SourceLocation ConstexprLoc = D.getDeclSpec().getConstexprSpecLoc();
SourceRange ConstexprRange(ConstexprLoc, ConstexprLoc);
// If the declarator is complex, we need to move the keyword to the
// innermost chunk as we switch it from 'constexpr' to 'const'.
int Kind = DeclaratorChunk::Paren;
for (unsigned I = 0, E = D.getNumTypeObjects(); I != E; ++I) {
Kind = D.getTypeObject(I).Kind;
if (Kind != DeclaratorChunk::Paren)
break;
}
if ((D.getDeclSpec().getTypeQualifiers() & DeclSpec::TQ_const) ||
Kind == DeclaratorChunk::Reference)
Diag(ConstexprLoc, diag::err_invalid_constexpr_var_decl)
<< FixItHint::CreateRemoval(ConstexprRange);
else if (Kind == DeclaratorChunk::Paren)
Diag(ConstexprLoc, diag::err_invalid_constexpr_var_decl)
<< FixItHint::CreateReplacement(ConstexprRange, "const");
else
Diag(ConstexprLoc, diag::err_invalid_constexpr_var_decl)
<< FixItHint::CreateRemoval(ConstexprRange)
<< FixItHint::CreateInsertion(D.getIdentifierLoc(), "const ");
} else {
NewVD->setConstexpr(true);
}
}
}
@ -5796,11 +5824,26 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init,
// A member-declarator can contain a constant-initializer only
// if it declares a static member (9.4) of const integral or
// const enumeration type, see 9.4.2.
//
// C++0x [class.static.data]p3:
// If a non-volatile const static data member is of integral or
// enumeration type, its declaration in the class definition can
// specify a brace-or-equal-initializer in which every initalizer-clause
// that is an assignment-expression is a constant expression. A static
// data member of literal type can be declared in the class definition
// with the constexpr specifier; if so, its declaration shall specify a
// brace-or-equal-initializer in which every initializer-clause that is
// an assignment-expression is a constant expression.
QualType T = VDecl->getType();
// Do nothing on dependent types.
if (T->isDependentType()) {
// Allow any 'static constexpr' members, whether or not they are of literal
// type. We separately check that the initializer is a constant expression,
// which implicitly requires the member to be of literal type.
} else if (VDecl->isConstexpr()) {
// Require constness.
} else if (!T.isConstQualified()) {
Diag(VDecl->getLocation(), diag::err_in_class_initializer_non_const)
@ -5809,6 +5852,9 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init,
// We allow integer constant expressions in all cases.
} else if (T->isIntegralOrEnumerationType()) {
// FIXME: In C++0x, a non-constexpr const static data member with an
// in-class initializer cannot be volatile.
// Check whether the expression is a constant expression.
SourceLocation Loc;
if (Init->isValueDependent())
@ -5828,31 +5874,28 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init,
VDecl->setInvalidDecl();
}
// We allow floating-point constants as an extension in C++03, and
// C++0x has far more complicated rules that we don't really
// implement fully.
} else {
bool Allowed = false;
if (getLangOptions().CPlusPlus0x) {
Allowed = T->isLiteralType();
} else if (T->isFloatingType()) { // also permits complex, which is ok
Diag(VDecl->getLocation(), diag::ext_in_class_initializer_float_type)
<< T << Init->getSourceRange();
Allowed = true;
}
// Suggest adding 'constexpr' in C++0x for literal types.
} else if (getLangOptions().CPlusPlus0x && T->isLiteralType()) {
Diag(VDecl->getLocation(), diag::ext_in_class_initializer_literal_type)
<< T << Init->getSourceRange()
<< FixItHint::CreateInsertion(VDecl->getLocStart(), "constexpr ");
VDecl->setConstexpr(true);
if (!Allowed) {
Diag(VDecl->getLocation(), diag::err_in_class_initializer_bad_type)
<< T << Init->getSourceRange();
VDecl->setInvalidDecl();
// We allow floating-point constants as an extension.
} else if (T->isFloatingType()) { // also permits complex, which is ok
Diag(VDecl->getLocation(), diag::ext_in_class_initializer_float_type)
<< T << Init->getSourceRange();
// TODO: there are probably expressions that pass here that shouldn't.
} else if (!Init->isValueDependent() &&
!Init->isConstantInitializer(Context, false)) {
if (!Init->isValueDependent() &&
!Init->isConstantInitializer(Context, false)) {
Diag(Init->getExprLoc(), diag::err_in_class_initializer_non_constant)
<< Init->getSourceRange();
VDecl->setInvalidDecl();
}
} else {
Diag(VDecl->getLocation(), diag::err_in_class_initializer_bad_type)
<< T << Init->getSourceRange();
VDecl->setInvalidDecl();
}
} else if (VDecl->isFileVarDecl()) {
if (VDecl->getStorageClassAsWritten() == SC_Extern &&
@ -5893,6 +5936,17 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init,
if (!VDecl->isInvalidDecl())
checkUnsafeAssigns(VDecl->getLocation(), VDecl->getType(), Init);
if (VDecl->isConstexpr() && !VDecl->isInvalidDecl() &&
!VDecl->getType()->isDependentType() &&
!Init->isTypeDependent() && !Init->isValueDependent() &&
!Init->isConstantInitializer(Context,
VDecl->getType()->isReferenceType())) {
// FIXME: Improve this diagnostic to explain why the initializer is not
// a constant expression.
Diag(VDecl->getLocation(), diag::err_constexpr_var_requires_const_init)
<< VDecl << Init->getSourceRange();
}
Init = MaybeCreateExprWithCleanups(Init);
// Attach the initializer to the decl.
@ -5958,6 +6012,24 @@ void Sema::ActOnUninitializedDecl(Decl *RealDecl,
return;
}
// C++0x [dcl.constexpr]p9: An object or reference declared constexpr must
// have an initializer.
// C++0x [class.static.data]p3: A static data member can be declared with
// the constexpr specifier; if so, its declaration shall specify
// a brace-or-equal-initializer.
if (Var->isConstexpr()) {
// FIXME: Provide fix-its to convert the constexpr to const.
if (Var->isStaticDataMember() && Var->getAnyInitializer()) {
Diag(Var->getLocation(), diag::err_constexpr_initialized_static_member)
<< Var->getDeclName();
} else {
Diag(Var->getLocation(), diag::err_constexpr_var_requires_init)
<< Var->getDeclName();
}
Var->setInvalidDecl();
return;
}
switch (Var->isThisDeclarationADefinition()) {
case VarDecl::Definition:
if (!Var->isStaticDataMember() || !Var->getAnyInitializer())
@ -6151,9 +6223,8 @@ void Sema::ActOnCXXForRangeDecl(Decl *D) {
case SC_OpenCLWorkGroupLocal:
llvm_unreachable("Unexpected storage class");
}
// FIXME: constexpr isn't allowed here.
//if (DS.isConstexprSpecified())
// Error = 5;
if (VD->isConstexpr())
Error = 5;
if (Error != -1) {
Diag(VD->getOuterLocStart(), diag::err_for_range_storage_class)
<< VD->getDeclName() << Error;

View File

@ -1239,14 +1239,8 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
if (Init)
AddInitializerToDecl(Member, Init, false,
DS.getTypeSpecType() == DeclSpec::TST_auto);
else if (DS.getTypeSpecType() == DeclSpec::TST_auto &&
DS.getStorageClassSpec() == DeclSpec::SCS_static) {
// C++0x [dcl.spec.auto]p4: 'auto' can only be used in the type of a static
// data member if a brace-or-equal-initializer is provided.
Diag(Loc, diag::err_auto_var_requires_init)
<< Name << cast<ValueDecl>(Member)->getType();
Member->setInvalidDecl();
}
else if (DS.getStorageClassSpec() == DeclSpec::SCS_static)
ActOnUninitializedDecl(Member, DS.getTypeSpecType() == DeclSpec::TST_auto);
FinalizeDeclaration(Member);
@ -8727,10 +8721,21 @@ void Sema::AddCXXDirectInitializerToDecl(Decl *RealDecl,
return;
}
CheckImplicitConversions(Result.get(), LParenLoc);
Expr *Init = Result.get();
CheckImplicitConversions(Init, LParenLoc);
Result = MaybeCreateExprWithCleanups(Result);
VDecl->setInit(Result.takeAs<Expr>());
if (VDecl->isConstexpr() && !VDecl->isInvalidDecl() &&
!Init->isValueDependent() &&
!Init->isConstantInitializer(Context,
VDecl->getType()->isReferenceType())) {
// FIXME: Improve this diagnostic to explain why the initializer is not
// a constant expression.
Diag(VDecl->getLocation(), diag::err_constexpr_var_requires_const_init)
<< VDecl << Init->getSourceRange();
}
Init = MaybeCreateExprWithCleanups(Init);
VDecl->setInit(Init);
VDecl->setCXXDirectInitializer(true);
CheckCompleteVariableDeclaration(VDecl);

View File

@ -0,0 +1,24 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++0x %s
struct NonLit {
NonLit();
};
struct S {
static constexpr int a = 0;
static constexpr int b; // expected-error {{declaration of constexpr variable 'b' requires an initializer}}
static constexpr int c = 0;
static const int d;
static constexpr double e = 0.0; // ok
static const double f = 0.0; // expected-warning {{accepted as an extension}}
static char *const g = 0; // expected-warning {{accepted as an extension}}
static const NonLit h = NonLit(); // expected-error {{must be initialized out of line}}
};
constexpr int S::a; // expected-error {{definition of initialized static data member 'a' cannot be marked constexpr}}
constexpr int S::b = 0;
const int S::c;
constexpr int S::d = 0;

View File

@ -11,19 +11,22 @@ struct notlit2 {
constexpr int i1 = 0;
constexpr int f1() { return 0; }
struct s1 {
constexpr static int mi = 0;
constexpr static int mi1 = 0;
const static int mi2;
};
constexpr int s1::mi2 = 0;
// invalid declarations
// not a definition of an object
constexpr extern int i2; // x
constexpr extern int i2; // expected-error {{constexpr variable declaration must be a definition}}
// not a literal type
constexpr notlit nl1; // x
constexpr notlit nl1; // expected-error {{declaration of constexpr variable 'nl1' requires an initializer}}
// function parameters
void f2(constexpr int i) {} // expected-error {{function parameter cannot be constexpr}}
// non-static member
struct s2 {
constexpr int mi; // expected-error {{non-static data member cannot be constexpr}}
constexpr int mi1; // expected-error {{non-static data member cannot be constexpr}}
static constexpr int mi2; // expected-error {{requires an initializer}}
};
// typedef
typedef constexpr int CI; // expected-error {{typedef cannot be constexpr}}
@ -63,7 +66,8 @@ constexpr T ft(T t) { return t; }
template <>
notlit ft(notlit nl) { return nl; }
constexpr int i3 = ft(1);
// FIXME: The initializer is a constant expression.
constexpr int i3 = ft(1); // unexpected-error {{must be initialized by a constant expression}}
void test() {
// ignore constexpr when instantiating with non-literal
@ -85,17 +89,17 @@ constexpr pixel::pixel(int a)
: x(square(a)), y(square(a))
{ }
constexpr pixel small(2); // x (no definition of square(int) yet, so can't
// constexpr-eval pixel(int))
constexpr pixel small(2); // expected-error {{must be initialized by a constant expression}}
constexpr int square(int x) {
return x * x;
}
constexpr pixel large(4); // now valid
// FIXME: The initializer is a constant expression.
constexpr pixel large(4); // unexpected-error {{must be initialized by a constant expression}}
int next(constexpr int x) { // expected-error {{function parameter cannot be constexpr}}
return x + 1;
}
extern constexpr int memsz; // x
extern constexpr int memsz; // expected-error {{constexpr variable declaration must be a definition}}

View File

@ -0,0 +1,37 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++0x %s
// A constexpr specifier used in an object declaration declares the object as
// const.
constexpr int a = 0;
extern const int a;
int i;
constexpr int *b = &i;
extern int *const b;
constexpr int &c = i;
extern int &c;
constexpr int (*d)(int) = 0;
extern int (*const d)(int);
// A variable declaration which uses the constexpr specifier shall have an
// initializer and shall be initialized by a constant expression.
constexpr int ni1; // expected-error {{declaration of constexpr variable 'ni1' requires an initializer}}
constexpr struct C { C(); } ni2; // expected-error {{declaration of constexpr variable 'ni2' requires an initializer}}
constexpr double &ni3; // expected-error {{declaration of constexpr variable 'ni3' requires an initializer}}
constexpr int nc1 = i; // expected-error {{constexpr variable 'nc1' must be initialized by a constant expression}}
constexpr C nc2 = C(); // expected-error {{constexpr variable 'nc2' must be initialized by a constant expression}}
int &f();
constexpr int &nc3 = f(); // expected-error {{constexpr variable 'nc3' must be initialized by a constant expression}}
constexpr int nc4(i); // expected-error {{constexpr variable 'nc4' must be initialized by a constant expression}}
constexpr C nc5((C())); // expected-error {{constexpr variable 'nc5' must be initialized by a constant expression}}
int &f();
constexpr int &nc6(f()); // expected-error {{constexpr variable 'nc6' must be initialized by a constant expression}}
struct pixel {
int x, y;
};
constexpr pixel ur = { 1294, 1024 }; // ok
constexpr pixel origin; // expected-error {{requires an initializer}}

View File

@ -100,8 +100,7 @@ void g() {
for (extern int a : A()) {} // expected-error {{loop variable 'a' may not be declared 'extern'}}
for (static int a : A()) {} // expected-error {{loop variable 'a' may not be declared 'static'}}
for (register int a : A()) {} // expected-error {{loop variable 'a' may not be declared 'register'}}
// FIXME: when clang supports constexpr, this should be rejected.
for (constexpr int a : A()) {} // desired-error {{loop variable 'a' may not be declared 'constexpr'}}
for (constexpr int a : A()) {} // expected-error {{loop variable 'a' may not be declared 'constexpr'}}
struct NoBeginADL {
null_t alt_end();

View File

@ -1,6 +1,6 @@
// RUN: %clang_cc1 -verify -std=c++0x %s
// RUN: cp %s %t
// RUN: not %clang_cc1 -x c++ -std=c++0x -fixit %t
// RUN: not %clang_cc1 -x c++ -std=c++0x -Werror -fixit %t
// RUN: %clang_cc1 -Wall -pedantic -x c++ -std=c++0x %t
/* This is a test of the various code modification hints that only
@ -17,3 +17,45 @@ void x() {
using ::T = void; // expected-error {{name defined in alias declaration must be an identifier}}
using typename U = void; // expected-error {{name defined in alias declaration must be an identifier}}
using typename ::V = void; // expected-error {{name defined in alias declaration must be an identifier}}
namespace Constexpr {
extern constexpr int a; // expected-error {{must be a definition}}
// -> extern const int a;
extern constexpr int *b; // expected-error {{must be a definition}}
// -> extern int *const b;
extern constexpr int &c; // expected-error {{must be a definition}}
// -> extern int &b;
extern constexpr const int d; // expected-error {{must be a definition}}
// -> extern const int d;
int z;
constexpr int a = 0;
constexpr int *b = &z;
constexpr int &c = z;
constexpr int d = a;
// FIXME: Provide FixIts for static data members too.
#if 0
struct S {
static constexpr int a = 0;
static constexpr int b; // xpected-error {{requires an initializer}}
// -> const int b;
};
constexpr int S::a; // xpected-error {{requires an initializer}}
// -> const int S::a;
constexpr int S::b = 0;
#endif
struct S {
static const double d = 0.0; // expected-warning {{accepted as an extension}}
// -> constexpr static const double d = 0.0;
static char *const p = 0; // expected-warning {{accepted as an extension}}
// -> constexpr static char *const p = 0;
};
}

View File

@ -172,8 +172,8 @@ namespace rdar8367341 {
float foo();
struct A {
static const float x = 5.0f; // expected-warning {{in-class initializer for static data member of type 'const float' is a C++0x extension}}
static const float y = foo(); // expected-warning {{in-class initializer for static data member of type 'const float' is a C++0x extension}} expected-error {{in-class initializer is not a constant expression}}
static const float x = 5.0f; // expected-warning {{in-class initializer for static data member of type 'const float' not allowed}}
static const float y = foo(); // expected-warning {{in-class initializer for static data member of type 'const float' not allowed}} expected-error {{in-class initializer is not a constant expression}}
};
}

View File

@ -11,7 +11,7 @@ X<int, 0> xi0; // expected-note{{in instantiation of template class 'X<int, 0>'
template<typename T>
class Y {
static const T value = 0; // expected-warning{{in-class initializer for static data member of type 'const float' is a C++0x extension}}
static const T value = 0; // expected-warning{{in-class initializer for static data member of type 'const float' not allowed, accepted as an extension}}
};
Y<float> fy; // expected-note{{in instantiation of template class 'Y<float>' requested here}}