Author: cor3ntin Date: 2025-03-18T20:50:56+01:00 New Revision: bc8b19c7575f3882b7655e129d4fc3b74c7fbba3
URL: https://github.com/llvm/llvm-project/commit/bc8b19c7575f3882b7655e129d4fc3b74c7fbba3 DIFF: https://github.com/llvm/llvm-project/commit/bc8b19c7575f3882b7655e129d4fc3b74c7fbba3.diff LOG: [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 Added: clang/test/SemaCXX/builtin-structured-binding-size.cpp Modified: clang/docs/LanguageExtensions.rst clang/docs/ReleaseNotes.rst clang/include/clang/AST/ExprCXX.h clang/include/clang/AST/Stmt.h clang/include/clang/Basic/DiagnosticSemaKinds.td clang/include/clang/Basic/TokenKinds.def clang/include/clang/Sema/Sema.h clang/lib/AST/ASTImporter.cpp clang/lib/AST/ByteCode/Compiler.cpp clang/lib/AST/ExprCXX.cpp clang/lib/AST/ExprConstant.cpp clang/lib/CodeGen/CGExprScalar.cpp clang/lib/Sema/SemaDeclCXX.cpp clang/lib/Sema/SemaExprCXX.cpp clang/lib/Serialization/ASTReaderStmt.cpp clang/lib/Serialization/ASTWriterStmt.cpp clang/lib/StaticAnalyzer/Core/SValBuilder.cpp clang/test/CodeGenCXX/builtins.cpp clang/test/CodeGenCXX/mangle.cpp Removed: ################################################################################ diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index 20203dcfc1c2d..d4771775c9739 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -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 ====== diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 0adbc19f40096..ce4336acb806a 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -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. diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index abc65e77da021..724ed437f1075 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -51,6 +51,7 @@ #include <cstdint> #include <memory> #include <optional> +#include <variant> namespace clang { @@ -2765,20 +2766,16 @@ class CXXPseudoDestructorExpr : public Expr { /// \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 @@ class TypeTraitExpr final 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 @@ class TypeTraitExpr final 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; } diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index e779f94d16b94..fa4abdb489203 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -954,11 +954,13 @@ class alignas(void *) Stmt { 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 Value : 1; + 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. diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index d65d1a8ef2cee..e156ad03e7640 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -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: " diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 397a5d95709fb..1bf9f43f80986 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -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) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 505889e82d811..9561c59b1facf 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -6116,7 +6116,8 @@ class Sema final : public SemaBase { 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 diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index 3c15d7d9dcb52..514887a4dccc1 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -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) { diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index e76d93728e0db..1541d6e699a23 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -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> diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp index c8d61e2cf3f26..c2cf4ffe506c6 100644 --- a/clang/lib/AST/ExprCXX.cpp +++ b/clang/lib/AST/ExprCXX.cpp @@ -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()); } diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 7803b1026aab9..022a20181879e 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -12102,7 +12102,12 @@ class IntExprEvaluator } 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) { diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index 6646057b9d772..eccdcdb497f84 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -724,7 +724,12 @@ class ScalarExprEmitter } 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) { diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 20533961a2217..bd6321c46a78f 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -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) diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 34219e0235a74..e34f6e272cebd 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -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"); } diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 1e13bcfe60b47..8dceca6ff3dbf 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -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: diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 32211ced4ff15..037eb13b1dc52 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -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)); diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index 4f45b24be86c1..eb5054708fece 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -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: diff --git a/clang/test/CodeGenCXX/builtins.cpp b/clang/test/CodeGenCXX/builtins.cpp index 37f9491d12d04..9169f3a3276d3 100644 --- a/clang/test/CodeGenCXX/builtins.cpp +++ b/clang/test/CodeGenCXX/builtins.cpp @@ -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 +} diff --git a/clang/test/CodeGenCXX/mangle.cpp b/clang/test/CodeGenCXX/mangle.cpp index c5b472670e8c0..cf506aff92f0e 100644 --- a/clang/test/CodeGenCXX/mangle.cpp +++ b/clang/test/CodeGenCXX/mangle.cpp @@ -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 { diff --git a/clang/test/SemaCXX/builtin-structured-binding-size.cpp b/clang/test/SemaCXX/builtin-structured-binding-size.cpp new file mode 100644 index 0000000000000..85b1d81e08e33 --- /dev/null +++ b/clang/test/SemaCXX/builtin-structured-binding-size.cpp @@ -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}} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits