[clang] [Clang] Implement __reference_converts_from_temporary (PR #91199)
via cfe-commits
cfe-commits at lists.llvm.org
Thu May 9 07:15:05 PDT 2024
https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/91199
>From dbc1c6296e943a3b9eff921e762cca641734dd3d Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 6 May 2024 14:55:54 +0200
Subject: [PATCH 1/2] [Clang] Implement __reference_converts_from_temporary
This completes the required language support for P2255R2.
---
clang/docs/LanguageExtensions.rst | 5 +-
clang/docs/ReleaseNotes.rst | 3 +
clang/include/clang/Basic/TokenKinds.def | 1 +
clang/lib/Lex/PPMacroExpansion.cpp | 2 -
clang/lib/Parse/ParseDeclCXX.cpp | 5 +-
clang/lib/Parse/ParseExpr.cpp | 1 -
clang/lib/Sema/SemaExprCXX.cpp | 157 +++++++++++++----------
clang/test/SemaCXX/type-traits.cpp | 68 ++++++++++
clang/www/cxx_status.html | 10 +-
9 files changed, 170 insertions(+), 82 deletions(-)
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index c2e90f4e7d587..97c6d69cee111 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1661,8 +1661,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 2c5308fbcb319..4e9b23d9a6a80 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -142,6 +142,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 7d6febb04a82c..41e25bebde06c 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..3b20edfd1d4f1 100644
--- a/clang/test/SemaCXX/type-traits.cpp
+++ b/clang/test/SemaCXX/type-traits.cpp
@@ -2944,6 +2944,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 &&));
@@ -2987,6 +2998,63 @@ 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));
+
+ // 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 260f74ded93c5..fd764c847d39b 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>
>From c07a07e4e082836487c9f4d0c0fc81530acbfc97 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 9 May 2024 16:14:48 +0200
Subject: [PATCH 2/2] Add access tests
---
clang/test/SemaCXX/type-traits.cpp | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/clang/test/SemaCXX/type-traits.cpp b/clang/test/SemaCXX/type-traits.cpp
index 3b20edfd1d4f1..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)));
@@ -2984,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));
@@ -3036,6 +3046,8 @@ void reference_converts_from_temporary_checks() {
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));
More information about the cfe-commits
mailing list