https://github.com/snarang181 updated https://github.com/llvm/llvm-project/pull/143722
>From 6f787e1997f53968e23586b4eb03c45e6559ebbf Mon Sep 17 00:00:00 2001 From: Samarth Narang <snar...@umass.edu> Date: Mon, 2 Jun 2025 19:30:39 -0400 Subject: [PATCH 1/2] Add std layout diagnostics Add diagnostic test cases --- .../clang/Basic/DiagnosticSemaKinds.td | 9 +- clang/lib/Sema/SemaTypeTraits.cpp | 177 +++++++++++++++++- .../type-traits-unsatisfied-diags-std.cpp | 98 ++++++++++ .../SemaCXX/type-traits-unsatisfied-diags.cpp | 142 ++++++++++++++ 4 files changed, 420 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 8fe7ad6138aa0..f76f061196416 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1767,7 +1767,9 @@ def note_unsatisfied_trait : Note<"%0 is not %enum_select<TraitName>{" "%TriviallyRelocatable{trivially relocatable}|" "%Replaceable{replaceable}|" - "%TriviallyCopyable{trivially copyable}" + "%TriviallyCopyable{trivially copyable}|" + "%Constructible{constructible with provided types}|" + "%StandardLayout{standard-layout}" "}1">; def note_unsatisfied_trait_reason @@ -1787,6 +1789,11 @@ def note_unsatisfied_trait_reason "%NonReplaceableField{has a non-replaceable member %1 of type %2}|" "%NTCBase{has a non-trivially-copyable base %1}|" "%NTCField{has a non-trivially-copyable member %1 of type %2}|" + "%NonStdLayoutBase{has a non-standard-layout base %1}|" + "%MixedAccess{has mixed access specifiers}|" + "%MultipleDataBase{has multiple base classes with data members}|" + "%VirtualFunction{has virtual functions}|" + "%NonStdLayoutMember{has a non-standard-layout member %1 of type %2}|" "%DeletedDtr{has a %select{deleted|user-provided}1 destructor}|" "%UserProvidedCtr{has a user provided %select{copy|move}1 " "constructor}|" diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp index 1738ab4466001..3b216df8ad3f3 100644 --- a/clang/lib/Sema/SemaTypeTraits.cpp +++ b/clang/lib/Sema/SemaTypeTraits.cpp @@ -1947,6 +1947,8 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) { TypeTrait::UTT_IsCppTriviallyRelocatable) .Case("is_replaceable", TypeTrait::UTT_IsReplaceable) .Case("is_trivially_copyable", TypeTrait::UTT_IsTriviallyCopyable) + .Case("is_constructible", TypeTrait::TT_IsConstructible) + .Case("is_standard_layout", TypeTrait::UTT_IsStandardLayout) .Default(std::nullopt); } @@ -2257,21 +2259,180 @@ static void DiagnoseNonTriviallyCopyableReason(Sema &SemaRef, } } -static void DiagnoseNonTriviallyCopyableReason(Sema &SemaRef, - SourceLocation Loc, QualType T) { +static void DiagnoseNonConstructibleReason( + Sema &SemaRef, SourceLocation Loc, + const llvm::SmallVector<clang::QualType, 1> &Ts) { + if (Ts.empty()) { + return; + } + + bool ContainsVoid = false; + for (const QualType &ArgTy : Ts) { + ContainsVoid |= ArgTy->isVoidType(); + } + + if (ContainsVoid) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::CVVoidType; + + QualType T = Ts[0]; + if (T->isFunctionType()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::FunctionType; + + if (T->isIncompleteArrayType()) + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::IncompleteArrayType; + + const CXXRecordDecl *D = T->getAsCXXRecordDecl(); + if (!D || D->isInvalidDecl() || !D->hasDefinition()) + return; + + llvm::BumpPtrAllocator OpaqueExprAllocator; + SmallVector<Expr *, 2> ArgExprs; + ArgExprs.reserve(Ts.size() - 1); + for (unsigned I = 1, N = Ts.size(); I != N; ++I) { + QualType ArgTy = Ts[I]; + if (ArgTy->isObjectType() || ArgTy->isFunctionType()) + ArgTy = SemaRef.Context.getRValueReferenceType(ArgTy); + ArgExprs.push_back( + new (OpaqueExprAllocator.Allocate<OpaqueValueExpr>()) + OpaqueValueExpr(Loc, ArgTy.getNonLValueExprType(SemaRef.Context), + Expr::getValueKindForType(ArgTy))); + } + + EnterExpressionEvaluationContext Unevaluated( + SemaRef, Sema::ExpressionEvaluationContext::Unevaluated); + Sema::ContextRAII TUContext(SemaRef, + SemaRef.Context.getTranslationUnitDecl()); + InitializedEntity To(InitializedEntity::InitializeTemporary(T)); + InitializationKind InitKind(InitializationKind::CreateDirect(Loc, Loc, Loc)); + InitializationSequence Init(SemaRef, To, InitKind, ArgExprs); + + Init.Diagnose(SemaRef, To, InitKind, ArgExprs); + SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D; +} + +static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc, + QualType T) { SemaRef.Diag(Loc, diag::note_unsatisfied_trait) - << T << diag::TraitName::TriviallyCopyable; + << T << diag::TraitName::StandardLayout; - if (T->isReferenceType()) + // Check type-level exclusion first + if (T->isVariablyModifiedType()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::VLA; + return; + } + + if (T->isReferenceType()) { SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) << diag::TraitNotSatisfiedReason::Ref; + return; + } + T = T.getNonReferenceType(); + const CXXRecordDecl *D = T->getAsCXXRecordDecl(); + if (!D || D->isInvalidDecl()) + return; + + if (D->hasDefinition()) + DiagnoseNonStandardLayoutReason(SemaRef, Loc, D); + + SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D; +} + +static bool hasMixedAccessSpecifier(const CXXRecordDecl *D) { + AccessSpecifier FirstAccess = AS_none; + for (const FieldDecl *Field : D->fields()) { + if (Field->isUnnamedBitField()) + continue; + AccessSpecifier FieldAccess = Field->getAccess(); + if (FirstAccess == AS_none) { + FirstAccess = FieldAccess; + } else if (FieldAccess != FirstAccess) { + return true; + } + } + return false; +} +static bool hasMultipleDataBaseClassesWithFields(const CXXRecordDecl *D) { + int NumBasesWithFields = 0; + for (const CXXBaseSpecifier &Base : D->bases()) { + const CXXRecordDecl *BaseRD = Base.getType()->getAsCXXRecordDecl(); + if (!BaseRD || BaseRD->isInvalidDecl()) + continue; + + for (const FieldDecl *Field : BaseRD->fields()) { + if (!Field->isUnnamedBitField()) { + ++NumBasesWithFields; + break; // Only count the base once. + } + } + } + return NumBasesWithFields > 1; +} + +static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc, + const CXXRecordDecl *D) { + for (const CXXBaseSpecifier &B : D->bases()) { + assert(B.getType()->getAsCXXRecordDecl() && "invalid base?"); + if (B.isVirtual()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::VBase << B.getType() + << B.getSourceRange(); + } + if (!B.getType()->isStandardLayoutType()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::NonStdLayoutBase << B.getType() + << B.getSourceRange(); + } + } + if (hasMixedAccessSpecifier(D)) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::MixedAccess; + } + if (hasMultipleDataBaseClassesWithFields(D)) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::MultipleDataBase; + } + if (D->isPolymorphic()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::VirtualFunction; + } + for (const FieldDecl *Field : D->fields()) { + if (!Field->getType()->isStandardLayoutType()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::NonStdLayoutMember << Field + << Field->getType() << Field->getSourceRange(); + } + } +} + +static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc, + QualType T) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait) + << T << diag::TraitName::StandardLayout; + + // Check type-level exclusion first + if (T->isVariablyModifiedType()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::VLA; + return; + } + + if (T->isReferenceType()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::Ref; + return; + } + T = T.getNonReferenceType(); const CXXRecordDecl *D = T->getAsCXXRecordDecl(); if (!D || D->isInvalidDecl()) return; if (D->hasDefinition()) - DiagnoseNonTriviallyCopyableReason(SemaRef, Loc, D); + DiagnoseNonStandardLayoutReason(SemaRef, Loc, D); SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D; } @@ -2296,6 +2457,12 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) { case UTT_IsTriviallyCopyable: DiagnoseNonTriviallyCopyableReason(*this, E->getBeginLoc(), Args[0]); break; + case TT_IsConstructible: + DiagnoseNonConstructibleReason(*this, E->getBeginLoc(), Args); + break; + case UTT_IsStandardLayout: + DiagnoseNonStandardLayoutReason(*this, E->getBeginLoc(), Args[0]); + break; default: break; } diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp index 329b611110c1d..0f223f372418a 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp @@ -20,6 +20,21 @@ struct is_trivially_copyable { template <typename T> constexpr bool is_trivially_copyable_v = __is_trivially_copyable(T); + +template <typename... Args> +struct is_constructible { + static constexpr bool value = __is_constructible(Args...); +}; + +template <typename... Args> +constexpr bool is_constructible_v = __is_constructible(Args...); + +template <typename T> +struct is_standard_layout { + static constexpr bool value = __is_standard_layout(T); +}; +template <typename T> +constexpr bool is_standard_layout_v = __is_standard_layout(T); #endif #ifdef STD2 @@ -44,6 +59,26 @@ using is_trivially_copyable = __details_is_trivially_copyable<T>; template <typename T> constexpr bool is_trivially_copyable_v = __is_trivially_copyable(T); + +template <typename... Args> +struct __details_is_constructible{ + static constexpr bool value = __is_constructible(Args...); +}; + +template <typename... Args> +using is_constructible = __details_is_constructible<Args...>; + +template <typename... Args> +constexpr bool is_constructible_v = __is_constructible(Args...); + +template <typename T> +struct __details_is_standard_layout { + static constexpr bool value = __is_standard_layout(T); +}; +template <typename T> +using is_standard_layout = __details_is_standard_layout<T>; +template <typename T> +constexpr bool is_standard_layout_v = __is_standard_layout(T); #endif @@ -73,6 +108,24 @@ using is_trivially_copyable = __details_is_trivially_copyable<T>; template <typename T> constexpr bool is_trivially_copyable_v = is_trivially_copyable<T>::value; + +template <typename... Args> +struct __details_is_constructible : bool_constant<__is_constructible(Args...)> {}; + +template <typename... Args> +using is_constructible = __details_is_constructible<Args...>; + +template <typename... Args> +constexpr bool is_constructible_v = is_constructible<Args...>::value; + + +template <typename T> +struct __details_is_standard_layout : bool_constant<__is_standard_layout(T)> {}; +template <typename T> +using is_standard_layout = __details_is_standard_layout<T>; +template <typename T> +constexpr bool is_standard_layout_v = is_standard_layout<T>::value; + #endif } @@ -100,6 +153,30 @@ static_assert(std::is_trivially_copyable_v<int&>); // expected-note@-1 {{because it is a reference type}} +// Direct tests +static_assert(std::is_standard_layout<int>::value); +static_assert(std::is_standard_layout_v<int>); + +static_assert(std::is_standard_layout<int&>::value); +// expected-error-re@-1 {{static assertion failed due to requirement 'std::{{.*}}is_standard_layout<int &>::value'}} \ +// expected-note@-1 {{'int &' is not standard-layout}} \ +// expected-note@-1 {{because it is a reference type}} + +static_assert(std::is_standard_layout_v<int&>); +// expected-error@-1 {{static assertion failed due to requirement 'std::is_standard_layout_v<int &>'}} \ +// expected-note@-1 {{'int &' is not standard-layout}} \ +// expected-note@-1 {{because it is a reference type}} + + +static_assert(std::is_constructible<int, int>::value); + +static_assert(std::is_constructible<void>::value); +// expected-error-re@-1 {{static assertion failed due to requirement 'std::{{.*}}is_constructible<void>::value'}} \ +// expected-note@-1 {{because it is a cv void type}} +static_assert(std::is_constructible_v<void>); +// expected-error@-1 {{static assertion failed due to requirement 'std::is_constructible_v<void>'}} \ +// expected-note@-1 {{because it is a cv void type}} + namespace test_namespace { using namespace std; static_assert(is_trivially_relocatable<int&>::value); @@ -119,9 +196,28 @@ namespace test_namespace { // expected-error@-1 {{static assertion failed due to requirement 'is_trivially_copyable_v<int &>'}} \ // expected-note@-1 {{'int &' is not trivially copyable}} \ // expected-note@-1 {{because it is a reference type}} + + static_assert(is_constructible<void>::value); + // expected-error-re@-1 {{static assertion failed due to requirement '{{.*}}is_constructible<void>::value'}} \ + // expected-note@-1 {{because it is a cv void type}} + static_assert(is_constructible_v<void>); + // expected-error@-1 {{static assertion failed due to requirement 'is_constructible_v<void>'}} \ + // expected-note@-1 {{because it is a cv void type}} + + static_assert(is_standard_layout<int&>::value); + // expected-error-re@-1 {{static assertion failed due to requirement '{{.*}}is_standard_layout<int &>::value'}} \ + // expected-note@-1 {{'int &' is not standard-layout}} \ + // expected-note@-1 {{because it is a reference type}} + + static_assert(is_standard_layout_v<int&>); + // expected-error@-1 {{static assertion failed due to requirement 'is_standard_layout_v<int &>'}} \ + // expected-note@-1 {{'int &' is not standard-layout}} \ + // expected-note@-1 {{because it is a reference type}} + } + namespace concepts { template <typename T> requires std::is_trivially_relocatable<T>::value void f(); // #cand1 @@ -192,3 +288,5 @@ static_assert(std::is_replaceable_v<int&>); // expected-error@-1 {{static assertion failed due to requirement 'std::is_replaceable_v<int &>'}} \ // expected-note@-1 {{'int &' is not replaceable}} \ // expected-note@-1 {{because it is a reference type}} + + diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp index a8c78f6304ca9..d3f9b64fa2bfc 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp @@ -488,3 +488,145 @@ static_assert(__is_trivially_copyable(S12)); // expected-note@-1 {{'S12' is not trivially copyable}} \ // expected-note@#tc-S12 {{'S12' defined here}} } + +namespace constructible { + +struct S1 { // #c-S1 + S1(int); // #cc-S1 +}; +static_assert(__is_constructible(S1, char*)); +// expected-error@-1 {{static assertion failed due to requirement '__is_constructible(constructible::S1, char *)'}} \ +// expected-error@-1 {{no matching constructor for initialization of 'S1'}} \ +// expected-note@#c-S1 {{candidate constructor (the implicit copy constructor) not viable: no known conversion from 'char *' to 'const S1' for 1st argument}} \ +// expected-note@#c-S1 {{candidate constructor (the implicit move constructor) not viable: no known conversion from 'char *' to 'S1' for 1st argument}} \ +// expected-note@#cc-S1 {{candidate constructor not viable: no known conversion from 'char *' to 'int' for 1st argument; dereference the argument with *}} \ +// expected-note@#c-S1 {{'S1' defined here}} + +struct S2 { // #c-S2 + S2(int, float, double); // #cc-S2 +}; +static_assert(__is_constructible(S2, float)); +// expected-error@-1 {{static assertion failed due to requirement '__is_constructible(constructible::S2, float)'}} \ +// expected-note@#c-S2 {{candidate constructor (the implicit copy constructor) not viable: no known conversion from 'float' to 'const S2' for 1st argument}} \ +// expected-note@#c-S2 {{candidate constructor (the implicit move constructor) not viable: no known conversion from 'float' to 'S2' for 1st argument}} \ +// expected-error@-1 {{no matching constructor for initialization of 'S2'}} \ +// expected-note@#cc-S2 {{candidate constructor not viable: requires 3 arguments, but 1 was provided}} \ +// expected-note@#c-S2 {{'S2' defined here}} + +static_assert(__is_constructible(S2, float, void)); +// expected-error@-1 {{static assertion failed due to requirement '__is_constructible(constructible::S2, float, void)'}} \ +// expected-note@#c-S2 {{candidate constructor (the implicit move constructor) not viable: requires 1 argument, but 2 were provided}} \ +// expected-note@#c-S2 {{candidate constructor (the implicit copy constructor) not viable: requires 1 argument, but 2 were provided}} \ +// expected-note@-1{{because it is a cv void type}} \ +// expected-error@-1 {{no matching constructor for initialization of 'S2'}} \ +// expected-note@#cc-S2 {{candidate constructor not viable: requires 3 arguments, but 2 were provided}} \ +// expected-note@#c-S2 {{'S2' defined here}} + +static_assert(__is_constructible(int[])); +// expected-error@-1 {{static assertion failed due to requirement '__is_constructible(int[])'}} \ +// expected-note@-1 {{because it is an incomplete array type}} + +static_assert(__is_constructible(void)); +// expected-error@-1 {{static assertion failed due to requirement '__is_constructible(void)'}} \ +// expected-note@-1 {{because it is a cv void type}} + +static_assert(__is_constructible(void, void)); +// expected-error@-1 {{static assertion failed due to requirement '__is_constructible(void, void)'}} \ +// expected-note@-1 {{because it is a cv void type}} + +static_assert(__is_constructible(const void)); +// expected-error@-1 {{static assertion failed due to requirement '__is_constructible(const void)'}} \ +// expected-note@-1 {{because it is a cv void type}} + +static_assert(__is_constructible(volatile void)); +// expected-error@-1 {{static assertion failed due to requirement '__is_constructible(volatile void)'}} \ +// expected-note@-1 {{because it is a cv void type}} + +static_assert(__is_constructible(int ())); +// expected-error@-1 {{static assertion failed due to requirement '__is_constructible(int ())'}} \ +// expected-note@-1 {{because it is a function type}} + +static_assert(__is_constructible(void (int, float))); +// expected-error@-1 {{static assertion failed due to requirement '__is_constructible(void (int, float))'}} \ +// expected-note@-1 {{because it is a function type}} +} + +namespace standard_layout_tests { + struct WithVirtual { // #sl-Virtual + virtual void foo(); +}; +static_assert(__is_standard_layout(WithVirtual)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::WithVirtual)'}} \ +// expected-note@-1 {{'WithVirtual' is not standard-layout}} \ +// expected-note@-1 {{because it has virtual functions}} \ +// expected-note@#sl-Virtual {{'WithVirtual' defined here}} + +struct MixedAccess { // #sl-Mixed +public: + int a; +private: + int b; +}; +static_assert(__is_standard_layout(MixedAccess)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::MixedAccess)'}} \ +// expected-note@-1 {{'MixedAccess' is not standard-layout}} \ +// expected-note@-1 {{because it has mixed access specifiers}} \ +// expected-note@#sl-Mixed {{'MixedAccess' defined here}} + +struct VirtualBase { virtual ~VirtualBase(); }; // #sl-VirtualBase +struct VB : virtual VirtualBase {}; // #sl-VB +static_assert(__is_standard_layout(VB)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::VB)'}} \ +// expected-note@-1 {{'VB' is not standard-layout}} \ +// expected-note@-1 {{because it has a virtual base 'VirtualBase'}} \ +// expected-note@-1 {{because it has a non-standard-layout base 'VirtualBase'}} \ +// expected-note@-1 {{because it has virtual functions}} +// expected-note@#sl-VB {{'VB' defined here}} + +union U { // #sl-U +public: + int x; +private: + int y; +}; +static_assert(__is_standard_layout(U)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::U)'}} \ +// expected-note@-1 {{'U' is not standard-layout}} \ +// expected-note@-1 {{because it has mixed access specifiers}} +// expected-note@#sl-U {{'U' defined here}} + +// Single base class is OK +struct BaseClass{ int a; }; // #sl-BaseClass +struct DerivedOK : BaseClass {}; // #sl-DerivedOK +static_assert(__is_standard_layout(DerivedOK)); + +// Primitive types should be standard layout +static_assert(__is_standard_layout(int)); // #sl-Int +static_assert(__is_standard_layout(float)); // #sl-Float + +// Multi-level inheritance: Non-standard layout +struct Base1 { int a; }; // #sl-Base1 +struct Base2 { int b; }; // #sl-Base2 +struct DerivedClass : Base1, Base2 {}; // #sl-DerivedClass +static_assert(__is_standard_layout(DerivedClass)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::DerivedClass)'}} \ +// expected-note@-1 {{'DerivedClass' is not standard-layout}} \ +// expected-note@-1 {{because it has multiple base classes with data members}} \ +// expected-note@#sl-DerivedClass {{'DerivedClass' defined here}} + +// Inheritance hierarchy with multiple classes having data members +struct BaseA { int a; }; // #sl-BaseA +struct BaseB : BaseA {}; // inherits BaseA, has no new members +struct BaseC: BaseB { int c; }; // #sl-BaseC +static_assert(__is_standard_layout(BaseC)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::BaseC)'}} \ +// expected-note@-1 {{'BaseC' is not standard-layout}} \ +// expected-note@#sl-BaseC {{'BaseC' defined here}} \ + +// Multiple direct base classes with no data members --> standard layout +struct BaseX {}; // #sl-BaseX +struct BaseY {}; // #sl-BaseY +struct MultiBase : BaseX, BaseY {}; // #sl-MultiBase +static_assert(__is_standard_layout(MultiBase)); + +} >From 40ac4232a5a799e08c034afdecc3120741f3bdfa Mon Sep 17 00:00:00 2001 From: Samarth Narang <snar...@umass.edu> Date: Thu, 12 Jun 2025 22:48:48 -0400 Subject: [PATCH 2/2] Add more descriptive messages for indirect base class inheritance --- .../clang/Basic/DiagnosticSemaKinds.td | 1 + clang/lib/Sema/SemaTypeTraits.cpp | 31 +++++++++++++++++++ .../type-traits-unsatisfied-diags-std.cpp | 3 -- .../SemaCXX/type-traits-unsatisfied-diags.cpp | 1 + 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index f76f061196416..f12cba7450af0 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1794,6 +1794,7 @@ def note_unsatisfied_trait_reason "%MultipleDataBase{has multiple base classes with data members}|" "%VirtualFunction{has virtual functions}|" "%NonStdLayoutMember{has a non-standard-layout member %1 of type %2}|" + "%IndirectBaseWithFields{has an indirect base %1 with data members}|" "%DeletedDtr{has a %select{deleted|user-provided}1 destructor}|" "%UserProvidedCtr{has a user provided %select{copy|move}1 " "constructor}|" diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp index 3b216df8ad3f3..2fdbf4e2b5079 100644 --- a/clang/lib/Sema/SemaTypeTraits.cpp +++ b/clang/lib/Sema/SemaTypeTraits.cpp @@ -2407,6 +2407,37 @@ static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc, << Field->getType() << Field->getSourceRange(); } } + + // if this class and an indirect base + // both have non-static data members, grab the first such base. + if (D->hasDirectFields()) { + SmallVector<const CXXRecordDecl *, 4> Records; + + // Recursive lambda to collect all bases that declare fields + std::function<void(const CXXRecordDecl *)> collect = + [&](const CXXRecordDecl *R) { + for (const CXXBaseSpecifier &B : R->bases()) { + const auto *BR = B.getType()->getAsCXXRecordDecl(); + if (!BR || !BR->hasDefinition()) + continue; + if (BR->hasDirectFields()) + Records.push_back(BR); + // Recurse into the base class. + collect(BR); + } + }; + + // Collect all bases that declare fields. + collect(D); + + // If more than one record has fields, then the layout is non-standard. + if (!Records.empty()) { + const CXXRecordDecl *Indirect = Records.front(); + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::IndirectBaseWithFields << Indirect + << Indirect->getSourceRange(); + } + } } static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc, diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp index 0f223f372418a..626a0d4fa62b4 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp @@ -217,7 +217,6 @@ namespace test_namespace { } - namespace concepts { template <typename T> requires std::is_trivially_relocatable<T>::value void f(); // #cand1 @@ -288,5 +287,3 @@ static_assert(std::is_replaceable_v<int&>); // expected-error@-1 {{static assertion failed due to requirement 'std::is_replaceable_v<int &>'}} \ // expected-note@-1 {{'int &' is not replaceable}} \ // expected-note@-1 {{because it is a reference type}} - - diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp index d3f9b64fa2bfc..9a934187a13ee 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp @@ -621,6 +621,7 @@ struct BaseC: BaseB { int c; }; // #sl-BaseC static_assert(__is_standard_layout(BaseC)); // expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::BaseC)'}} \ // expected-note@-1 {{'BaseC' is not standard-layout}} \ +// expected-note@-1 {{because it has an indirect base 'BaseA' with data members}} \ // expected-note@#sl-BaseC {{'BaseC' defined here}} \ // Multiple direct base classes with no data members --> standard layout _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits