[Sema] Fix handling of fields with initializers in nested anonymous unions. (#91692)

Make sure we count the anonymous union as an initialized field, so we
properly construct the AST.

Included bonus testcase Test3, which shows a remaining gap: an anonymous
union can contain a partially initialized anonymous struct, and we
handle that inconsistently.

Fixes #91257
This commit is contained in:
Eli Friedman 2024-06-04 09:24:44 -07:00 committed by GitHub
parent 8e94f0a0ad
commit 5ae5774fb0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 49 additions and 14 deletions

View File

@ -814,19 +814,13 @@ InitListChecker::FillInEmptyInitializations(const InitializedEntity &Entity,
if (const RecordType *RType = ILE->getType()->getAs<RecordType>()) {
const RecordDecl *RDecl = RType->getDecl();
if (RDecl->isUnion() && ILE->getInitializedFieldInUnion())
FillInEmptyInitForField(0, ILE->getInitializedFieldInUnion(),
Entity, ILE, RequiresSecondPass, FillWithNoInit);
else if (RDecl->isUnion() && isa<CXXRecordDecl>(RDecl) &&
cast<CXXRecordDecl>(RDecl)->hasInClassInitializer()) {
for (auto *Field : RDecl->fields()) {
if (Field->hasInClassInitializer()) {
FillInEmptyInitForField(0, Field, Entity, ILE, RequiresSecondPass,
FillWithNoInit);
break;
}
}
if (RDecl->isUnion() && ILE->getInitializedFieldInUnion()) {
FillInEmptyInitForField(0, ILE->getInitializedFieldInUnion(), Entity, ILE,
RequiresSecondPass, FillWithNoInit);
} else {
assert((!RDecl->isUnion() || !isa<CXXRecordDecl>(RDecl) ||
!cast<CXXRecordDecl>(RDecl)->hasInClassInitializer()) &&
"We should have computed initialized fields already");
// The fields beyond ILE->getNumInits() are default initialized, so in
// order to leave them uninitialized, the ILE is expanded and the extra
// fields are then filled with NoInitExpr.
@ -2164,12 +2158,15 @@ void InitListChecker::CheckStructUnionTypes(
return;
for (RecordDecl::field_iterator FieldEnd = RD->field_end();
Field != FieldEnd; ++Field) {
if (Field->hasInClassInitializer()) {
if (Field->hasInClassInitializer() ||
(Field->isAnonymousStructOrUnion() &&
Field->getType()->getAsCXXRecordDecl()->hasInClassInitializer())) {
StructuredList->setInitializedFieldInUnion(*Field);
// FIXME: Actually build a CXXDefaultInitExpr?
return;
}
}
llvm_unreachable("Couldn't find in-class initializer");
}
// Value-initialize the first member of the union that isn't an unnamed

View File

@ -36,7 +36,7 @@ void Test() {
constexpr U0 u0a{};
// CHECK: | `-VarDecl {{.*}} <col:{{.*}}, col:{{.*}}> col:{{.*}} u0a 'const U0' constexpr listinit
// CHECK-NEXT: | |-value: Union None
// CHECK-NEXT: | |-value: Union .U0::(anonymous union at {{.*}}) Union .f Float 3.141500e+00
constexpr U0 u0b{3.1415f};
// CHECK: | `-VarDecl {{.*}} <col:{{.*}}, col:{{.*}}> col:{{.*}} u0b 'const U0' constexpr listinit

View File

@ -77,3 +77,41 @@ namespace use_self {
int fib(int n) { return FibTree{n}.v; }
}
namespace nested_union {
union Test1 {
union {
int inner { 42 };
};
int outer;
};
static_assert(Test1{}.inner == 42, "");
struct Test2 {
union {
struct {
int inner : 32 { 42 }; // expected-warning {{C++20 extension}}
int inner_no_init;
};
int outer;
};
};
static_assert(Test2{}.inner == 42, "");
static_assert(Test2{}.inner_no_init == 0, "");
struct Int { int x; };
struct Test3 {
int x;
union {
struct { // expected-note {{in implicit initialization}}
const int& y; // expected-note {{uninitialized reference member is here}}
int inner : 32 { 42 }; // expected-warning {{C++20 extension}}
};
int outer;
};
};
Test3 test3 = {1}; // expected-error {{reference member of type 'const int &' uninitialized}}
constexpr char f(Test3) { return 1; } // expected-note {{candidate function}}
constexpr char f(Int) { return 2; } // expected-note {{candidate function}}
// FIXME: This shouldn't be ambiguous; either we should reject the declaration
// of Test3, or we should exclude f(Test3) as a candidate.
static_assert(f({1}) == 2, ""); // expected-error {{call to 'f' is ambiguous}}
}