Skip to content

Commit 477d38c

Browse files
snarang181Anthony Tran
authored andcommitted
[Clang] Implement diagnostics for why std::is_standard_layout is false (llvm#144161)
1 parent 89f7202 commit 477d38c

File tree

4 files changed

+339
-1
lines changed

4 files changed

+339
-1
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1768,7 +1768,8 @@ def note_unsatisfied_trait
17681768
"%TriviallyRelocatable{trivially relocatable}|"
17691769
"%Replaceable{replaceable}|"
17701770
"%TriviallyCopyable{trivially copyable}|"
1771-
"%Empty{empty}"
1771+
"%Empty{empty}|"
1772+
"%StandardLayout{standard-layout}"
17721773
"}1">;
17731774

17741775
def note_unsatisfied_trait_reason
@@ -1792,6 +1793,12 @@ def note_unsatisfied_trait_reason
17921793
"%VirtualFunction{has a virtual function %1}|"
17931794
"%NonEmptyBase{has a base class %1 that is not empty}|"
17941795
"%NonZeroLengthField{field %1 is a non-zero-length bit-field}|"
1796+
"%NonStandardLayoutBase{has a non-standard-layout base %1}|"
1797+
"%MixedAccess{has mixed access specifiers}|"
1798+
"%MixedAccessField{field %1 has a different access specifier than field %2}|"
1799+
"%MultipleDataBase{has multiple base classes with data members}|"
1800+
"%NonStandardLayoutMember{has a non-standard-layout member %1 of type %2}|"
1801+
"%IndirectBaseWithFields{has an indirect base %1 with data members}|"
17951802
"%DeletedDtr{has a %select{deleted|user-provided}1 destructor}|"
17961803
"%UserProvidedCtr{has a user provided %select{copy|move}1 "
17971804
"constructor}|"

clang/lib/Sema/SemaTypeTraits.cpp

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,6 +1959,7 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) {
19591959
.Case("is_trivially_copyable", TypeTrait::UTT_IsTriviallyCopyable)
19601960
.Case("is_assignable", TypeTrait::BTT_IsAssignable)
19611961
.Case("is_empty", TypeTrait::UTT_IsEmpty)
1962+
.Case("is_standard_layout", TypeTrait::UTT_IsStandardLayout)
19621963
.Default(std::nullopt);
19631964
}
19641965

@@ -2382,6 +2383,150 @@ static void DiagnoseIsEmptyReason(Sema &S, SourceLocation Loc, QualType T) {
23822383
}
23832384
}
23842385

