Index: include/clang/AST/Expr.h =================================================================== --- include/clang/AST/Expr.h +++ include/clang/AST/Expr.h @@ -526,7 +526,8 @@ /// If this expression is not constant and Culprit is non-null, /// it is used to store the address of first non constant expr. bool isConstantInitializer(ASTContext &Ctx, bool ForRef, - const Expr **Culprit = nullptr) const; + const Expr **Culprit = nullptr, + bool AllowNonLiteral = false) const; /// EvalStatus is a struct with detailed info about an evaluation in progress. struct EvalStatus { Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -6803,7 +6803,10 @@ def err_incomplete_type_used_in_type_trait_expr : Error< "incomplete type %0 used in type trait expression">; - + +def err_has_constant_init_expression_trait_invalid_arg : Error< + "expression does not reference a named variable">; + def err_dimension_expr_not_constant_integer : Error< "dimension expression does not evaluate to a constant unsigned int">; Index: include/clang/Basic/ExpressionTraits.h =================================================================== --- include/clang/Basic/ExpressionTraits.h +++ include/clang/Basic/ExpressionTraits.h @@ -19,7 +19,8 @@ enum ExpressionTrait { ET_IsLValueExpr, - ET_IsRValueExpr + ET_IsRValueExpr, + ET_HasConstantInitializer }; } Index: include/clang/Basic/TokenKinds.def =================================================================== --- include/clang/Basic/TokenKinds.def +++ include/clang/Basic/TokenKinds.def @@ -454,6 +454,9 @@ TYPE_TRAIT_2(__is_trivially_assignable, IsTriviallyAssignable, KEYCXX) KEYWORD(__underlying_type , KEYCXX) +// Clang-only C++ Expression Traits +KEYWORD(__has_constant_initializer , KEYCXX) + // Embarcadero Expression Traits KEYWORD(__is_lvalue_expr , KEYCXX) KEYWORD(__is_rvalue_expr , KEYCXX) Index: lib/AST/Expr.cpp =================================================================== --- lib/AST/Expr.cpp +++ lib/AST/Expr.cpp @@ -2612,7 +2612,8 @@ } bool Expr::isConstantInitializer(ASTContext &Ctx, bool IsForRef, - const Expr **Culprit) const { + const Expr **Culprit, + bool AllowNonLiteral) const { // This function is attempting whether an expression is an initializer // which can be evaluated at compile-time. It very closely parallels // ConstExprEmitter in CGExprConstant.cpp; if they don't match, it @@ -2649,7 +2650,17 @@ assert(CE->getNumArgs() == 1 && "trivial ctor with > 1 argument"); return CE->getArg(0)->isConstantInitializer(Ctx, false, Culprit); } - + if (CE->getConstructor()->isConstexpr() && + (CE->getConstructor()->getParent()->hasTrivialDestructor() || + AllowNonLiteral)) { + if (!CE->getNumArgs()) return true; + unsigned numArgs = CE->getNumArgs(); + for (unsigned i = 0; i < numArgs; i++) { + if (!CE->getArg(i)->isConstantInitializer(Ctx, false, Culprit)) + return false; + } + return true; + } break; } case CompoundLiteralExprClass: { Index: lib/AST/StmtPrinter.cpp =================================================================== --- lib/AST/StmtPrinter.cpp +++ lib/AST/StmtPrinter.cpp @@ -2321,8 +2321,9 @@ static const char *getExpressionTraitName(ExpressionTrait ET) { switch (ET) { - case ET_IsLValueExpr: return "__is_lvalue_expr"; - case ET_IsRValueExpr: return "__is_rvalue_expr"; + case ET_IsLValueExpr: return "__is_lvalue_expr"; + case ET_IsRValueExpr: return "__is_rvalue_expr"; + case ET_HasConstantInitializer: return "__has_constant_initializer"; } llvm_unreachable("Expression type trait not covered by switch"); } Index: lib/Parse/ParseDeclCXX.cpp =================================================================== --- lib/Parse/ParseDeclCXX.cpp +++ lib/Parse/ParseDeclCXX.cpp @@ -1273,7 +1273,8 @@ Tok.isNot(tok::identifier) && !Tok.isAnnotation() && Tok.getIdentifierInfo() && - Tok.isOneOf(tok::kw___is_abstract, + Tok.isOneOf(tok::kw___has_constant_initializer, + tok::kw___is_abstract, tok::kw___is_arithmetic, tok::kw___is_array, tok::kw___is_assignable, @@ -3667,8 +3668,8 @@ return true; case AttributeList::AT_WarnUnusedResult: return !ScopeName && AttrName->getName().equals("nodiscard"); - case AttributeList::AT_Unused: - return !ScopeName && AttrName->getName().equals("maybe_unused"); + case AttributeList::AT_Unused: + return !ScopeName && AttrName->getName().equals("maybe_unused"); default: return false; } Index: lib/Parse/ParseExpr.cpp =================================================================== --- lib/Parse/ParseExpr.cpp +++ lib/Parse/ParseExpr.cpp @@ -677,11 +677,14 @@ /// '__trivially_copyable' /// /// binary-type-trait: -/// [GNU] '__is_base_of' +/// [GNU] '__is_base_of' /// [MS] '__is_convertible_to' /// '__is_convertible' /// '__is_same' /// +/// [Clang] expression-trait: +/// '__has_constant_initializer' +/// /// [Embarcadero] array-type-trait: /// '__array_rank' /// '__array_extent' @@ -797,6 +800,7 @@ RevertibleTypeTraits[PP.getIdentifierInfo(#Name)] \ = RTT_JOIN(tok::kw_,Name) + REVERTIBLE_TYPE_TRAIT(__has_constant_initializer); REVERTIBLE_TYPE_TRAIT(__is_abstract); REVERTIBLE_TYPE_TRAIT(__is_arithmetic); REVERTIBLE_TYPE_TRAIT(__is_array); @@ -1321,10 +1325,11 @@ case tok::kw___array_extent: return ParseArrayTypeTrait(); + case tok::kw___has_constant_initializer: case tok::kw___is_lvalue_expr: case tok::kw___is_rvalue_expr: return ParseExpressionTrait(); - + case tok::at: { SourceLocation AtLoc = ConsumeToken(); return ParseObjCAtExpression(AtLoc); Index: lib/Parse/ParseExprCXX.cpp =================================================================== --- lib/Parse/ParseExprCXX.cpp +++ lib/Parse/ParseExprCXX.cpp @@ -2949,6 +2949,7 @@ static ExpressionTrait ExpressionTraitFromTokKind(tok::TokenKind kind) { switch(kind) { default: llvm_unreachable("Not a known unary expression trait."); + case tok::kw___has_constant_initializer: return ET_HasConstantInitializer; case tok::kw___is_lvalue_expr: return ET_IsLValueExpr; case tok::kw___is_rvalue_expr: return ET_IsRValueExpr; } @@ -3083,6 +3084,7 @@ if (T.expectAndConsume()) return ExprError(); + EnterExpressionEvaluationContext Unevaluated(Actions, Sema::Unevaluated); ExprResult Expr = ParseExpression(); T.consumeClose(); Index: lib/Sema/SemaExprCXX.cpp =================================================================== --- lib/Sema/SemaExprCXX.cpp +++ lib/Sema/SemaExprCXX.cpp @@ -4749,10 +4749,38 @@ return Result; } -static bool EvaluateExpressionTrait(ExpressionTrait ET, Expr *E) { +static bool EvaluateExpressionTrait(Sema &Self, ExpressionTrait ET, + SourceLocation KWLoc, Expr *E, + SourceLocation RParen) { switch (ET) { case ET_IsLValueExpr: return E->isLValue(); case ET_IsRValueExpr: return E->isRValue(); + case ET_HasConstantInitializer: { + DeclRefExpr *DRE = dyn_cast(E->IgnoreImpCasts()); + if (!DRE) { + // It is a usage error to specify an expression that does not reference + // a named variable. + Self.Diag(KWLoc, diag::err_has_constant_init_expression_trait_invalid_arg) + << E->getSourceRange(); + return false; + } + if (VarDecl *VD = dyn_cast(DRE->getDecl())) { + // Thread local objects specified as TSL_Static have a constant + // initializer. TLS_Dynamic objects still need to be checked below. + if (VD->getTLSKind() == VarDecl::TLS_Static) + return true; + // Check the initializer of objects with static or thread-local storage + // duration. AObjects with automatic or dynamic lifetime never have + // a 'constant initializer'. + else if ((VD->hasGlobalStorage() || + VD->getTLSKind() != VarDecl::TLS_None) && VD->hasInit()) { + QualType baseType = Self.Context.getBaseElementType(VD->getType()); + return VD->getInit()->isConstantInitializer(Self.Context, + baseType->isReferenceType(), nullptr, /*AllowNonLiteral=*/true); + } + } + return false; + } } llvm_unreachable("Expression trait not covered by switch"); } @@ -4769,7 +4797,7 @@ return BuildExpressionTrait(ET, KWLoc, PE.get(), RParen); } - bool Value = EvaluateExpressionTrait(ET, Queried); + bool Value = EvaluateExpressionTrait(*this, ET, KWLoc, Queried, RParen); return new (Context) ExpressionTraitExpr(KWLoc, ET, Queried, Value, RParen, Context.BoolTy); Index: test/SemaCXX/expression-traits.cpp =================================================================== --- test/SemaCXX/expression-traits.cpp +++ test/SemaCXX/expression-traits.cpp @@ -1,5 +1,5 @@ // RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions %s - +// RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -std=c++14 %s // // Tests for "expression traits" intrinsics such as __is_lvalue_expr. // @@ -617,3 +617,219 @@ { check_temp_param_6<3,AnInt>(); } + +//===========================================================================// +// __has_constant_initializer +//===========================================================================// +struct PODType { + int value; + int value2; +}; +#if __cplusplus >= 201103L +struct LitType { + constexpr LitType() : value(0) {} + constexpr LitType(int x) : value(x) {} + LitType(void*) : value(-1) {} + int value; +}; +#endif + +struct NonLit { +#if __cplusplus >= 201402L + constexpr NonLit() : value(0) {} + constexpr NonLit(int x ) : value(x) {} +#else + NonLit() : value(0) {} + NonLit(int x ) : value(x) {} +#endif + NonLit(void*) : value(-1) {} + ~NonLit() {} + int value; +}; + +struct StoresNonLit { +#if __cplusplus >= 201402L + constexpr StoresNonLit() : obj() {} + constexpr StoresNonLit(int x) : obj(x) {} +#else + StoresNonLit() : obj() {} + StoresNonLit(int x) : obj(x) {} +#endif + StoresNonLit(void* p) : obj(p) {} + NonLit obj; +}; + +const bool NonLitHasConstInit = +#if __cplusplus >= 201402L + true; +#else + false; +#endif + +// Test diagnostics when the argument does not refer to a named identifier +void check_is_constant_init_bogus() +{ + (void)__has_constant_initializer(42); // expected-error {{does not reference a named variable}} + (void)__has_constant_initializer(ReturnInt()); // expected-error {{does not reference a named variable}} + (void)__has_constant_initializer(42, 43); // expected-error {{does not reference a named variable}} +} + +// [basic.start.static]p2.1 +// if each full-expression (including implicit conversions) that appears in +// the initializer of a reference with static or thread storage duration is +// a constant expression (5.20) and the reference is bound to a glvalue +// designating an object with static storage duration, to a temporary object +// (see 12.2) or subobject thereof, or to a function; + +// Test binding to a static glvalue +const int glvalue_int = 42; +const int glvalue_int2 = ReturnInt(); +const int& glvalue_ref = glvalue_int; +const int& glvalue_ref2 = glvalue_int2; +static_assert(__has_constant_initializer(glvalue_ref), ""); +static_assert(__has_constant_initializer(glvalue_ref2), ""); + +__thread const int& glvalue_ref_tl = glvalue_int; +static_assert(__has_constant_initializer(glvalue_ref_tl), ""); + +void test_basic_start_static_2_1() { + const int non_global = 42; + const int& non_global_ref = non_global; + static_assert(!__has_constant_initializer(non_global), "automatic variables never have const init"); + static_assert(!__has_constant_initializer(non_global_ref), "automatic variables never have const init"); + + static const int& local_init = non_global; + static_assert(!__has_constant_initializer(local_init), "init must be static glvalue"); + static const int& global_init = glvalue_int; + static_assert(__has_constant_initializer(global_init), ""); + static const int& temp_init = 42; + static_assert(__has_constant_initializer(temp_init), ""); +#if 0 + /// FIXME: Why is this failing? + __thread const int& tl_init = 42; + static_assert(__has_constant_initializer(tl_init), ""); +#endif +} + +const int& temp_ref = 42; +const int& temp_ref2 = ReturnInt(); +static_assert(__has_constant_initializer(temp_ref), ""); +static_assert(!__has_constant_initializer(temp_ref2), ""); + +const NonLit& nl_temp_ref = 42; +static_assert(!__has_constant_initializer(nl_temp_ref), ""); + +#if __cplusplus >= 201103L +const LitType& lit_temp_ref = 42; +static_assert(__has_constant_initializer(lit_temp_ref), ""); + +const int& subobj_ref = LitType{}.value; +static_assert(__has_constant_initializer(subobj_ref), ""); +#endif + +const int& nl_subobj_ref = NonLit().value; +static_assert(!__has_constant_initializer(nl_subobj_ref), ""); + +// [basic.start.static]p2.2 +// if an object with static or thread storage duration is initialized by a +// constructor call, and if the initialization full-expression is a constant +// initializer for the object; +#if __cplusplus >= 201103L +void test_basic_start_static_2_2() +{ + constexpr LitType l; + static_assert(!__has_constant_initializer(l), "non-static objects don't have const init"); + + static LitType static_lit = l; + static_assert(__has_constant_initializer(static_lit), ""); + + static LitType static_lit2 = (void*)0; + static_assert(!__has_constant_initializer(static_lit2), "constructor not constexpr"); + + static LitType static_lit3 = ReturnInt(); + static_assert(!__has_constant_initializer(static_lit3), "initializer not constexpr"); + +#if __cplusplus >= 201103L + thread_local LitType tls = 42; + static_assert(__has_constant_initializer(tls), ""); +#endif +} + +LitType lit_ctor; +LitType lit_ctor2{}; +LitType lit_ctor3 = {}; +__thread LitType lit_ctor_tl = {}; +static_assert(__has_constant_initializer(lit_ctor), ""); +static_assert(__has_constant_initializer(lit_ctor2), ""); +static_assert(__has_constant_initializer(lit_ctor3), ""); +static_assert(__has_constant_initializer(lit_ctor_tl), ""); + +NonLit nl_ctor; +NonLit nl_ctor2{}; +NonLit nl_ctor3 = {}; +thread_local NonLit nl_ctor_tl = {}; +static_assert(NonLitHasConstInit == __has_constant_initializer(nl_ctor), ""); +static_assert(NonLitHasConstInit == __has_constant_initializer(nl_ctor2), ""); +static_assert(NonLitHasConstInit == __has_constant_initializer(nl_ctor3), ""); +static_assert(NonLitHasConstInit == __has_constant_initializer(nl_ctor_tl), ""); + +StoresNonLit snl; +static_assert(NonLitHasConstInit == __has_constant_initializer(snl), ""); + +// Non-literal types cannot appear in the initializer of a non-literal type. +int nl_in_init = NonLit{42}.value; +static_assert(!__has_constant_initializer(nl_in_init), ""); + +int lit_in_init = LitType{42}.value; +static_assert(__has_constant_initializer(lit_in_init), ""); +#endif + +// [basic.start.static]p2.3 +// if an object with static or thread storage duration is not initialized by a +// constructor call and if either the object is value-initialized or every +// full-expression that appears in its initializer is a constant expression. +void test_basic_start_static_2_3() +{ + const int local = 42; + static_assert(!__has_constant_initializer(local), "automatic variable does not have const init"); + + static int static_local = 42; + static_assert(__has_constant_initializer(static_local), ""); + + static int static_local2; + static_assert(!__has_constant_initializer(static_local2), "no init"); + +#if __cplusplus >= 201103L + thread_local int tl_local = 42; + static_assert(__has_constant_initializer(tl_local), ""); +#endif +} + +int no_init; +static_assert(!__has_constant_initializer(no_init), ""); + +int arg_init = 42; +static_assert(__has_constant_initializer(arg_init), ""); + +PODType pod_init = {}; +static_assert(__has_constant_initializer(pod_init), ""); + +PODType pod_missing_init = {42 /* should have second arg */}; +static_assert(__has_constant_initializer(pod_missing_init), ""); + +PODType pod_full_init = {1, 2}; +static_assert(__has_constant_initializer(pod_full_init), ""); + +PODType pod_non_constexpr_init = {1, ReturnInt()}; +static_assert(!__has_constant_initializer(pod_non_constexpr_init), ""); + +#if __cplusplus >= 201103L +int val_init{}; +static_assert(__has_constant_initializer(val_init), ""); + +int brace_init = {}; +static_assert(__has_constant_initializer(brace_init), ""); +#endif + +__thread int tl_init = 0; +static_assert(__has_constant_initializer(tl_init), "");