mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-18 23:56:49 +00:00
[Clang] Introduce a trait to determine the structure binding size (#131515)
Introduce a trait to determine the number of bindings that would be produced by ```cpp auto [...p] = expr; ``` This is necessary to implement P2300 (https://eel.is/c++draft/exec#snd.concepts-5), but can also be used to implement a general get<N> function that supports aggregates `__builtin_structured_binding_size` is a unary type trait that evaluates to the number of bindings in a decomposition If the argument cannot be decomposed, a sfinae-friendly error is produced. A type is considered a valid tuple if `std::tuple_size_v<T>` is a valid expression, even if there is no valid `std::tuple_element` specialization or suitable `get` function for that type. Fixes #46049
This commit is contained in:
parent
0619892cab
commit
bc8b19c757
@ -1912,6 +1912,40 @@ A simplistic usage example as might be seen in standard C++ headers follows:
|
||||
// Emulate type trait for compatibility with other compilers.
|
||||
#endif
|
||||
|
||||
|
||||
.. _builtin_structured_binding_size-doc:
|
||||
|
||||
__builtin_structured_binding_size (C++)
|
||||
---------------------------------------
|
||||
|
||||
The ``__builtin_structured_binding_size(T)`` type trait returns
|
||||
the *structured binding size* ([dcl.struct.bind]) of type ``T``
|
||||
|
||||
This is equivalent to the size of the pack ``p`` in ``auto&& [...p] = declval<T&>();``.
|
||||
If the argument cannot be decomposed, ``__builtin_structured_binding_size(T)``
|
||||
is not a valid expression (``__builtin_structured_binding_size`` is SFINAE-friendly).
|
||||
|
||||
builtin arrays, builtin SIMD vectors,
|
||||
builtin complex types, *tuple-like* types, and decomposable class types
|
||||
are decomposable types.
|
||||
|
||||
A type is considered a valid *tuple-like* if ``std::tuple_size_v<T>`` is a valid expression,
|
||||
even if there is no valid ``std::tuple_element`` specialization or suitable
|
||||
``get`` function for that type.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
template<std::size_t Idx, typename T>
|
||||
requires (Idx < __builtin_structured_binding_size(T))
|
||||
decltype(auto) constexpr get_binding(T&& obj) {
|
||||
auto && [...p] = std::forward<T>(obj);
|
||||
return p...[Idx];
|
||||
}
|
||||
struct S { int a = 0, b = 42; };
|
||||
static_assert(__builtin_structured_binding_size(S) == 2);
|
||||
static_assert(get_binding<1>(S{}) == 42);
|
||||
|
||||
|
||||
Blocks
|
||||
======
|
||||
|
||||
|
@ -74,6 +74,9 @@ What's New in Clang |release|?
|
||||
C++ Language Changes
|
||||
--------------------
|
||||
|
||||
- Added a :ref:`__builtin_structured_binding_size <builtin_structured_binding_size-doc>` (T)
|
||||
builtin that returns the number of structured bindings that would be produced by destructuring ``T``.
|
||||
|
||||
- Similarly to GCC, Clang now supports constant expressions in
|
||||
the strings of a GNU ``asm`` statement.
|
||||
|
||||
|
@ -51,6 +51,7 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
namespace clang {
|
||||
|
||||
@ -2765,20 +2766,16 @@ public:
|
||||
/// \endcode
|
||||
class TypeTraitExpr final
|
||||
: public Expr,
|
||||
private llvm::TrailingObjects<TypeTraitExpr, TypeSourceInfo *> {
|
||||
private llvm::TrailingObjects<TypeTraitExpr, APValue, TypeSourceInfo *> {
|
||||
/// The location of the type trait keyword.
|
||||
SourceLocation Loc;
|
||||
|
||||
/// The location of the closing parenthesis.
|
||||
SourceLocation RParenLoc;
|
||||
|
||||
// Note: The TypeSourceInfos for the arguments are allocated after the
|
||||
// TypeTraitExpr.
|
||||
|
||||
TypeTraitExpr(QualType T, SourceLocation Loc, TypeTrait Kind,
|
||||
ArrayRef<TypeSourceInfo *> Args,
|
||||
SourceLocation RParenLoc,
|
||||
bool Value);
|
||||
ArrayRef<TypeSourceInfo *> Args, SourceLocation RParenLoc,
|
||||
std::variant<bool, APValue> Value);
|
||||
|
||||
TypeTraitExpr(EmptyShell Empty) : Expr(TypeTraitExprClass, Empty) {}
|
||||
|
||||
@ -2786,6 +2783,10 @@ class TypeTraitExpr final
|
||||
return getNumArgs();
|
||||
}
|
||||
|
||||
size_t numTrailingObjects(OverloadToken<APValue>) const {
|
||||
return TypeTraitExprBits.IsBooleanTypeTrait ? 0 : 1;
|
||||
}
|
||||
|
||||
public:
|
||||
friend class ASTStmtReader;
|
||||
friend class ASTStmtWriter;
|
||||
@ -2798,7 +2799,13 @@ public:
|
||||
SourceLocation RParenLoc,
|
||||
bool Value);
|
||||
|
||||
static TypeTraitExpr *Create(const ASTContext &C, QualType T,
|
||||
SourceLocation Loc, TypeTrait Kind,
|
||||
ArrayRef<TypeSourceInfo *> Args,
|
||||
SourceLocation RParenLoc, APValue Value);
|
||||
|
||||
static TypeTraitExpr *CreateDeserialized(const ASTContext &C,
|
||||
bool IsStoredAsBool,
|
||||
unsigned NumArgs);
|
||||
|
||||
/// Determine which type trait this expression uses.
|
||||
@ -2806,11 +2813,20 @@ public:
|
||||
return static_cast<TypeTrait>(TypeTraitExprBits.Kind);
|
||||
}
|
||||
|
||||
bool getValue() const {
|
||||
assert(!isValueDependent());
|
||||
bool isStoredAsBoolean() const {
|
||||
return TypeTraitExprBits.IsBooleanTypeTrait;
|
||||
}
|
||||
|
||||
bool getBoolValue() const {
|
||||
assert(!isValueDependent() && TypeTraitExprBits.IsBooleanTypeTrait);
|
||||
return TypeTraitExprBits.Value;
|
||||
}
|
||||
|
||||
const APValue &getAPValue() const {
|
||||
assert(!isValueDependent() && !TypeTraitExprBits.IsBooleanTypeTrait);
|
||||
return *getTrailingObjects<APValue>();
|
||||
}
|
||||
|
||||
/// Determine the number of arguments to this type trait.
|
||||
unsigned getNumArgs() const { return TypeTraitExprBits.NumArgs; }
|
||||
|
||||
|
@ -954,11 +954,13 @@ protected:
|
||||
LLVM_PREFERRED_TYPE(TypeTrait)
|
||||
unsigned Kind : 8;
|
||||
|
||||
/// If this expression is not value-dependent, this indicates whether
|
||||
/// the trait evaluated true or false.
|
||||
LLVM_PREFERRED_TYPE(bool)
|
||||
unsigned IsBooleanTypeTrait : 1;
|
||||
|
||||
/// If this expression is a non value-dependent boolean trait,
|
||||
/// this indicates whether the trait evaluated true or false.
|
||||
LLVM_PREFERRED_TYPE(bool)
|
||||
unsigned Value : 1;
|
||||
|
||||
/// The number of arguments to this type trait. According to [implimits]
|
||||
/// 8 bits would be enough, but we require (and test for) at least 16 bits
|
||||
/// to mirror FunctionType.
|
||||
|
@ -591,6 +591,8 @@ def err_decomp_decl_std_tuple_size_not_constant : Error<
|
||||
"is not a valid integral constant expression">;
|
||||
def note_in_binding_decl_init : Note<
|
||||
"in implicit initialization of binding declaration %0">;
|
||||
def err_arg_is_not_destructurable : Error<
|
||||
"type %0 cannot be decomposed">;
|
||||
|
||||
def err_std_type_trait_not_class_template : Error<
|
||||
"unsupported standard library implementation: "
|
||||
|
@ -553,8 +553,8 @@ TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary
|
||||
// IsDeducible is only used internally by clang for CTAD implementation and
|
||||
// is not exposed to users.
|
||||
TYPE_TRAIT_2(/*EmptySpellingName*/, IsDeducible, KEYCXX)
|
||||
|
||||
TYPE_TRAIT_1(__is_bitwise_cloneable, IsBitwiseCloneable, KEYALL)
|
||||
TYPE_TRAIT_1(__builtin_structured_binding_size, StructuredBindingSize, KEYCXX)
|
||||
|
||||
// Embarcadero Expression Traits
|
||||
EXPRESSION_TRAIT(__is_lvalue_expr, IsLValueExpr, KEYCXX)
|
||||
|
@ -6116,7 +6116,8 @@ public:
|
||||
RecordDecl *ClassDecl,
|
||||
const IdentifierInfo *Name);
|
||||
|
||||
unsigned GetDecompositionElementCount(QualType DecompType);
|
||||
std::optional<unsigned int> GetDecompositionElementCount(QualType DecompType,
|
||||
SourceLocation Loc);
|
||||
void CheckCompleteDecompositionDeclaration(DecompositionDecl *DD);
|
||||
|
||||
/// Stack containing information needed when in C++2a an 'auto' is encountered
|
||||
|
@ -8955,13 +8955,16 @@ ExpectedStmt ASTNodeImporter::VisitTypeTraitExpr(TypeTraitExpr *E) {
|
||||
if (Error Err = ImportContainerChecked(E->getArgs(), ToArgs))
|
||||
return std::move(Err);
|
||||
|
||||
// According to Sema::BuildTypeTrait(), if E is value-dependent,
|
||||
// Value is always false.
|
||||
bool ToValue = (E->isValueDependent() ? false : E->getValue());
|
||||
|
||||
return TypeTraitExpr::Create(
|
||||
Importer.getToContext(), ToType, ToBeginLoc, E->getTrait(), ToArgs,
|
||||
ToEndLoc, ToValue);
|
||||
if (E->isStoredAsBoolean()) {
|
||||
// According to Sema::BuildTypeTrait(), if E is value-dependent,
|
||||
// Value is always false.
|
||||
bool ToValue = (E->isValueDependent() ? false : E->getBoolValue());
|
||||
return TypeTraitExpr::Create(Importer.getToContext(), ToType, ToBeginLoc,
|
||||
E->getTrait(), ToArgs, ToEndLoc, ToValue);
|
||||
}
|
||||
return TypeTraitExpr::Create(Importer.getToContext(), ToType, ToBeginLoc,
|
||||
E->getTrait(), ToArgs, ToEndLoc,
|
||||
E->getAPValue());
|
||||
}
|
||||
|
||||
ExpectedStmt ASTNodeImporter::VisitCXXTypeidExpr(CXXTypeidExpr *E) {
|
||||
|
@ -2844,9 +2844,13 @@ template <class Emitter>
|
||||
bool Compiler<Emitter>::VisitTypeTraitExpr(const TypeTraitExpr *E) {
|
||||
if (DiscardResult)
|
||||
return true;
|
||||
if (E->getType()->isBooleanType())
|
||||
return this->emitConstBool(E->getValue(), E);
|
||||
return this->emitConst(E->getValue(), E);
|
||||
if (E->isStoredAsBoolean()) {
|
||||
if (E->getType()->isBooleanType())
|
||||
return this->emitConstBool(E->getBoolValue(), E);
|
||||
return this->emitConst(E->getBoolValue(), E);
|
||||
}
|
||||
PrimType T = classifyPrim(E->getType());
|
||||
return this->visitAPValue(E->getAPValue(), T, E);
|
||||
}
|
||||
|
||||
template <class Emitter>
|
||||
|
@ -1854,23 +1854,34 @@ bool MaterializeTemporaryExpr::isUsableInConstantExpressions(
|
||||
|
||||
TypeTraitExpr::TypeTraitExpr(QualType T, SourceLocation Loc, TypeTrait Kind,
|
||||
ArrayRef<TypeSourceInfo *> Args,
|
||||
SourceLocation RParenLoc, bool Value)
|
||||
SourceLocation RParenLoc,
|
||||
std::variant<bool, APValue> Value)
|
||||
: Expr(TypeTraitExprClass, T, VK_PRValue, OK_Ordinary), Loc(Loc),
|
||||
RParenLoc(RParenLoc) {
|
||||
assert(Kind <= TT_Last && "invalid enum value!");
|
||||
|
||||
TypeTraitExprBits.Kind = Kind;
|
||||
assert(static_cast<unsigned>(Kind) == TypeTraitExprBits.Kind &&
|
||||
"TypeTraitExprBits.Kind overflow!");
|
||||
TypeTraitExprBits.Value = Value;
|
||||
|
||||
TypeTraitExprBits.IsBooleanTypeTrait = std::holds_alternative<bool>(Value);
|
||||
if (TypeTraitExprBits.IsBooleanTypeTrait)
|
||||
TypeTraitExprBits.Value = std::get<bool>(Value);
|
||||
else
|
||||
*getTrailingObjects<APValue>() = std::get<APValue>(std::move(Value));
|
||||
|
||||
TypeTraitExprBits.NumArgs = Args.size();
|
||||
assert(Args.size() == TypeTraitExprBits.NumArgs &&
|
||||
"TypeTraitExprBits.NumArgs overflow!");
|
||||
|
||||
auto **ToArgs = getTrailingObjects<TypeSourceInfo *>();
|
||||
for (unsigned I = 0, N = Args.size(); I != N; ++I)
|
||||
ToArgs[I] = Args[I];
|
||||
|
||||
setDependence(computeDependence(this));
|
||||
|
||||
assert((TypeTraitExprBits.IsBooleanTypeTrait || isValueDependent() ||
|
||||
getAPValue().isInt() || getAPValue().isAbsent()) &&
|
||||
"Only int values are supported by clang");
|
||||
}
|
||||
|
||||
TypeTraitExpr *TypeTraitExpr::Create(const ASTContext &C, QualType T,
|
||||
@ -1879,13 +1890,25 @@ TypeTraitExpr *TypeTraitExpr::Create(const ASTContext &C, QualType T,
|
||||
ArrayRef<TypeSourceInfo *> Args,
|
||||
SourceLocation RParenLoc,
|
||||
bool Value) {
|
||||
void *Mem = C.Allocate(totalSizeToAlloc<TypeSourceInfo *>(Args.size()));
|
||||
void *Mem =
|
||||
C.Allocate(totalSizeToAlloc<APValue, TypeSourceInfo *>(0, Args.size()));
|
||||
return new (Mem) TypeTraitExpr(T, Loc, Kind, Args, RParenLoc, Value);
|
||||
}
|
||||
|
||||
TypeTraitExpr *TypeTraitExpr::Create(const ASTContext &C, QualType T,
|
||||
SourceLocation Loc, TypeTrait Kind,
|
||||
ArrayRef<TypeSourceInfo *> Args,
|
||||
SourceLocation RParenLoc, APValue Value) {
|
||||
void *Mem =
|
||||
C.Allocate(totalSizeToAlloc<APValue, TypeSourceInfo *>(1, Args.size()));
|
||||
return new (Mem) TypeTraitExpr(T, Loc, Kind, Args, RParenLoc, Value);
|
||||
}
|
||||
|
||||
TypeTraitExpr *TypeTraitExpr::CreateDeserialized(const ASTContext &C,
|
||||
bool IsStoredAsBool,
|
||||
unsigned NumArgs) {
|
||||
void *Mem = C.Allocate(totalSizeToAlloc<TypeSourceInfo *>(NumArgs));
|
||||
void *Mem = C.Allocate(totalSizeToAlloc<APValue, TypeSourceInfo *>(
|
||||
IsStoredAsBool ? 0 : 1, NumArgs));
|
||||
return new (Mem) TypeTraitExpr(EmptyShell());
|
||||
}
|
||||
|
||||
|
@ -12102,7 +12102,12 @@ public:
|
||||
}
|
||||
|
||||
bool VisitTypeTraitExpr(const TypeTraitExpr *E) {
|
||||
return Success(E->getValue(), E);
|
||||
if (E->isStoredAsBoolean())
|
||||
return Success(E->getBoolValue(), E);
|
||||
if (E->getAPValue().isAbsent())
|
||||
return false;
|
||||
assert(E->getAPValue().isInt() && "APValue type not supported");
|
||||
return Success(E->getAPValue().getInt(), E);
|
||||
}
|
||||
|
||||
bool VisitArrayTypeTraitExpr(const ArrayTypeTraitExpr *E) {
|
||||
|
@ -724,7 +724,12 @@ public:
|
||||
}
|
||||
|
||||
Value *VisitTypeTraitExpr(const TypeTraitExpr *E) {
|
||||
return llvm::ConstantInt::get(ConvertType(E->getType()), E->getValue());
|
||||
if (E->isStoredAsBoolean())
|
||||
return llvm::ConstantInt::get(ConvertType(E->getType()),
|
||||
E->getBoolValue());
|
||||
assert(E->getAPValue().isInt() && "APValue type not supported");
|
||||
return llvm::ConstantInt::get(ConvertType(E->getType()),
|
||||
E->getAPValue().getInt());
|
||||
}
|
||||
|
||||
Value *VisitConceptSpecializationExpr(const ConceptSpecializationExpr *E) {
|
||||
|
@ -1475,6 +1475,48 @@ static DeclAccessPair findDecomposableBaseClass(Sema &S, SourceLocation Loc,
|
||||
return DeclAccessPair::make(const_cast<CXXRecordDecl*>(ClassWithFields), AS);
|
||||
}
|
||||
|
||||
static bool CheckMemberDecompositionFields(Sema &S, SourceLocation Loc,
|
||||
const CXXRecordDecl *OrigRD,
|
||||
QualType DecompType,
|
||||
DeclAccessPair BasePair) {
|
||||
const auto *RD = cast_or_null<CXXRecordDecl>(BasePair.getDecl());
|
||||
if (!RD)
|
||||
return true;
|
||||
|
||||
for (auto *FD : RD->fields()) {
|
||||
if (FD->isUnnamedBitField())
|
||||
continue;
|
||||
|
||||
// All the non-static data members are required to be nameable, so they
|
||||
// must all have names.
|
||||
if (!FD->getDeclName()) {
|
||||
if (RD->isLambda()) {
|
||||
S.Diag(Loc, diag::err_decomp_decl_lambda);
|
||||
S.Diag(RD->getLocation(), diag::note_lambda_decl);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (FD->isAnonymousStructOrUnion()) {
|
||||
S.Diag(Loc, diag::err_decomp_decl_anon_union_member)
|
||||
<< DecompType << FD->getType()->isUnionType();
|
||||
S.Diag(FD->getLocation(), diag::note_declared_at);
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME: Are there any other ways we could have an anonymous member?
|
||||
}
|
||||
// The field must be accessible in the context of the structured binding.
|
||||
// We already checked that the base class is accessible.
|
||||
// FIXME: Add 'const' to AccessedEntity's classes so we can remove the
|
||||
// const_cast here.
|
||||
S.CheckStructuredBindingMemberAccess(
|
||||
Loc, const_cast<CXXRecordDecl *>(OrigRD),
|
||||
DeclAccessPair::make(FD, CXXRecordDecl::MergeAccess(
|
||||
BasePair.getAccess(), FD->getAccess())));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool checkMemberDecomposition(Sema &S, ArrayRef<BindingDecl*> Bindings,
|
||||
ValueDecl *Src, QualType DecompType,
|
||||
const CXXRecordDecl *OrigRD) {
|
||||
@ -1485,7 +1527,7 @@ static bool checkMemberDecomposition(Sema &S, ArrayRef<BindingDecl*> Bindings,
|
||||
CXXCastPath BasePath;
|
||||
DeclAccessPair BasePair =
|
||||
findDecomposableBaseClass(S, Src->getLocation(), OrigRD, BasePath);
|
||||
const CXXRecordDecl *RD = cast_or_null<CXXRecordDecl>(BasePair.getDecl());
|
||||
const auto *RD = cast_or_null<CXXRecordDecl>(BasePair.getDecl());
|
||||
if (!RD)
|
||||
return true;
|
||||
QualType BaseType = S.Context.getQualifiedType(S.Context.getRecordType(RD),
|
||||
@ -1503,43 +1545,20 @@ static bool checkMemberDecomposition(Sema &S, ArrayRef<BindingDecl*> Bindings,
|
||||
auto FlatBindings = DD->flat_bindings();
|
||||
assert(llvm::range_size(FlatBindings) == NumFields);
|
||||
auto FlatBindingsItr = FlatBindings.begin();
|
||||
|
||||
if (CheckMemberDecompositionFields(S, Src->getLocation(), OrigRD, DecompType,
|
||||
BasePair))
|
||||
return true;
|
||||
|
||||
for (auto *FD : RD->fields()) {
|
||||
if (FD->isUnnamedBitField())
|
||||
continue;
|
||||
|
||||
// All the non-static data members are required to be nameable, so they
|
||||
// must all have names.
|
||||
if (!FD->getDeclName()) {
|
||||
if (RD->isLambda()) {
|
||||
S.Diag(Src->getLocation(), diag::err_decomp_decl_lambda);
|
||||
S.Diag(RD->getLocation(), diag::note_lambda_decl);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (FD->isAnonymousStructOrUnion()) {
|
||||
S.Diag(Src->getLocation(), diag::err_decomp_decl_anon_union_member)
|
||||
<< DecompType << FD->getType()->isUnionType();
|
||||
S.Diag(FD->getLocation(), diag::note_declared_at);
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME: Are there any other ways we could have an anonymous member?
|
||||
}
|
||||
|
||||
// We have a real field to bind.
|
||||
assert(FlatBindingsItr != FlatBindings.end());
|
||||
BindingDecl *B = *(FlatBindingsItr++);
|
||||
SourceLocation Loc = B->getLocation();
|
||||
|
||||
// The field must be accessible in the context of the structured binding.
|
||||
// We already checked that the base class is accessible.
|
||||
// FIXME: Add 'const' to AccessedEntity's classes so we can remove the
|
||||
// const_cast here.
|
||||
S.CheckStructuredBindingMemberAccess(
|
||||
Loc, const_cast<CXXRecordDecl *>(OrigRD),
|
||||
DeclAccessPair::make(FD, CXXRecordDecl::MergeAccess(
|
||||
BasePair.getAccess(), FD->getAccess())));
|
||||
|
||||
// Initialize the binding to Src.FD.
|
||||
ExprResult E = S.BuildDeclRefExpr(Src, DecompType, VK_LValue, Loc);
|
||||
if (E.isInvalid())
|
||||
@ -1642,6 +1661,56 @@ void Sema::CheckCompleteDecompositionDeclaration(DecompositionDecl *DD) {
|
||||
DD->setInvalidDecl();
|
||||
}
|
||||
|
||||
std::optional<unsigned> Sema::GetDecompositionElementCount(QualType T,
|
||||
SourceLocation Loc) {
|
||||
const ASTContext &Ctx = getASTContext();
|
||||
assert(!T->isDependentType());
|
||||
|
||||
Qualifiers Quals;
|
||||
QualType Unqual = Context.getUnqualifiedArrayType(T, Quals);
|
||||
Quals.removeCVRQualifiers();
|
||||
T = Context.getQualifiedType(Unqual, Quals);
|
||||
|
||||
if (auto *CAT = Ctx.getAsConstantArrayType(T))
|
||||
return CAT->getSize().getZExtValue();
|
||||
if (auto *VT = T->getAs<VectorType>())
|
||||
return VT->getNumElements();
|
||||
if (T->getAs<ComplexType>())
|
||||
return 2;
|
||||
|
||||
llvm::APSInt TupleSize(Ctx.getTypeSize(Ctx.getSizeType()));
|
||||
switch (isTupleLike(*this, Loc, T, TupleSize)) {
|
||||
case IsTupleLike::Error:
|
||||
return {};
|
||||
case IsTupleLike::TupleLike:
|
||||
return TupleSize.getExtValue();
|
||||
case IsTupleLike::NotTupleLike:
|
||||
break;
|
||||
}
|
||||
|
||||
const CXXRecordDecl *OrigRD = T->getAsCXXRecordDecl();
|
||||
if (!OrigRD || OrigRD->isUnion())
|
||||
return std::nullopt;
|
||||
|
||||
if (RequireCompleteType(Loc, T, diag::err_incomplete_type))
|
||||
return std::nullopt;
|
||||
|
||||
CXXCastPath BasePath;
|
||||
DeclAccessPair BasePair =
|
||||
findDecomposableBaseClass(*this, Loc, OrigRD, BasePath);
|
||||
const auto *RD = cast_or_null<CXXRecordDecl>(BasePair.getDecl());
|
||||
if (!RD)
|
||||
return std::nullopt;
|
||||
|
||||
unsigned NumFields = llvm::count_if(
|
||||
RD->fields(), [](FieldDecl *FD) { return !FD->isUnnamedBitField(); });
|
||||
|
||||
if (CheckMemberDecompositionFields(*this, Loc, OrigRD, T, BasePair))
|
||||
return true;
|
||||
|
||||
return NumFields;
|
||||
}
|
||||
|
||||
void Sema::MergeVarDeclExceptionSpecs(VarDecl *New, VarDecl *Old) {
|
||||
// Shortcut if exceptions are disabled.
|
||||
if (!getLangOpts().CXXExceptions)
|
||||
|
@ -5066,6 +5066,10 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT,
|
||||
case UTT_IsInterfaceClass:
|
||||
return true;
|
||||
|
||||
// We diagnose incomplete class types later.
|
||||
case UTT_StructuredBindingSize:
|
||||
return true;
|
||||
|
||||
// C++14 [meta.unary.prop]:
|
||||
// If T is a non-union class type, T shall be a complete type.
|
||||
case UTT_IsEmpty:
|
||||
@ -5813,6 +5817,34 @@ static ExprResult CheckConvertibilityForTypeTraits(
|
||||
return Result;
|
||||
}
|
||||
|
||||
static APValue EvaluateSizeTTypeTrait(Sema &S, TypeTrait Kind,
|
||||
SourceLocation KWLoc,
|
||||
ArrayRef<TypeSourceInfo *> Args,
|
||||
SourceLocation RParenLoc,
|
||||
bool IsDependent) {
|
||||
if (IsDependent)
|
||||
return APValue();
|
||||
|
||||
switch (Kind) {
|
||||
case TypeTrait::UTT_StructuredBindingSize: {
|
||||
QualType T = Args[0]->getType();
|
||||
SourceRange ArgRange = Args[0]->getTypeLoc().getSourceRange();
|
||||
std::optional<unsigned> Size =
|
||||
S.GetDecompositionElementCount(T, ArgRange.getBegin());
|
||||
if (!Size) {
|
||||
S.Diag(KWLoc, diag::err_arg_is_not_destructurable) << T << ArgRange;
|
||||
return APValue();
|
||||
}
|
||||
llvm::APSInt V =
|
||||
S.getASTContext().MakeIntValue(*Size, S.getASTContext().getSizeType());
|
||||
return APValue{V};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
llvm_unreachable("Not a SizeT type trait");
|
||||
}
|
||||
}
|
||||
|
||||
static bool EvaluateBooleanTypeTrait(Sema &S, TypeTrait Kind,
|
||||
SourceLocation KWLoc,
|
||||
ArrayRef<TypeSourceInfo *> Args,
|
||||
@ -6014,9 +6046,12 @@ bool Sema::CheckTypeTraitArity(unsigned Arity, SourceLocation Loc, size_t N) {
|
||||
|
||||
enum class TypeTraitReturnType {
|
||||
Bool,
|
||||
SizeT,
|
||||
};
|
||||
|
||||
static TypeTraitReturnType GetReturnType(TypeTrait Kind) {
|
||||
if (Kind == TypeTrait::UTT_StructuredBindingSize)
|
||||
return TypeTraitReturnType::SizeT;
|
||||
return TypeTraitReturnType::Bool;
|
||||
}
|
||||
|
||||
@ -6047,6 +6082,12 @@ ExprResult Sema::BuildTypeTrait(TypeTrait Kind, SourceLocation KWLoc,
|
||||
return TypeTraitExpr::Create(Context, Context.getLogicalOperationType(),
|
||||
KWLoc, Kind, Args, RParenLoc, Result);
|
||||
}
|
||||
case TypeTraitReturnType::SizeT: {
|
||||
APValue Result =
|
||||
EvaluateSizeTTypeTrait(*this, Kind, KWLoc, Args, RParenLoc, Dependent);
|
||||
return TypeTraitExpr::Create(Context, Context.getSizeType(), KWLoc, Kind,
|
||||
Args, RParenLoc, Result);
|
||||
}
|
||||
}
|
||||
llvm_unreachable("unhandled type trait return type");
|
||||
}
|
||||
|
@ -2135,9 +2135,15 @@ void ASTStmtReader::VisitUnresolvedLookupExpr(UnresolvedLookupExpr *E) {
|
||||
|
||||
void ASTStmtReader::VisitTypeTraitExpr(TypeTraitExpr *E) {
|
||||
VisitExpr(E);
|
||||
E->TypeTraitExprBits.IsBooleanTypeTrait = Record.readInt();
|
||||
E->TypeTraitExprBits.NumArgs = Record.readInt();
|
||||
E->TypeTraitExprBits.Kind = Record.readInt();
|
||||
E->TypeTraitExprBits.Value = Record.readInt();
|
||||
|
||||
if (E->TypeTraitExprBits.IsBooleanTypeTrait)
|
||||
E->TypeTraitExprBits.Value = Record.readInt();
|
||||
else
|
||||
*E->getTrailingObjects<APValue>() = Record.readAPValue();
|
||||
|
||||
SourceRange Range = readSourceRange();
|
||||
E->Loc = Range.getBegin();
|
||||
E->RParenLoc = Range.getEnd();
|
||||
@ -4298,8 +4304,9 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
|
||||
}
|
||||
|
||||
case EXPR_TYPE_TRAIT:
|
||||
S = TypeTraitExpr::CreateDeserialized(Context,
|
||||
Record[ASTStmtReader::NumExprFields]);
|
||||
S = TypeTraitExpr::CreateDeserialized(
|
||||
Context, Record[ASTStmtReader::NumExprFields],
|
||||
Record[ASTStmtReader::NumExprFields + 1]);
|
||||
break;
|
||||
|
||||
case EXPR_ARRAY_TYPE_TRAIT:
|
||||
|
@ -2140,9 +2140,15 @@ void ASTStmtWriter::VisitUnresolvedLookupExpr(UnresolvedLookupExpr *E) {
|
||||
|
||||
void ASTStmtWriter::VisitTypeTraitExpr(TypeTraitExpr *E) {
|
||||
VisitExpr(E);
|
||||
Record.push_back(E->TypeTraitExprBits.IsBooleanTypeTrait);
|
||||
Record.push_back(E->TypeTraitExprBits.NumArgs);
|
||||
Record.push_back(E->TypeTraitExprBits.Kind); // FIXME: Stable encoding
|
||||
Record.push_back(E->TypeTraitExprBits.Value);
|
||||
|
||||
if (E->TypeTraitExprBits.IsBooleanTypeTrait)
|
||||
Record.push_back(E->TypeTraitExprBits.Value);
|
||||
else
|
||||
Record.AddAPValue(E->getAPValue());
|
||||
|
||||
Record.AddSourceRange(E->getSourceRange());
|
||||
for (unsigned I = 0, N = E->getNumArgs(); I != N; ++I)
|
||||
Record.AddTypeSourceInfo(E->getArg(I));
|
||||
|
@ -368,7 +368,10 @@ std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) {
|
||||
|
||||
case Stmt::TypeTraitExprClass: {
|
||||
const auto *TE = cast<TypeTraitExpr>(E);
|
||||
return makeTruthVal(TE->getValue(), TE->getType());
|
||||
if (TE->isStoredAsBoolean())
|
||||
return makeTruthVal(TE->getBoolValue(), TE->getType());
|
||||
assert(TE->getAPValue().isInt() && "APValue type not supported");
|
||||
return makeIntVal(TE->getAPValue().getInt());
|
||||
}
|
||||
|
||||
case Stmt::IntegerLiteralClass:
|
||||
|
@ -77,3 +77,9 @@ int constexpr_overflow_result() {
|
||||
// CHECK: [[RET_VAL:%.+]] = load i32, ptr [[Z]]
|
||||
// CHECK: ret i32 [[RET_VAL]]
|
||||
}
|
||||
|
||||
int structured_binding_size() {
|
||||
struct S2 {int a, b;};
|
||||
return __builtin_structured_binding_size(S2);
|
||||
// CHECK: ret i32 2
|
||||
}
|
||||
|
@ -1158,6 +1158,12 @@ template void f16<int>(int, __remove_volatile(int));
|
||||
template <typename T> void f17(T, __remove_restrict(T)) {}
|
||||
template void f17<int>(int, __remove_restrict(int));
|
||||
// CHECK-LABEL: @_ZN6test553f17IiEEvT_u17__remove_restrictIS1_E
|
||||
|
||||
struct S{};
|
||||
template <class T> void f18(decltype(__builtin_structured_binding_size(T))) {}
|
||||
template void f18<S>(__SIZE_TYPE__);
|
||||
// CHECK: void @_ZN6test553f18INS_1SEEEvDTu33__builtin_structured_binding_sizeT_EE
|
||||
|
||||
} // namespace test55
|
||||
|
||||
namespace test56 {
|
||||
|
231
clang/test/SemaCXX/builtin-structured-binding-size.cpp
Normal file
231
clang/test/SemaCXX/builtin-structured-binding-size.cpp
Normal file
@ -0,0 +1,231 @@
|
||||
// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -verify
|
||||
// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -verify -fexperimental-new-constant-interpreter
|
||||
|
||||
|
||||
struct S0 {};
|
||||
struct S1 {int a;};
|
||||
struct S2 {int a; int b; static int c;};
|
||||
struct S3 {double a; int b; int c;};
|
||||
struct S4 {int a: 1; int b :2;};
|
||||
struct S5 {int : 1; int b :2;};
|
||||
struct S6 {union {int a;}; }; // #note-anon-union
|
||||
struct S7 {int a[];};
|
||||
|
||||
|
||||
|
||||
struct SD : S1 {};
|
||||
struct SE1 : S1 { int b;};
|
||||
|
||||
class P1 {int a;}; // #note-private
|
||||
|
||||
union U1 {};
|
||||
union U2 {int a;};
|
||||
|
||||
template <typename T>
|
||||
concept is_destructurable = requires {
|
||||
{ __builtin_structured_binding_size(T) };
|
||||
};
|
||||
|
||||
static_assert(__builtin_structured_binding_size(S0) == 0);
|
||||
static_assert(__is_same_as(decltype(__builtin_structured_binding_size(S0)), decltype(sizeof(void*))));
|
||||
|
||||
static_assert(__builtin_structured_binding_size(S1) == 0);
|
||||
// expected-error@-1 {{static assertion failed due to requirement '__builtin_structured_binding_size(S1) == 0'}} \
|
||||
// expected-note@-1 {{expression evaluates to '1 == 0'}}
|
||||
static_assert(__builtin_structured_binding_size(S1) == 1);
|
||||
static_assert(__builtin_structured_binding_size(S2) == 2);
|
||||
static_assert(__builtin_structured_binding_size(S3) == 3);
|
||||
static_assert(__builtin_structured_binding_size(S4) == 2);
|
||||
static_assert(__builtin_structured_binding_size(S5) == 2);
|
||||
// expected-error@-1 {{static assertion failed due to requirement '__builtin_structured_binding_size(S5) == 2'}} \
|
||||
// expected-note@-1 {{expression evaluates to '1 == 2'}}
|
||||
static_assert(__builtin_structured_binding_size(S6) == 2);
|
||||
// expected-error@-1 {{static assertion failed due to requirement '__builtin_structured_binding_size(S6) == 2'}} \
|
||||
// expected-error@-1 {{cannot decompose class type 'S6' because it has an anonymous union member}} \
|
||||
// expected-note@-1 {{expression evaluates to '1 == 2'}}
|
||||
// expected-note@#note-anon-union {{declared here}}
|
||||
static_assert(__builtin_structured_binding_size(S7) == 1);
|
||||
|
||||
|
||||
static_assert(__builtin_structured_binding_size(SD) == 1);
|
||||
static_assert(__builtin_structured_binding_size(SE1) == 1);
|
||||
// expected-error@-1 {{cannot decompose class type 'SE1': both it and its base class 'S1' have non-static data members}} \
|
||||
// expected-error@-1 {{type 'SE1' cannot be decomposed}} \
|
||||
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
|
||||
|
||||
static_assert(__builtin_structured_binding_size(U1) == 0);
|
||||
// expected-error@-1 {{type 'U1' cannot be decomposed}} \
|
||||
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
|
||||
static_assert(__builtin_structured_binding_size(U2) == 0);
|
||||
// expected-error@-1 {{type 'U2' cannot be decomposed}} \
|
||||
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
|
||||
|
||||
|
||||
|
||||
static_assert(__builtin_structured_binding_size(int[0]) == 0);
|
||||
static_assert(__builtin_structured_binding_size(int[1]) == 1);
|
||||
static_assert(__builtin_structured_binding_size(int[42]) == 42);
|
||||
|
||||
using vec2 = int __attribute__((__vector_size__(2 * sizeof(int))));
|
||||
using vec3 = int __attribute__((__vector_size__(3 * sizeof(int))));
|
||||
static_assert(__builtin_structured_binding_size(vec2) == 2);
|
||||
static_assert(__builtin_structured_binding_size(vec3) == 3);
|
||||
static_assert(__builtin_structured_binding_size(decltype(__builtin_complex(0., 0.))) == 2);
|
||||
|
||||
|
||||
int VLASize; // expected-note {{declared here}}
|
||||
static_assert(__builtin_structured_binding_size(int[VLASize]) == 42);
|
||||
// expected-error@-1 {{type 'int[VLASize]' cannot be decomposed}} \
|
||||
// expected-warning@-1 {{variable length arrays in C++ are a Clang extension}} \
|
||||
// expected-note@-1 {{read of non-const variable 'VLASize' is not allowed in a constant expression}} \
|
||||
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
|
||||
|
||||
|
||||
struct Incomplete; // expected-note {{forward declaration of 'Incomplete'}}
|
||||
static_assert(__builtin_structured_binding_size(Incomplete) == 1);
|
||||
// expected-error@-1 {{incomplete type 'Incomplete' where a complete type is required}} \
|
||||
// expected-error@-1 {{type 'Incomplete' cannot be decomposed}} \
|
||||
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
|
||||
static_assert(__builtin_structured_binding_size(Incomplete[]) == 1);
|
||||
// expected-error@-1 {{type 'Incomplete[]' cannot be decomposed}} \
|
||||
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
|
||||
static_assert(__builtin_structured_binding_size(Incomplete[0]) == 0);
|
||||
static_assert(__builtin_structured_binding_size(Incomplete[1]) == 1);
|
||||
static_assert(__builtin_structured_binding_size(Incomplete[42]) == 42);
|
||||
|
||||
|
||||
static_assert(__builtin_structured_binding_size(P1) == 0);
|
||||
// expected-error@-1 {{static assertion failed due to requirement '__builtin_structured_binding_size(P1) == 0'}} \
|
||||
// expected-note@-1 {{expression evaluates to '1 == 0'}} \
|
||||
// expected-error@-1 {{cannot decompose private member 'a' of 'P1}} \
|
||||
// expected-note@#note-private {{implicitly declared private here}}
|
||||
|
||||
|
||||
void func(int array[14], int x = __builtin_structured_binding_size(decltype(array)));
|
||||
//expected-error@-1 {{type 'decltype(array)' (aka 'int *') cannot be decomposed}}
|
||||
|
||||
struct SM {
|
||||
static int array[14];
|
||||
static_assert(__builtin_structured_binding_size(decltype(array)) == 14);
|
||||
};
|
||||
|
||||
template <typename Ty, int N = __builtin_structured_binding_size(Ty)> // #tpl-1
|
||||
struct T {
|
||||
static constexpr int value = N;
|
||||
};
|
||||
|
||||
T<int> t1;
|
||||
// expected-error@#tpl-1 {{type 'int' cannot be decomposed}} \
|
||||
// expected-error@#tpl-1 {{non-type template argument is not a constant expression}} \
|
||||
// expected-note@-1 {{in instantiation of default argument for 'T<int>' required here}} \
|
||||
// expected-note@-1 {{while checking a default template argument used here}} \
|
||||
|
||||
static_assert(T<S3>::value == 3);
|
||||
|
||||
static_assert(is_destructurable<S0>);
|
||||
static_assert(is_destructurable<const S0>);
|
||||
static_assert(is_destructurable<volatile S0>);
|
||||
static_assert(!is_destructurable<S0&>);
|
||||
static_assert(is_destructurable<S1>);
|
||||
static_assert(!is_destructurable<S1&>);
|
||||
static_assert(!is_destructurable<SE1>);
|
||||
static_assert(!is_destructurable<int>);
|
||||
static_assert(!is_destructurable<int[]>);
|
||||
static_assert(is_destructurable<int[1]>);
|
||||
static_assert(!is_destructurable<P1>);
|
||||
|
||||
template <typename T>
|
||||
constexpr int f() {return 0;};
|
||||
template <typename T>
|
||||
requires is_destructurable<T>
|
||||
constexpr int f() {return 1;};
|
||||
|
||||
static_assert(f<int>() == 0);
|
||||
static_assert(f<S0>() == 1);
|
||||
|
||||
struct T0;
|
||||
struct T1;
|
||||
struct T42;
|
||||
struct TSizeError;
|
||||
|
||||
namespace std {
|
||||
|
||||
template <typename>
|
||||
struct tuple_size;
|
||||
|
||||
template <>
|
||||
struct tuple_size<T0> {
|
||||
static constexpr int value = 0;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct tuple_size<T1> {
|
||||
static constexpr int value = 1;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct tuple_size<T42> {
|
||||
static constexpr int value = 42;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct tuple_size<TSizeError> {
|
||||
static constexpr void* value = nullptr;
|
||||
};
|
||||
|
||||
static_assert(__builtin_structured_binding_size(T0) == 0);
|
||||
|
||||
static_assert(is_destructurable<const T0>);
|
||||
static_assert(is_destructurable<volatile T0>);
|
||||
static_assert(!is_destructurable<T0&>);
|
||||
|
||||
|
||||
static_assert(__builtin_structured_binding_size(T1) == 1);
|
||||
static_assert(__builtin_structured_binding_size(T42) == 42);
|
||||
static_assert(__builtin_structured_binding_size(TSizeError) == 42);
|
||||
// expected-error@-1 {{cannot decompose this type; 'std::tuple_size<TSizeError>::value' is not a valid integral constant expression}} \
|
||||
// expected-error@-1 {{type 'TSizeError' cannot be decomposed}} \
|
||||
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
|
||||
static_assert(!is_destructurable<TSizeError>);
|
||||
}
|
||||
|
||||
|
||||
struct S {
|
||||
int x;
|
||||
int y;
|
||||
static_assert(__builtin_structured_binding_size(S) == 2);
|
||||
//expected-error@-1 {{incomplete type 'S' where a complete type is required}} \
|
||||
// expected-error@-1 {{type 'S' cannot be decomposed}} \
|
||||
// expected-error@-1 {{static assertion expression is not an integral constant expression}} \
|
||||
// expected-note@-4 {{definition of 'S' is not complete until the closing '}'}}
|
||||
};
|
||||
|
||||
// Check we can implement std::exec::tag_of_t
|
||||
template <typename T>
|
||||
struct type_identity {
|
||||
using type = T;
|
||||
};
|
||||
template<typename T> T &&declval();
|
||||
|
||||
template <typename T>
|
||||
requires (__builtin_structured_binding_size(T) >=2)
|
||||
consteval auto tag_of_impl(T& t) {
|
||||
auto && [tag, ..._] = t;
|
||||
return type_identity<decltype(auto(tag))>{};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires (__builtin_structured_binding_size(T) >=2) // #tag-of-constr
|
||||
using tag_of_t = decltype(tag_of_impl(declval<T&>()))::type;
|
||||
|
||||
static_assert(__is_same_as(tag_of_t<S2>, int));
|
||||
static_assert(__is_same_as(tag_of_t<S3>, double));
|
||||
|
||||
|
||||
static_assert(__is_same_as(tag_of_t<S1>, int));
|
||||
// expected-error@-1 {{constraints not satisfied for alias template 'tag_of_t' [with T = S1]}} \
|
||||
// expected-note@#tag-of-constr {{because '__builtin_structured_binding_size(S1) >= 2' (1 >= 2) evaluated to false}}
|
||||
|
||||
static_assert(__is_same_as(tag_of_t<int>, int)); // error
|
||||
// expected-error@-1 {{constraints not satisfied for alias template 'tag_of_t' [with T = int]}}
|
||||
// expected-note@#tag-of-constr {{because substituted constraint expression is ill-formed: type 'int' cannot be decomposed}}
|
Loading…
x
Reference in New Issue
Block a user