Index: docs/LanguageExtensions.rst =================================================================== --- docs/LanguageExtensions.rst +++ docs/LanguageExtensions.rst @@ -943,6 +943,9 @@ More information could be found `here `_. + +.. _langext-traits_feature_detection: + Checks for Type Trait Primitives ================================ @@ -1024,6 +1027,41 @@ * ``__is_nothrow_constructible`` (MSVC 2013, clang) * ``__is_assignable`` (MSVC 2015, clang) + +Expression Traits +================= + +Expression traits are special builtin constant expressions much like type traits +except that they act on expressions not types. They allow the determination of +various characteristics of an expression. The supplied expression is treated +as an unevaluated operand. + +Feature detection for expression traits is the same as for type traits. +See :ref:`Checks for Type Trait Primatives ` +for information. + +The following expression trait primitives are supported by Clang: + +* ``__has_constant_initializer(expr)``: + Determines whether `expr` names + a object that will be initialized during + `constant initialization `_ + according to the rules of [basic.start.static]. If ``expr`` does not name + an object, or if the object it names doesn't have static or thread-local + storage duration the result is false. This trait can be used determine if it's + safe to use a global variable during program startup. For example: + + .. code-block:: c++ + + static MyType global_obj; + #if __has_extension(has_constant_initializer) + static_assert(__has_constant_initializer(global_obj), + "global_obj may be unsafely used before it is initialized"); + #endif + +* ``__is_lvalue_expr(expr)`` +* ``__is_rvalue_expr(expr)`` + Blocks ====== 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 @@ -6837,7 +6837,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_not_named_var : Error< + "expression must be 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,15 @@ 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)) { + for (auto *Arg : CE->arguments()) { + if (!Arg->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/Lex/PPMacroExpansion.cpp =================================================================== --- lib/Lex/PPMacroExpansion.cpp +++ lib/Lex/PPMacroExpansion.cpp @@ -1267,6 +1267,10 @@ .Case("cxx_binary_literals", true) .Case("cxx_init_captures", LangOpts.CPlusPlus11) .Case("cxx_variable_templates", LangOpts.CPlusPlus) + // Expression traits + .Case("has_constant_initializer", LangOpts.CPlusPlus) + .Case("is_lvalue_expr", LangOpts.CPlusPlus) + .Case("is_rvalue_expr", LangOpts.CPlusPlus) .Default(false); } 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,39 @@ 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: { + // Check for 'constant initialization' according to [basic.start.static]. + 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_not_named_var) + << 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'. + 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 +4798,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/Lexer/has_extension_cxx.cpp =================================================================== --- test/Lexer/has_extension_cxx.cpp +++ test/Lexer/has_extension_cxx.cpp @@ -66,3 +66,8 @@ #if __has_extension(cxx_init_captures) int has_init_captures(); #endif + +// CHECK: has_constant_initializer +#if __has_extension(has_constant_initializer) +int has_constant_initializer(); +#endif 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,275 @@ { 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), ""); + +struct TT1 { + static const int& no_init; + static const int& glvalue_init; + static const int& temp_init; + static const int& subobj_init; +#if __cplusplus >= 201103L + static thread_local const int& tl_glvalue_init; + static thread_local const int& tl_temp_init; +#endif +}; +const int& TT1::glvalue_init = glvalue_int; +const int& TT1::temp_init = 42; +const int& TT1::subobj_init = PODType().value; + +static_assert(!__has_constant_initializer(TT1::no_init), ""); +static_assert(__has_constant_initializer(TT1::glvalue_init), ""); +static_assert(__has_constant_initializer(TT1::temp_init), ""); +static_assert(__has_constant_initializer(TT1::subobj_init), ""); +#if __cplusplus >= 201103L +thread_local const int& TT1::tl_glvalue_init = glvalue_int; +thread_local const int& TT1::tl_temp_init = 42; +static_assert(__has_constant_initializer(TT1::tl_glvalue_init), ""); +static_assert(!__has_constant_initializer(TT1::tl_temp_init), ""); +#endif + +// [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; + +void test_basic_start_static_2_2() +{ + static PODType pod; + static_assert(__has_constant_initializer(pod), ""); + +#if __cplusplus >= 201103L + 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"); + + thread_local LitType tls = 42; + static_assert(__has_constant_initializer(tls), ""); +#endif +} + +struct TT2 { + static PODType pod_noinit; + static PODType pod_copy_init; +#if __cplusplus >= 201103L + static constexpr LitType lit = {}; + static const NonLit non_lit; + static const NonLit non_lit_copy_init; +#endif +}; +PODType TT2::pod_noinit; +PODType TT2::pod_copy_init(TT2::pod_noinit); +static_assert(__has_constant_initializer(TT2::pod_noinit), ""); +static_assert(!__has_constant_initializer(TT2::pod_copy_init), ""); +#if __cplusplus >= 201103L +const NonLit TT2::non_lit(42); +const NonLit TT2::non_lit_copy_init = 42; +static_assert(__has_constant_initializer(TT2::lit), ""); +static_assert(__has_constant_initializer(TT2::non_lit) == NonLitHasConstInit, ""); +static_assert(!__has_constant_initializer(TT2::non_lit_copy_init), ""); +#endif + +#if __cplusplus >= 201103L +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), ""); + +void foo1(int p = 42) { + // ParmVarDecl's with an initializer do not have static or thread local + // storage duration. + static_assert(!__has_constant_initializer(p), ""); +}