Skip to content

[Clang] Implement __reference_converts_from_temporary #91199

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^^^^^^^^^^^^^^^

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/TokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 0 additions & 2 deletions clang/lib/Lex/PPMacroExpansion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 2 additions & 3 deletions clang/lib/Parse/ParseDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why the change here? It seems the fallback is still valuable here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As they are new names, they were never structs. So there is no need for compatibility for old standard libraries.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so was here unintentionally/without good reason? I guess that makes sense. SGTM.

))
// 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
Expand Down
1 change: 0 additions & 1 deletion clang/lib/Parse/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
157 changes: 91 additions & 66 deletions clang/lib/Sema/SemaExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a test for this access checking? I would expect something conversion ops/ctors that are protected, but they are friends of eachother.

// 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,
Expand All @@ -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: {
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
80 changes: 80 additions & 0 deletions clang/test/SemaCXX/type-traits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 &)));
Expand Down Expand Up @@ -2937,13 +2943,26 @@ 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)));

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 &&));
Expand Down Expand Up @@ -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));
Expand All @@ -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() {
Expand Down
10 changes: 1 addition & 9 deletions clang/www/cxx_status.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down