[clang] bc8b19c - [Clang] Introduce a trait to determine the structure binding size (#131515)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 18 12:50:59 PDT 2025
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 at -1 {{static assertion failed due to requirement '__builtin_structured_binding_size(S1) == 0'}} \
+// expected-note at -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 at -1 {{static assertion failed due to requirement '__builtin_structured_binding_size(S5) == 2'}} \
+// expected-note at -1 {{expression evaluates to '1 == 2'}}
+static_assert(__builtin_structured_binding_size(S6) == 2);
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_structured_binding_size(S6) == 2'}} \
+// expected-error at -1 {{cannot decompose class type 'S6' because it has an anonymous union member}} \
+// expected-note at -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 at -1 {{cannot decompose class type 'SE1': both it and its base class 'S1' have non-static data members}} \
+// expected-error at -1 {{type 'SE1' cannot be decomposed}} \
+// expected-error at -1 {{static assertion expression is not an integral constant expression}}
+
+static_assert(__builtin_structured_binding_size(U1) == 0);
+// expected-error at -1 {{type 'U1' cannot be decomposed}} \
+// expected-error at -1 {{static assertion expression is not an integral constant expression}}
+static_assert(__builtin_structured_binding_size(U2) == 0);
+// expected-error at -1 {{type 'U2' cannot be decomposed}} \
+// expected-error at -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 at -1 {{type 'int[VLASize]' cannot be decomposed}} \
+// expected-warning at -1 {{variable length arrays in C++ are a Clang extension}} \
+// expected-note at -1 {{read of non-const variable 'VLASize' is not allowed in a constant expression}} \
+// expected-error at -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 at -1 {{incomplete type 'Incomplete' where a complete type is required}} \
+// expected-error at -1 {{type 'Incomplete' cannot be decomposed}} \
+// expected-error at -1 {{static assertion expression is not an integral constant expression}}
+static_assert(__builtin_structured_binding_size(Incomplete[]) == 1);
+// expected-error at -1 {{type 'Incomplete[]' cannot be decomposed}} \
+// expected-error at -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 at -1 {{static assertion failed due to requirement '__builtin_structured_binding_size(P1) == 0'}} \
+// expected-note at -1 {{expression evaluates to '1 == 0'}} \
+// expected-error at -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 at -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 at -1 {{in instantiation of default argument for 'T<int>' required here}} \
+// expected-note at -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 at -1 {{cannot decompose this type; 'std::tuple_size<TSizeError>::value' is not a valid integral constant expression}} \
+// expected-error at -1 {{type 'TSizeError' cannot be decomposed}} \
+// expected-error at -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 at -1 {{incomplete type 'S' where a complete type is required}} \
+ // expected-error at -1 {{type 'S' cannot be decomposed}} \
+ // expected-error at -1 {{static assertion expression is not an integral constant expression}} \
+ // expected-note at -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 at -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 at -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}}
More information about the cfe-commits
mailing list