2386+
static bool hasMultipleDataBaseClassesWithFields(const CXXRecordDecl *D) {
2387+
int NumBasesWithFields = 0;
2388+
for (const CXXBaseSpecifier &Base : D->bases()) {
2389+
const CXXRecordDecl *BaseRD = Base.getType()->getAsCXXRecordDecl();
2390+
if (!BaseRD || BaseRD->isInvalidDecl())
2391+
continue;
2392+
2393+
for (const FieldDecl *Field : BaseRD->fields()) {
2394+
if (!Field->isUnnamedBitField()) {
2395+
if (++NumBasesWithFields > 1)
2396+
return true; // found more than one base class with fields
2397+
break; // no need to check further fields in this base class
2398+
}
2399+
}
2400+
}
2401+
return false;
2402+
}
2403+
2404+
static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc,
2405+
const CXXRecordDecl *D) {
2406+
for (const CXXBaseSpecifier &B : D->bases()) {
2407+
assert(B.getType()->getAsCXXRecordDecl() && "invalid base?");
2408+
if (B.isVirtual()) {
2409+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2410+
<< diag::TraitNotSatisfiedReason::VBase << B.getType()
2411+
<< B.getSourceRange();
2412+
}
2413+
if (!B.getType()->isStandardLayoutType()) {
2414+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2415+
<< diag::TraitNotSatisfiedReason::NonStandardLayoutBase << B.getType()
2416+
<< B.getSourceRange();
2417+
}
2418+
}
2419+
// Check for mixed access specifiers in fields.
2420+
const FieldDecl *FirstField = nullptr;
2421+
AccessSpecifier FirstAccess = AS_none;
2422+
2423+
for (const FieldDecl *Field : D->fields()) {
2424+
if (Field->isUnnamedBitField())
2425+
continue;
2426+
2427+
// Record the first field we see
2428+
if (!FirstField) {
2429+
FirstField = Field;
2430+
FirstAccess = Field->getAccess();
2431+
continue;
2432+
}
2433+
2434+
// Check if the field has a different access specifier than the first one.
2435+
if (Field->getAccess() != FirstAccess) {
2436+
// Emit a diagnostic about mixed access specifiers.
2437+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2438+
<< diag::TraitNotSatisfiedReason::MixedAccess;
2439+
2440+
SemaRef.Diag(FirstField->getLocation(), diag::note_defined_here)
2441+
<< FirstField;
2442+
2443+
SemaRef.Diag(Field->getLocation(), diag::note_unsatisfied_trait_reason)
2444+
<< diag::TraitNotSatisfiedReason::MixedAccessField << Field
2445+
<< FirstField;
2446+
2447+
// No need to check further fields, as we already found mixed access.
2448+
break;
2449+
}
2450+
}
2451+
if (hasMultipleDataBaseClassesWithFields(D)) {
2452+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2453+
<< diag::TraitNotSatisfiedReason::MultipleDataBase;
2454+
}
2455+
if (D->isPolymorphic()) {
2456+
// Find the best location to point “defined here” at.
2457+
const CXXMethodDecl *VirtualMD = nullptr;
2458+
// First, look for a virtual method.
2459+
for (const auto *M : D->methods()) {
2460+
if (M->isVirtual()) {
2461+
VirtualMD = M;
2462+
break;
2463+
}
2464+
}
2465+
if (VirtualMD) {
2466+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2467+
<< diag::TraitNotSatisfiedReason::VirtualFunction << VirtualMD;
2468+
SemaRef.Diag(VirtualMD->getLocation(), diag::note_defined_here)
2469+
<< VirtualMD;
2470+
} else {
2471+
// If no virtual method, point to the record declaration itself.
2472+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2473+
<< diag::TraitNotSatisfiedReason::VirtualFunction << D;
2474+
SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
2475+
}
2476+
}
2477+
for (const FieldDecl *Field : D->fields()) {
2478+
if (!Field->getType()->isStandardLayoutType()) {
2479+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2480+
<< diag::TraitNotSatisfiedReason::NonStandardLayoutMember << Field
2481+
<< Field->getType() << Field->getSourceRange();
2482+
}
2483+
}
2484+
// Find any indirect base classes that have fields.
2485+
if (D->hasDirectFields()) {
2486+
const CXXRecordDecl *Indirect = nullptr;
2487+
D->forallBases([&](const CXXRecordDecl *BaseDef) {
2488+
if (BaseDef->hasDirectFields()) {
2489+
Indirect = BaseDef;
2490+
return false; // stop traversal
2491+
}
2492+
return true; // continue to the next base
2493+
});
2494+
if (Indirect) {
2495+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2496+
<< diag::TraitNotSatisfiedReason::IndirectBaseWithFields << Indirect
2497+
<< Indirect->getSourceRange();
2498+
}
2499+
}
2500+
}
2501+
2502+
static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc,
2503+
QualType T) {
2504+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait)
2505+
<< T << diag::TraitName::StandardLayout;
2506+
2507+
// Check type-level exclusion first.
2508+
if (T->isVariablyModifiedType()) {
2509+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2510+
<< diag::TraitNotSatisfiedReason::VLA;
2511+
return;
2512+
}
2513+
2514+
if (T->isReferenceType()) {
2515+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2516+
<< diag::TraitNotSatisfiedReason::Ref;
2517+
return;
2518+
}
2519+
T = T.getNonReferenceType();
2520+
const CXXRecordDecl *D = T->getAsCXXRecordDecl();
2521+
if (!D || D->isInvalidDecl())
2522+
return;
2523+
2524+
if (D->hasDefinition())
2525+
DiagnoseNonStandardLayoutReason(SemaRef, Loc, D);
2526+
2527+
SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
2528+
}
2529+
23852530
void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
23862531
E = E->IgnoreParenImpCasts();
23872532
if (E->containsErrors())
@@ -2408,6 +2553,9 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
24082553
case UTT_IsEmpty:
24092554
DiagnoseIsEmptyReason(*this, E->getBeginLoc(), Args[0]);
24102555
break;
2556+
case UTT_IsStandardLayout:
2557+
DiagnoseNonStandardLayoutReason(*this, E->getBeginLoc(), Args[0]);
2558+
break;
24112559
default:
24122560
break;
24132561
}

clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ struct is_empty {
3535
};
3636
template <typename T>
3737
constexpr bool is_empty_v = __is_empty(T);
38+
39+
template <typename T>
40+
struct is_standard_layout {
41+
static constexpr bool value = __is_standard_layout(T);
42+
};
43+
template <typename T>
44+
constexpr bool is_standard_layout_v = __is_standard_layout(T);
3845
#endif
3946

4047
#ifdef STD2
@@ -79,6 +86,17 @@ template <typename T>
7986
using is_empty = __details_is_empty<T>;
8087
template <typename T>
8188
constexpr bool is_empty_v = __is_empty(T);
89+
90+
template <typename T>
91+
struct __details_is_standard_layout {
92+
static constexpr bool value = __is_standard_layout(T);
93+
94+
95+
};
96+
template <typename T>
97+
using is_standard_layout = __details_is_standard_layout<T>;
98+
template <typename T>
99+
constexpr bool is_standard_layout_v = __is_standard_layout(T);
82100
#endif
83101

84102

@@ -124,6 +142,13 @@ template <typename T>
124142
using is_empty = __details_is_empty<T>;
125143
template <typename T>
126144
constexpr bool is_empty_v = is_empty<T>::value;
145+
146+
template <typename T>
147+
struct __details_is_standard_layout : bool_constant<__is_standard_layout(T)> {};
148+
template <typename T>
149+
using is_standard_layout = __details_is_standard_layout<T>;
150+
template <typename T>
151+
constexpr bool is_standard_layout_v = is_standard_layout<T>::value;
127152
#endif
128153

129154
}
@@ -150,6 +175,21 @@ static_assert(std::is_trivially_copyable_v<int&>);
150175
// expected-note@-1 {{'int &' is not trivially copyable}} \
151176
// expected-note@-1 {{because it is a reference type}}
152177

178+
179+
// Direct tests
180+
static_assert(std::is_standard_layout<int>::value);
181+
static_assert(std::is_standard_layout_v<int>);
182+
183+
static_assert(std::is_standard_layout<int&>::value);
184+
// expected-error-re@-1 {{static assertion failed due to requirement 'std::{{.*}}is_standard_layout<int &>::value'}} \
185+
// expected-note@-1 {{'int &' is not standard-layout}} \
186+
// expected-note@-1 {{because it is a reference type}}
187+
188+
static_assert(std::is_standard_layout_v<int&>);
189+
// expected-error@-1 {{static assertion failed due to requirement 'std::is_standard_layout_v<int &>'}} \
190+
// expected-note@-1 {{'int &' is not standard-layout}} \
191+
// expected-note@-1 {{because it is a reference type}}
192+
153193
static_assert(!std::is_empty<int>::value);
154194

155195
static_assert(std::is_empty<int&>::value);
@@ -191,6 +231,16 @@ namespace test_namespace {
191231
// expected-note@-1 {{'int &' is not trivially copyable}} \
192232
// expected-note@-1 {{because it is a reference type}}
193233

234+
static_assert(is_standard_layout<int&>::value);
235+
// expected-error-re@-1 {{static assertion failed due to requirement '{{.*}}is_standard_layout<int &>::value'}} \
236+
// expected-note@-1 {{'int &' is not standard-layout}} \
237+
// expected-note@-1 {{because it is a reference type}}
238+
239+
static_assert(is_standard_layout_v<int&>);
240+
// expected-error@-1 {{static assertion failed due to requirement 'is_standard_layout_v<int &>'}} \
241+
// expected-note@-1 {{'int &' is not standard-layout}} \
242+
// expected-note@-1 {{because it is a reference type}}
243+
194244
static_assert(is_assignable<int&, void>::value);
195245
// expected-error-re@-1 {{static assertion failed due to requirement '{{.*}}is_assignable<int &, void>::value'}} \
196246
// expected-error@-1 {{assigning to 'int' from incompatible type 'void'}}

0 commit comments

Comments
 (0)