From: Richard Smith Date: Thu, 29 Sep 2011 19:11:37 +0000 (+0000) Subject: constexpr: semantic checking for constexpr variables. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c6d990a767150b02337de1136fdb55ccf349f4d1;p=clang 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). git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@140801 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Basic/DiagnosticGroups.td b/include/clang/Basic/DiagnosticGroups.td index 5a4ebad589..242c242d7d 100644 --- a/include/clang/Basic/DiagnosticGroups.td +++ b/include/clang/Basic/DiagnosticGroups.td @@ -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">; diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index cbae5be61f..65d448f27d 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -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; + "in-class initializer for static data member of type %0 not allowed, " + "accepted as an extension">, InGroup>; +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>; def err_in_class_initializer_non_constant : Error< "in-class initializer is not a constant expression">; diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index f6bfb3a2f2..e56d09f6a7 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -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; diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index e8892fd323..ae31e61c43 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/lib/Sema/SemaDeclCXX.cpp @@ -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(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()); + 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); diff --git a/test/CXX/class/class.static/class.static.data/p3.cpp b/test/CXX/class/class.static/class.static.data/p3.cpp new file mode 100644 index 0000000000..72dbec771a --- /dev/null +++ b/test/CXX/class/class.static/class.static.data/p3.cpp @@ -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; diff --git a/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p1.cpp b/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p1.cpp index d9b09c64da..e1911a2a79 100644 --- a/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p1.cpp +++ b/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p1.cpp @@ -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}} diff --git a/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp b/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp new file mode 100644 index 0000000000..9b2120c3e7 --- /dev/null +++ b/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp @@ -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}} diff --git a/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp b/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp index 19398ae4f2..d842bff3cb 100644 --- a/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp +++ b/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp @@ -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(); diff --git a/test/FixIt/fixit-cxx0x.cpp b/test/FixIt/fixit-cxx0x.cpp index c3c1dcf20f..2addad4696 100644 --- a/test/FixIt/fixit-cxx0x.cpp +++ b/test/FixIt/fixit-cxx0x.cpp @@ -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; + }; +} diff --git a/test/SemaCXX/class.cpp b/test/SemaCXX/class.cpp index fd2d0b388a..4071d1fdbb 100644 --- a/test/SemaCXX/class.cpp +++ b/test/SemaCXX/class.cpp @@ -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}} }; } diff --git a/test/SemaTemplate/instantiate-static-var.cpp b/test/SemaTemplate/instantiate-static-var.cpp index 0c06075248..723cbd388f 100644 --- a/test/SemaTemplate/instantiate-static-var.cpp +++ b/test/SemaTemplate/instantiate-static-var.cpp @@ -11,7 +11,7 @@ X xi0; // expected-note{{in instantiation of template class 'X' template 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 fy; // expected-note{{in instantiation of template class 'Y' requested here}}