Skip to content

Commit 4fec702

Browse files
authored
[clang] Add FixItHint for designated init order (#173136)
Generate fix-it for C++20 designated initializers when the initializers do not match the declaration order in the structure.
1 parent 14b9478 commit 4fec702

File tree

3 files changed

+173
-3
lines changed

3 files changed

+173
-3
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,9 @@ Improvements to Clang's diagnostics
493493
carries messages like 'In file included from ...' or 'In module ...'.
494494
Now the include/import locations are written into `sarif.run.result.relatedLocations`.
495495

496+
- Clang now generates a fix-it for C++20 designated initializers when the
497+
initializers do not match the declaration order in the structure.
498+
496499
Improvements to Clang's time-trace
497500
----------------------------------
498501

clang/lib/Sema/SemaInit.cpp

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@
3131
#include "clang/Sema/SemaHLSL.h"
3232
#include "clang/Sema/SemaObjC.h"
3333
#include "llvm/ADT/APInt.h"
34+
#include "llvm/ADT/DenseMap.h"
3435
#include "llvm/ADT/FoldingSet.h"
3536
#include "llvm/ADT/PointerIntPair.h"
37+
#include "llvm/ADT/SmallString.h"
3638
#include "llvm/ADT/SmallVector.h"
3739
#include "llvm/ADT/StringExtras.h"
3840
#include "llvm/Support/ErrorHandling.h"
@@ -3092,6 +3094,65 @@ InitListChecker::CheckDesignatedInitializer(const InitializedEntity &Entity,
30923094
PrevField = *FI;
30933095
}
30943096

3097+
const auto GenerateDesignatedInitReorderingFixit =
3098+
[&](SemaBase::SemaDiagnosticBuilder &Diag) {
3099+
struct ReorderInfo {
3100+
int Pos{};
3101+
const Expr *InitExpr{};
3102+
};
3103+
3104+
llvm::SmallDenseMap<IdentifierInfo *, int> MemberNameInx{};
3105+
llvm::SmallVector<ReorderInfo, 16> ReorderedInitExprs{};
3106+
3107+
const auto *CxxRecord =
3108+
IList->getSemanticForm()->getType()->getAsCXXRecordDecl();
3109+
3110+
for (const FieldDecl *Field : CxxRecord->fields())
3111+
MemberNameInx[Field->getIdentifier()] = Field->getFieldIndex();
3112+
3113+
for (const Expr *Init : IList->inits()) {
3114+
if (const auto *DI =
3115+
dyn_cast_if_present<DesignatedInitExpr>(Init)) {
3116+
// We expect only one Designator
3117+
if (DI->size() != 1)
3118+
return;
3119+
3120+
const IdentifierInfo *const FieldName =
3121+
DI->getDesignator(0)->getFieldName();
3122+
// In case we have an unknown initializer in the source, not in
3123+
// the record
3124+
if (MemberNameInx.contains(FieldName))
3125+
ReorderedInitExprs.emplace_back(
3126+
ReorderInfo{MemberNameInx.at(FieldName), Init});
3127+
}
3128+
}
3129+
3130+
llvm::sort(ReorderedInitExprs,
3131+
[](const ReorderInfo &A, const ReorderInfo &B) {
3132+
return A.Pos < B.Pos;
3133+
});
3134+
3135+
llvm::SmallString<128> FixedInitList{};
3136+
SourceManager &SM = SemaRef.getSourceManager();
3137+
const LangOptions &LangOpts = SemaRef.getLangOpts();
3138+
3139+
// In a derived Record, first n base-classes are initialized first.
3140+
// They do not use designated init, so skip them
3141+
const ArrayRef<clang::Expr *> IListInits =
3142+
IList->inits().drop_front(CxxRecord->getNumBases());
3143+
// loop over each existing expressions and apply replacement
3144+
for (const auto &[OrigExpr, Repl] :
3145+
llvm::zip(IListInits, ReorderedInitExprs)) {
3146+
CharSourceRange CharRange = CharSourceRange::getTokenRange(
3147+
Repl.InitExpr->getSourceRange());
3148+
const StringRef InitText =
3149+
Lexer::getSourceText(CharRange, SM, LangOpts);
3150+
3151+
Diag << FixItHint::CreateReplacement(OrigExpr->getSourceRange(),
3152+
InitText.str());
3153+
}
3154+
};
3155+
30953156
if (PrevField &&
30963157
PrevField->getFieldIndex() > KnownField->getFieldIndex()) {
30973158
SemaRef.Diag(DIE->getInit()->getBeginLoc(),
@@ -3101,9 +3162,10 @@ InitListChecker::CheckDesignatedInitializer(const InitializedEntity &Entity,
31013162
unsigned OldIndex = StructuredIndex - 1;
31023163
if (StructuredList && OldIndex <= StructuredList->getNumInits()) {
31033164
if (Expr *PrevInit = StructuredList->getInit(OldIndex)) {
3104-
SemaRef.Diag(PrevInit->getBeginLoc(),
3105-
diag::note_previous_field_init)
3106-
<< PrevField << PrevInit->getSourceRange();
3165+
auto Diag = SemaRef.Diag(PrevInit->getBeginLoc(),
3166+
diag::note_previous_field_init)
3167+
<< PrevField << PrevInit->getSourceRange();
3168+
GenerateDesignatedInitReorderingFixit(Diag);
31073169
}
31083170
}
31093171
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// RUN: %clang_cc1 -std=c++20 %s -Wreorder-init-list -fdiagnostics-parseable-fixits 2>&1 | FileCheck %s
2+
3+
// These tests are from clang/test/SemaCXX/cxx2a-initializer-aggregates.cpp
4+
struct C { int :0, x, :0, y, :0; };
5+
C c = {
6+
.x = 1,
7+
.x = 1,
8+
.y = 1,
9+
.y = 1,
10+
.x = 1,
11+
.x = 1,
12+
};
13+
// CHECK: fix-it:"{{.*}}":{[[@LINE-7]]:3-[[@LINE-7]]:9}:".x = 1"
14+
// CHECK: fix-it:"{{.*}}":{[[@LINE-7]]:3-[[@LINE-7]]:9}:".x = 1"
15+
// CHECK: fix-it:"{{.*}}":{[[@LINE-7]]:3-[[@LINE-7]]:9}:".x = 1"
16+
// CHECK: fix-it:"{{.*}}":{[[@LINE-7]]:3-[[@LINE-7]]:9}:".x = 1"
17+
// CHECK: fix-it:"{{.*}}":{[[@LINE-7]]:3-[[@LINE-7]]:9}:".y = 1"
18+
// CHECK: fix-it:"{{.*}}":{[[@LINE-7]]:3-[[@LINE-7]]:9}:".y = 1"
19+
20+
namespace GH63605 {
21+
struct A {
22+
unsigned x;
23+
unsigned y;
24+
unsigned z;
25+
};
26+
27+
struct B {
28+
unsigned a;
29+
unsigned b;
30+
};
31+
32+
struct : public A, public B {
33+
unsigned : 2;
34+
unsigned a : 6;
35+
unsigned : 1;
36+
unsigned b : 6;
37+
unsigned : 2;
38+
unsigned c : 6;
39+
unsigned d : 1;
40+
unsigned e : 2;
41+
} data = {
42+
{.z=0,
43+
44+
45+
.y=1,
46+
47+
.x=2},
48+
{.b=3,
49+
.a=4},
50+
.e = 1,
51+
52+
.d = 1,
53+
54+
.c = 1,
55+
.b = 1,
56+
.a = 1,
57+
};
58+
}
59+
// CHECK: fix-it:"{{.*}}":{[[@LINE-17]]:4-[[@LINE-17]]:8}:".x=2"
60+
// CHECK: fix-it:"{{.*}}":{[[@LINE-15]]:4-[[@LINE-15]]:8}:".y=1"
61+
// CHECK: fix-it:"{{.*}}":{[[@LINE-14]]:4-[[@LINE-14]]:8}:".z=0"
62+
// CHECK: fix-it:"{{.*}}":{[[@LINE-14]]:4-[[@LINE-14]]:8}:".a=4"
63+
// CHECK: fix-it:"{{.*}}":{[[@LINE-14]]:4-[[@LINE-14]]:8}:".b=3"
64+
// CHECK: fix-it:"{{.*}}":{[[@LINE-14]]:5-[[@LINE-14]]:11}:".a = 1"
65+
// CHECK: fix-it:"{{.*}}":{[[@LINE-13]]:5-[[@LINE-13]]:11}:".b = 1"
66+
// CHECK: fix-it:"{{.*}}":{[[@LINE-12]]:5-[[@LINE-12]]:11}:".c = 1"
67+
// CHECK: fix-it:"{{.*}}":{[[@LINE-12]]:5-[[@LINE-12]]:11}:".d = 1"
68+
// CHECK: fix-it:"{{.*}}":{[[@LINE-12]]:5-[[@LINE-12]]:11}:".e = 1"
69+
// END tests from clang/test/SemaCXX/cxx2a-initializer-aggregates.cpp
70+
71+
namespace reorder_derived {
72+
struct col {
73+
int r;
74+
int g;
75+
int b;
76+
};
77+
78+
struct point {
79+
float x;
80+
float y;
81+
float z;
82+
};
83+
84+
struct derived : public col, public point {
85+
int z2;
86+
int z1;
87+
};
88+
89+
void test() {
90+
derived a {
91+
{.b = 1, .g = 2, .r = 3},
92+
{ .z = 1, .y=2, .x = 3 },
93+
.z1 = 1,
94+
.z2 = 2,
95+
};
96+
}
97+
// CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:12}:".r = 3"
98+
// CHECK: fix-it:"{{.*}}":{[[@LINE-7]]:14-[[@LINE-7]]:20}:".g = 2"
99+
// CHECK: fix-it:"{{.*}}":{[[@LINE-8]]:22-[[@LINE-8]]:28}:".b = 1"
100+
// CHECK: fix-it:"{{.*}}":{[[@LINE-8]]:15-[[@LINE-8]]:19}:".y=2"
101+
// CHECK: fix-it:"{{.*}}":{[[@LINE-9]]:21-[[@LINE-9]]:28}:".z = 1"
102+
// CHECK: fix-it:"{{.*}}":{[[@LINE-10]]:7-[[@LINE-10]]:13}:".x = 3"
103+
// CHECK: fix-it:"{{.*}}":{[[@LINE-10]]:5-[[@LINE-10]]:12}:".z2 = 2"
104+
// CHECK: fix-it:"{{.*}}":{[[@LINE-10]]:5-[[@LINE-10]]:12}:".z1 = 1"
105+
} // namespace reorder_derived

0 commit comments

Comments
 (0)