Author: cor3ntin Date: 2024-05-10T08:50:44+02:00 New Revision: 2dbe89d15046bedcc36a5de1242e20aa91a5e598
URL: https://github.com/llvm/llvm-project/commit/2dbe89d15046bedcc36a5de1242e20aa91a5e598 DIFF: https://github.com/llvm/llvm-project/commit/2dbe89d15046bedcc36a5de1242e20aa91a5e598.diff LOG: [Clang] Implement __reference_converts_from_temporary (#91199) This completes the required language support for P2255R2. Added: Modified: clang/docs/LanguageExtensions.rst clang/docs/ReleaseNotes.rst clang/include/clang/Basic/TokenKinds.def clang/lib/Lex/PPMacroExpansion.cpp clang/lib/Parse/ParseDeclCXX.cpp clang/lib/Parse/ParseExpr.cpp clang/lib/Sema/SemaExprCXX.cpp clang/test/SemaCXX/type-traits.cpp clang/www/cxx_status.html Removed: ################################################################################ diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index 3627a780886a0..a09c409f8f91a 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -1662,8 +1662,11 @@ The following type trait primitives are supported by Clang. Those traits marked ``T`` from ``U`` is ill-formed. Deprecated, use ``__reference_constructs_from_temporary``. * ``__reference_constructs_from_temporary(T, U)`` (C++) - Returns true if a reference ``T`` can be constructed from a temporary of type + Returns true if a reference ``T`` can be direct-initialized from a temporary of type a non-cv-qualified ``U``. +* ``__reference_converts_from_temporary(T, U)`` (C++) + Returns true if a reference ``T`` can be copy-initialized from a temporary of type + a non-cv-qualified ``U``. * ``__underlying_type`` (C++, GNU, Microsoft) In addition, the following expression traits are supported: diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 4547636318a74..eef627ff2e31f 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -182,6 +182,9 @@ C++23 Feature Support - Implemented `P2448R2: Relaxing some constexpr restrictions <https://wg21.link/P2448R2>`_. +- Added a ``__reference_converts_from_temporary`` builtin, completing the necessary compiler support for + `P2255R2: Type trait to determine if a reference binds to a temporary <https://wg21.link/P2255R2>`_. + C++2c Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index a27fbed358a60..56c4b17f769d7 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -537,6 +537,7 @@ TYPE_TRAIT_1(__is_referenceable, IsReferenceable, KEYCXX) TYPE_TRAIT_1(__can_pass_in_regs, CanPassInRegs, KEYCXX) TYPE_TRAIT_2(__reference_binds_to_temporary, ReferenceBindsToTemporary, KEYCXX) TYPE_TRAIT_2(__reference_constructs_from_temporary, ReferenceConstructsFromTemporary, KEYCXX) +TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary, KEYCXX) // Embarcadero Expression Traits EXPRESSION_TRAIT(__is_lvalue_expr, IsLValueExpr, KEYCXX) diff --git a/clang/lib/Lex/PPMacroExpansion.cpp b/clang/lib/Lex/PPMacroExpansion.cpp index a5f22f01682d2..a478e0badb0c7 100644 --- a/clang/lib/Lex/PPMacroExpansion.cpp +++ b/clang/lib/Lex/PPMacroExpansion.cpp @@ -1714,8 +1714,6 @@ void Preprocessor::ExpandBuiltinMacro(Token &Tok) { return llvm::StringSwitch<bool>(II->getName()) .Case("__array_rank", true) .Case("__array_extent", true) - .Case("__reference_binds_to_temporary", true) - .Case("__reference_constructs_from_temporary", true) #define TRANSFORM_TYPE_TRAIT_DEF(_, Trait) .Case("__" #Trait, true) #include "clang/Basic/TransformTypeTraits.def" .Default(false); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index 8e0e868248293..96c9708c3711b 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -1779,9 +1779,8 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind, tok::kw___is_union, tok::kw___is_unsigned, tok::kw___is_void, - tok::kw___is_volatile, - tok::kw___reference_binds_to_temporary, - tok::kw___reference_constructs_from_temporary)) + tok::kw___is_volatile + )) // GNU libstdc++ 4.2 and libc++ use certain intrinsic names as the // name of struct templates, but some are keywords in GCC >= 4.3 // and Clang. Therefore, when we see the token sequence "struct diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 5f5f9a79c8c40..0551b8314f9f6 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -1166,7 +1166,6 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind, REVERTIBLE_TYPE_TRAIT(__is_void); REVERTIBLE_TYPE_TRAIT(__is_volatile); REVERTIBLE_TYPE_TRAIT(__reference_binds_to_temporary); - REVERTIBLE_TYPE_TRAIT(__reference_constructs_from_temporary); #define TRANSFORM_TYPE_TRAIT_DEF(_, Trait) \ REVERTIBLE_TYPE_TRAIT(RTT_JOIN(__, Trait)); #include "clang/Basic/TransformTypeTraits.def" diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index c1cb03e4ec7ae..ae844bc699143 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -5627,6 +5627,77 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT, static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, const TypeSourceInfo *Lhs, const TypeSourceInfo *Rhs, SourceLocation KeyLoc); +static ExprResult CheckConvertibilityForTypeTraits(Sema &Self, + const TypeSourceInfo *Lhs, + const TypeSourceInfo *Rhs, + SourceLocation KeyLoc) { + + QualType LhsT = Lhs->getType(); + QualType RhsT = Rhs->getType(); + + // C++0x [meta.rel]p4: + // Given the following function prototype: + // + // template <class T> + // typename add_rvalue_reference<T>::type create(); + // + // the predicate condition for a template specialization + // is_convertible<From, To> shall be satisfied if and only if + // the return expression in the following code would be + // well-formed, including any implicit conversions to the return + // type of the function: + // + // To test() { + // return create<From>(); + // } + // + // Access checking is performed as if in a context unrelated to To and + // From. Only the validity of the immediate context of the expression + // of the return-statement (including conversions to the return type) + // is considered. + // + // We model the initialization as a copy-initialization of a temporary + // of the appropriate type, which for this expression is identical to the + // return statement (since NRVO doesn't apply). + + // Functions aren't allowed to return function or array types. + if (RhsT->isFunctionType() || RhsT->isArrayType()) + return ExprError(); + + // A function definition requires a complete, non-abstract return type. + if (!Self.isCompleteType(Rhs->getTypeLoc().getBeginLoc(), RhsT) || + Self.isAbstractType(Rhs->getTypeLoc().getBeginLoc(), RhsT)) + return ExprError(); + + // Compute the result of add_rvalue_reference. + if (LhsT->isObjectType() || LhsT->isFunctionType()) + LhsT = Self.Context.getRValueReferenceType(LhsT); + + // Build a fake source and destination for initialization. + InitializedEntity To(InitializedEntity::InitializeTemporary(RhsT)); + OpaqueValueExpr From(KeyLoc, LhsT.getNonLValueExprType(Self.Context), + Expr::getValueKindForType(LhsT)); + Expr *FromPtr = &From; + InitializationKind Kind = + InitializationKind::CreateCopy(KeyLoc, SourceLocation()); + + // Perform the initialization in an unevaluated context within a SFINAE + // trap at translation unit scope. + EnterExpressionEvaluationContext Unevaluated( + Self, Sema::ExpressionEvaluationContext::Unevaluated); + Sema::SFINAETrap SFINAE(Self, /*AccessCheckingSFINAE=*/true); + Sema::ContextRAII TUContext(Self, Self.Context.getTranslationUnitDecl()); + InitializationSequence Init(Self, To, Kind, FromPtr); + if (Init.Failed()) + return ExprError(); + + ExprResult Result = Init.Perform(Self, To, Kind, FromPtr); + if (Result.isInvalid() || SFINAE.hasErrorOccurred()) + return ExprError(); + + return Result; +} + static bool EvaluateBooleanTypeTrait(Sema &S, TypeTrait Kind, SourceLocation KWLoc, ArrayRef<TypeSourceInfo *> Args, @@ -5640,13 +5711,16 @@ static bool EvaluateBooleanTypeTrait(Sema &S, TypeTrait Kind, // Evaluate ReferenceBindsToTemporary and ReferenceConstructsFromTemporary // alongside the IsConstructible traits to avoid duplication. - if (Kind <= BTT_Last && Kind != BTT_ReferenceBindsToTemporary && Kind != BTT_ReferenceConstructsFromTemporary) + if (Kind <= BTT_Last && Kind != BTT_ReferenceBindsToTemporary && + Kind != BTT_ReferenceConstructsFromTemporary && + Kind != BTT_ReferenceConvertsFromTemporary) return EvaluateBinaryTypeTrait(S, Kind, Args[0], Args[1], RParenLoc); switch (Kind) { case clang::BTT_ReferenceBindsToTemporary: case clang::BTT_ReferenceConstructsFromTemporary: + case clang::BTT_ReferenceConvertsFromTemporary: case clang::TT_IsConstructible: case clang::TT_IsNothrowConstructible: case clang::TT_IsTriviallyConstructible: { @@ -5710,8 +5784,10 @@ static bool EvaluateBooleanTypeTrait(Sema &S, TypeTrait Kind, Sema::ContextRAII TUContext(S, S.Context.getTranslationUnitDecl()); InitializedEntity To( InitializedEntity::InitializeTemporary(S.Context, Args[0])); - InitializationKind InitKind(InitializationKind::CreateDirect(KWLoc, KWLoc, - RParenLoc)); + InitializationKind InitKind( + Kind == clang::BTT_ReferenceConvertsFromTemporary + ? InitializationKind::CreateCopy(KWLoc, KWLoc) + : InitializationKind::CreateDirect(KWLoc, KWLoc, RParenLoc)); InitializationSequence Init(S, To, InitKind, ArgExprs); if (Init.Failed()) return false; @@ -5723,7 +5799,9 @@ static bool EvaluateBooleanTypeTrait(Sema &S, TypeTrait Kind, if (Kind == clang::TT_IsConstructible) return true; - if (Kind == clang::BTT_ReferenceBindsToTemporary || Kind == clang::BTT_ReferenceConstructsFromTemporary) { + if (Kind == clang::BTT_ReferenceBindsToTemporary || + Kind == clang::BTT_ReferenceConstructsFromTemporary || + Kind == clang::BTT_ReferenceConvertsFromTemporary) { if (!T->isReferenceType()) return false; @@ -5737,9 +5815,12 @@ static bool EvaluateBooleanTypeTrait(Sema &S, TypeTrait Kind, if (U->isReferenceType()) return false; - TypeSourceInfo *TPtr = S.Context.CreateTypeSourceInfo(S.Context.getPointerType(S.BuiltinRemoveReference(T, UnaryTransformType::RemoveCVRef, {}))); - TypeSourceInfo *UPtr = S.Context.CreateTypeSourceInfo(S.Context.getPointerType(S.BuiltinRemoveReference(U, UnaryTransformType::RemoveCVRef, {}))); - return EvaluateBinaryTypeTrait(S, TypeTrait::BTT_IsConvertibleTo, UPtr, TPtr, RParenLoc); + TypeSourceInfo *TPtr = S.Context.CreateTypeSourceInfo( + S.Context.getPointerType(T.getNonReferenceType())); + TypeSourceInfo *UPtr = S.Context.CreateTypeSourceInfo( + S.Context.getPointerType(U.getNonReferenceType())); + return !CheckConvertibilityForTypeTraits(S, UPtr, TPtr, RParenLoc) + .isInvalid(); } if (Kind == clang::TT_IsNothrowConstructible) @@ -5945,68 +6026,12 @@ static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, const TypeSourceI case BTT_IsConvertible: case BTT_IsConvertibleTo: case BTT_IsNothrowConvertible: { - // C++0x [meta.rel]p4: - // Given the following function prototype: - // - // template <class T> - // typename add_rvalue_reference<T>::type create(); - // - // the predicate condition for a template specialization - // is_convertible<From, To> shall be satisfied if and only if - // the return expression in the following code would be - // well-formed, including any implicit conversions to the return - // type of the function: - // - // To test() { - // return create<From>(); - // } - // - // Access checking is performed as if in a context unrelated to To and - // From. Only the validity of the immediate context of the expression - // of the return-statement (including conversions to the return type) - // is considered. - // - // We model the initialization as a copy-initialization of a temporary - // of the appropriate type, which for this expression is identical to the - // return statement (since NRVO doesn't apply). - - // Functions aren't allowed to return function or array types. - if (RhsT->isFunctionType() || RhsT->isArrayType()) - return false; - - // A return statement in a void function must have void type. if (RhsT->isVoidType()) return LhsT->isVoidType(); - // A function definition requires a complete, non-abstract return type. - if (!Self.isCompleteType(Rhs->getTypeLoc().getBeginLoc(), RhsT) || - Self.isAbstractType(Rhs->getTypeLoc().getBeginLoc(), RhsT)) - return false; - - // Compute the result of add_rvalue_reference. - if (LhsT->isObjectType() || LhsT->isFunctionType()) - LhsT = Self.Context.getRValueReferenceType(LhsT); - - // Build a fake source and destination for initialization. - InitializedEntity To(InitializedEntity::InitializeTemporary(RhsT)); - OpaqueValueExpr From(KeyLoc, LhsT.getNonLValueExprType(Self.Context), - Expr::getValueKindForType(LhsT)); - Expr *FromPtr = &From; - InitializationKind Kind(InitializationKind::CreateCopy(KeyLoc, - SourceLocation())); - - // Perform the initialization in an unevaluated context within a SFINAE - // trap at translation unit scope. - EnterExpressionEvaluationContext Unevaluated( - Self, Sema::ExpressionEvaluationContext::Unevaluated); - Sema::SFINAETrap SFINAE(Self, /*AccessCheckingSFINAE=*/true); - Sema::ContextRAII TUContext(Self, Self.Context.getTranslationUnitDecl()); - InitializationSequence Init(Self, To, Kind, FromPtr); - if (Init.Failed()) - return false; - - ExprResult Result = Init.Perform(Self, To, Kind, FromPtr); - if (Result.isInvalid() || SFINAE.hasErrorOccurred()) + ExprResult Result = + CheckConvertibilityForTypeTraits(Self, Lhs, Rhs, KeyLoc); + if (Result.isInvalid()) return false; if (BTT != BTT_IsNothrowConvertible) diff --git a/clang/test/SemaCXX/type-traits.cpp b/clang/test/SemaCXX/type-traits.cpp index 01991887b284a..f2fd45762abf8 100644 --- a/clang/test/SemaCXX/type-traits.cpp +++ b/clang/test/SemaCXX/type-traits.cpp @@ -2908,6 +2908,12 @@ struct ConvertsToRef { operator RefType() const { return static_cast<RefType>(obj); } mutable T obj = 42; }; +template <class T, class RefType = T &> +class ConvertsToRefPrivate { + operator RefType() const { return static_cast<RefType>(obj); } + mutable T obj = 42; +}; + void reference_binds_to_temporary_checks() { static_assert(!(__reference_binds_to_temporary(int &, int &))); @@ -2937,6 +2943,8 @@ void reference_binds_to_temporary_checks() { static_assert((__is_constructible(int const &, LongRef))); static_assert((__reference_binds_to_temporary(int const &, LongRef))); + static_assert(!__reference_binds_to_temporary(int const &, ConvertsToRefPrivate<long, long &>)); + // Test that it doesn't accept non-reference types as input. static_assert(!(__reference_binds_to_temporary(int, long))); @@ -2944,6 +2952,17 @@ void reference_binds_to_temporary_checks() { static_assert((__reference_binds_to_temporary(const int &, long))); } + +struct ExplicitConversionRvalueRef { + operator int(); + explicit operator int&&(); +}; + +struct ExplicitConversionRef { + operator int(); + explicit operator int&(); +}; + void reference_constructs_from_temporary_checks() { static_assert(!__reference_constructs_from_temporary(int &, int &)); static_assert(!__reference_constructs_from_temporary(int &, int &&)); @@ -2973,6 +2992,8 @@ void reference_constructs_from_temporary_checks() { static_assert(__is_constructible(int const &, LongRef)); static_assert(__reference_constructs_from_temporary(int const &, LongRef)); + static_assert(!__reference_constructs_from_temporary(int const &, ConvertsToRefPrivate<long, long &>)); + // Test that it doesn't accept non-reference types as input. static_assert(!__reference_constructs_from_temporary(int, long)); @@ -2987,6 +3008,65 @@ void reference_constructs_from_temporary_checks() { static_assert(!__reference_constructs_from_temporary(const int&, int&&)); static_assert(__reference_constructs_from_temporary(int&&, long&&)); static_assert(__reference_constructs_from_temporary(int&&, long)); + + + static_assert(!__reference_constructs_from_temporary(int&, ExplicitConversionRef)); + static_assert(!__reference_constructs_from_temporary(const int&, ExplicitConversionRef)); + static_assert(!__reference_constructs_from_temporary(int&&, ExplicitConversionRvalueRef)); + + +} + +void reference_converts_from_temporary_checks() { + static_assert(!__reference_converts_from_temporary(int &, int &)); + static_assert(!__reference_converts_from_temporary(int &, int &&)); + + static_assert(!__reference_converts_from_temporary(int const &, int &)); + static_assert(!__reference_converts_from_temporary(int const &, int const &)); + static_assert(!__reference_converts_from_temporary(int const &, int &&)); + + static_assert(!__reference_converts_from_temporary(int &, long &)); // doesn't construct + + static_assert(__reference_converts_from_temporary(int const &, long &)); + static_assert(__reference_converts_from_temporary(int const &, long &&)); + static_assert(__reference_converts_from_temporary(int &&, long &)); + + using LRef = ConvertsToRef<int, int &>; + using RRef = ConvertsToRef<int, int &&>; + using CLRef = ConvertsToRef<int, const int &>; + using LongRef = ConvertsToRef<long, long &>; + static_assert(__is_constructible(int &, LRef)); + static_assert(!__reference_converts_from_temporary(int &, LRef)); + + static_assert(__is_constructible(int &&, RRef)); + static_assert(!__reference_converts_from_temporary(int &&, RRef)); + + static_assert(__is_constructible(int const &, CLRef)); + static_assert(!__reference_converts_from_temporary(int &&, CLRef)); + + static_assert(__is_constructible(int const &, LongRef)); + static_assert(__reference_converts_from_temporary(int const &, LongRef)); + static_assert(!__reference_converts_from_temporary(int const &, ConvertsToRefPrivate<long, long &>)); + + + // Test that it doesn't accept non-reference types as input. + static_assert(!__reference_converts_from_temporary(int, long)); + + static_assert(__reference_converts_from_temporary(const int &, long)); + + // Additional checks + static_assert(__reference_converts_from_temporary(POD const&, Derives)); + static_assert(__reference_converts_from_temporary(int&&, int)); + static_assert(__reference_converts_from_temporary(const int&, int)); + static_assert(!__reference_converts_from_temporary(int&&, int&&)); + static_assert(!__reference_converts_from_temporary(const int&, int&&)); + static_assert(__reference_converts_from_temporary(int&&, long&&)); + static_assert(__reference_converts_from_temporary(int&&, long)); + + static_assert(!__reference_converts_from_temporary(int&, ExplicitConversionRef)); + static_assert(__reference_converts_from_temporary(const int&, ExplicitConversionRef)); + static_assert(__reference_converts_from_temporary(int&&, ExplicitConversionRvalueRef)); + } void array_rank() { diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 6e0599cc9fe0d..1338f544ffcb5 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -347,15 +347,7 @@ <h2 id="cxx23">C++23 implementation status</h2> <tr> <td>Type trait to determine if a reference binds to a temporary</td> <td><a href="https://wg21.link/P2255R2">P2255R2</a></td> - <td class="partial" align="center"> - <details><summary>Partial</summary> - Clang provides <tt>__reference_constructs_from_temporary</tt> type - trait builtin, with which <tt>std::reference_constructs_from_temporary</tt> - is implemented. <tt>__reference_converts_from_temporary</tt> needs to be - provided, following the normal cross-vendor convention to implement - traits requiring compiler support directly. - </details></td> - </td> + <td class="unreleased" align="center">Clang 19</td> </tr> <!-- July 2022 papers --> <tr> _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits