[clang-tools-extra] [clang-reorder-fields] Support designated initializers (PR #142150)
Vladimir Vuksanovic via cfe-commits
cfe-commits at lists.llvm.org
Fri May 30 06:43:34 PDT 2025
https://github.com/vvuksanovic created https://github.com/llvm/llvm-project/pull/142150
Initializer lists with designators, missing elements or omitted braces can now be rewritten. Any missing designators are added and they get sorted according to the new order.
```
struct Foo {
int a;
int b;
int c;
};
struct Foo foo = { .a = 1, 2, 3 }
```
when reordering elements to "b,a,c" becomes:
```
struct Foo {
int b;
int a;
int c;
};
struct Foo foo = { .b = 2, .a = 1, .c = 3 }
```
>From 67febc3e5b388d38025a87e7824f5a0e5d6adaaa Mon Sep 17 00:00:00 2001
From: Vladimir Vuksanovic <vladimir.vuksanovic at htecgroup.com>
Date: Fri, 30 May 2025 05:37:06 -0700
Subject: [PATCH] [clang-reorder-fields] Support designated initializers
Initializer lists with designators, missing elements or omitted braces can now be rewritten. Any missing designators are added and they get sorted according to the new order.
```
struct Foo {
int a;
int b;
int c;
};
struct Foo foo = { .a = 1, 2, 3 }
```
when reordering elements to "b,a,c" becomes:
```
struct Foo {
int b;
int a;
int c;
};
struct Foo foo = { .b = 2, .a = 1, .c = 3 }
```
---
.../clang-reorder-fields/CMakeLists.txt | 1 +
.../ReorderFieldsAction.cpp | 301 +++++++++++++++---
.../clang-reorder-fields/utils/Designator.cpp | 256 +++++++++++++++
.../clang-reorder-fields/utils/Designator.h | 118 +++++++
.../DesignatedInitializerList.c | 31 ++
5 files changed, 659 insertions(+), 48 deletions(-)
create mode 100644 clang-tools-extra/clang-reorder-fields/utils/Designator.cpp
create mode 100644 clang-tools-extra/clang-reorder-fields/utils/Designator.h
create mode 100644 clang-tools-extra/test/clang-reorder-fields/DesignatedInitializerList.c
diff --git a/clang-tools-extra/clang-reorder-fields/CMakeLists.txt b/clang-tools-extra/clang-reorder-fields/CMakeLists.txt
index 2fdeb65d89767..dfb28234fd548 100644
--- a/clang-tools-extra/clang-reorder-fields/CMakeLists.txt
+++ b/clang-tools-extra/clang-reorder-fields/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangReorderFields STATIC
+ utils/Designator.cpp
ReorderFieldsAction.cpp
DEPENDS
diff --git a/clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp b/clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp
index ea0207619fb2b..f5961a7dab0c9 100644
--- a/clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp
+++ b/clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp
@@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//
#include "ReorderFieldsAction.h"
+#include "utils/Designator.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
@@ -23,6 +24,7 @@
#include "clang/Tooling/Refactoring.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SetVector.h"
+#include "llvm/Support/ErrorHandling.h"
#include <string>
namespace clang {
@@ -81,7 +83,44 @@ getNewFieldsOrder(const RecordDecl *Definition,
return NewFieldsOrder;
}
+struct ReorderedStruct {
+public:
+ ReorderedStruct(const RecordDecl *Decl, ArrayRef<unsigned> NewFieldsOrder)
+ : Definition(Decl), NewFieldsOrder(NewFieldsOrder),
+ NewFieldsPositions(NewFieldsOrder.size()) {
+ for (unsigned I = 0; I < NewFieldsPositions.size(); ++I)
+ NewFieldsPositions[NewFieldsOrder[I]] = I;
+ }
+
+ const RecordDecl *Definition;
+ ArrayRef<unsigned> NewFieldsOrder;
+ SmallVector<unsigned, 4> NewFieldsPositions;
+};
+
// FIXME: error-handling
+/// Replaces a range of source code by the specified text.
+static void
+addReplacement(SourceRange Old, StringRef New, const ASTContext &Context,
+ std::map<std::string, tooling::Replacements> &Replacements) {
+ tooling::Replacement R(Context.getSourceManager(),
+ CharSourceRange::getTokenRange(Old), New,
+ Context.getLangOpts());
+ consumeError(Replacements[std::string(R.getFilePath())].add(R));
+}
+
+/// Replaces one range of source code by another and adds a prefix.
+static void
+addReplacement(SourceRange Old, SourceRange New, StringRef Prefix,
+ const ASTContext &Context,
+ std::map<std::string, tooling::Replacements> &Replacements) {
+ std::string NewText =
+ (Prefix + Lexer::getSourceText(CharSourceRange::getTokenRange(New),
+ Context.getSourceManager(),
+ Context.getLangOpts()))
+ .str();
+ addReplacement(Old, NewText, Context, Replacements);
+}
+
/// Replaces one range of source code by another.
static void
addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context,
@@ -89,10 +128,7 @@ addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context,
StringRef NewText =
Lexer::getSourceText(CharSourceRange::getTokenRange(New),
Context.getSourceManager(), Context.getLangOpts());
- tooling::Replacement R(Context.getSourceManager(),
- CharSourceRange::getTokenRange(Old), NewText,
- Context.getLangOpts());
- consumeError(Replacements[std::string(R.getFilePath())].add(R));
+ addReplacement(Old, NewText.str(), Context, Replacements);
}
/// Find all member fields used in the given init-list initializer expr
@@ -194,33 +230,33 @@ static SourceRange getFullFieldSourceRange(const FieldDecl &Field,
/// different accesses (public/protected/private) is not supported.
/// \returns true on success.
static bool reorderFieldsInDefinition(
- const RecordDecl *Definition, ArrayRef<unsigned> NewFieldsOrder,
- const ASTContext &Context,
+ const ReorderedStruct &RS, const ASTContext &Context,
std::map<std::string, tooling::Replacements> &Replacements) {
- assert(Definition && "Definition is null");
+ assert(RS.Definition && "Definition is null");
SmallVector<const FieldDecl *, 10> Fields;
- for (const auto *Field : Definition->fields())
+ for (const auto *Field : RS.Definition->fields())
Fields.push_back(Field);
// Check that the permutation of the fields doesn't change the accesses
- for (const auto *Field : Definition->fields()) {
+ for (const auto *Field : RS.Definition->fields()) {
const auto FieldIndex = Field->getFieldIndex();
- if (Field->getAccess() != Fields[NewFieldsOrder[FieldIndex]]->getAccess()) {
+ if (Field->getAccess() !=
+ Fields[RS.NewFieldsOrder[FieldIndex]]->getAccess()) {
llvm::errs() << "Currently reordering of fields with different accesses "
"is not supported\n";
return false;
}
}
- for (const auto *Field : Definition->fields()) {
+ for (const auto *Field : RS.Definition->fields()) {
const auto FieldIndex = Field->getFieldIndex();
- if (FieldIndex == NewFieldsOrder[FieldIndex])
+ if (FieldIndex == RS.NewFieldsOrder[FieldIndex])
continue;
- addReplacement(
- getFullFieldSourceRange(*Field, Context),
- getFullFieldSourceRange(*Fields[NewFieldsOrder[FieldIndex]], Context),
- Context, Replacements);
+ addReplacement(getFullFieldSourceRange(*Field, Context),
+ getFullFieldSourceRange(
+ *Fields[RS.NewFieldsOrder[FieldIndex]], Context),
+ Context, Replacements);
}
return true;
}
@@ -231,7 +267,7 @@ static bool reorderFieldsInDefinition(
/// fields. Thus, we need to ensure that we reorder just the initializers that
/// are present.
static void reorderFieldsInConstructor(
- const CXXConstructorDecl *CtorDecl, ArrayRef<unsigned> NewFieldsOrder,
+ const CXXConstructorDecl *CtorDecl, const ReorderedStruct &RS,
ASTContext &Context,
std::map<std::string, tooling::Replacements> &Replacements) {
assert(CtorDecl && "Constructor declaration is null");
@@ -243,10 +279,6 @@ static void reorderFieldsInConstructor(
// Thus this assert needs to be after the previous checks.
assert(CtorDecl->isThisDeclarationADefinition() && "Not a definition");
- SmallVector<unsigned, 10> NewFieldsPositions(NewFieldsOrder.size());
- for (unsigned i = 0, e = NewFieldsOrder.size(); i < e; ++i)
- NewFieldsPositions[NewFieldsOrder[i]] = i;
-
SmallVector<const CXXCtorInitializer *, 10> OldWrittenInitializersOrder;
SmallVector<const CXXCtorInitializer *, 10> NewWrittenInitializersOrder;
for (const auto *Initializer : CtorDecl->inits()) {
@@ -257,8 +289,8 @@ static void reorderFieldsInConstructor(
const FieldDecl *ThisM = Initializer->getMember();
const auto UsedMembers = findMembersUsedInInitExpr(Initializer, Context);
for (const FieldDecl *UM : UsedMembers) {
- if (NewFieldsPositions[UM->getFieldIndex()] >
- NewFieldsPositions[ThisM->getFieldIndex()]) {
+ if (RS.NewFieldsPositions[UM->getFieldIndex()] >
+ RS.NewFieldsPositions[ThisM->getFieldIndex()]) {
DiagnosticsEngine &DiagEngine = Context.getDiagnostics();
auto Description = ("reordering field " + UM->getName() + " after " +
ThisM->getName() + " makes " + UM->getName() +
@@ -276,8 +308,8 @@ static void reorderFieldsInConstructor(
auto ByFieldNewPosition = [&](const CXXCtorInitializer *LHS,
const CXXCtorInitializer *RHS) {
assert(LHS && RHS);
- return NewFieldsPositions[LHS->getMember()->getFieldIndex()] <
- NewFieldsPositions[RHS->getMember()->getFieldIndex()];
+ return RS.NewFieldsPositions[LHS->getMember()->getFieldIndex()] <
+ RS.NewFieldsPositions[RHS->getMember()->getFieldIndex()];
};
llvm::sort(NewWrittenInitializersOrder, ByFieldNewPosition);
assert(OldWrittenInitializersOrder.size() ==
@@ -289,35 +321,205 @@ static void reorderFieldsInConstructor(
Replacements);
}
+/// Replacement for broken InitListExpr::isExplicit function.
+/// TODO: Remove when InitListExpr::isExplicit is fixed.
+static bool isImplicitILE(const InitListExpr *ILE, const ASTContext &Context) {
+ // The ILE is implicit if either:
+ // - The left brace loc of the ILE matches the start of first init expression
+ // (for non designated decls)
+ // - The right brace loc of the ILE matches the end of first init expression
+ // (for designated decls)
+ // The first init expression should be taken from the syntactic form, but
+ // since the ILE could be implicit, there might not be a syntactic form.
+ // For that reason we have to check against all init expressions.
+ for (const Expr *Init : ILE->inits()) {
+ if (ILE->getLBraceLoc() == Init->getBeginLoc() ||
+ ILE->getRBraceLoc() == Init->getEndLoc())
+ return true;
+ }
+ return false;
+}
+
+/// Compares compatible designators according to the new struct order.
+/// Returns a negative value if Lhs < Rhs, positive value if Lhs > Rhs and 0 if
+/// they are equal.
+static int cmpDesignators(const DesignatorIter &Lhs, const DesignatorIter &Rhs,
+ const ReorderedStruct &Struct) {
+ assert(Lhs.getTag() == Rhs.getTag() && "Incompatible designators");
+ switch (Lhs.getTag()) {
+ case DesignatorIter::STRUCT: {
+ assert(Lhs.getStructDecl() == Rhs.getStructDecl() &&
+ "Incompatible structs");
+ // Use the new layout for reordered struct.
+ if (Struct.Definition == Lhs.getStructDecl()) {
+ return Struct.NewFieldsPositions[Lhs.getStructIter()->getFieldIndex()] -
+ Struct.NewFieldsPositions[Rhs.getStructIter()->getFieldIndex()];
+ }
+ return Lhs.getStructIter()->getFieldIndex() -
+ Rhs.getStructIter()->getFieldIndex();
+ }
+ case DesignatorIter::ARRAY:
+ return Lhs.getArrayIndex() - Rhs.getArrayIndex();
+ case DesignatorIter::ARRAY_RANGE:
+ return Lhs.getArrayRangeEnd() - Rhs.getArrayRangeEnd();
+ }
+ llvm_unreachable("Invalid designator tag");
+}
+
+/// Compares compatible designator lists according to the new struct order.
+/// Returns a negative value if Lhs < Rhs, positive value if Lhs > Rhs and 0 if
+/// they are equal.
+static int cmpDesignatorLists(const Designators &Lhs, const Designators &Rhs,
+ const ReorderedStruct &Reorders) {
+ for (unsigned Idx = 0, Size = std::min(Lhs.size(), Rhs.size()); Idx < Size;
+ ++Idx) {
+ int DesignatorComp = cmpDesignators(Lhs[Idx], Rhs[Idx], Reorders);
+ // If the current designators are not equal, return the result
+ if (DesignatorComp != 0)
+ return DesignatorComp;
+ // Otherwise, continue to the next pair.
+ }
+ //
+ return Lhs.size() - Rhs.size();
+}
+
+/// Finds the semantic form of the first explicit ancestor of the given
+/// initializer list including itself.
+static const InitListExpr *getExplicitILE(const InitListExpr *ILE,
+ ASTContext &Context) {
+ if (!isImplicitILE(ILE, Context))
+ return ILE;
+ const InitListExpr *TopLevelILE = ILE;
+ DynTypedNodeList Parents = Context.getParents(*TopLevelILE);
+ while (!Parents.empty() && Parents.begin()->get<InitListExpr>()) {
+ TopLevelILE = Parents.begin()->get<InitListExpr>();
+ Parents = Context.getParents(*TopLevelILE);
+ if (!isImplicitILE(TopLevelILE, Context))
+ break;
+ }
+ if (!TopLevelILE->isSemanticForm()) {
+ return TopLevelILE->getSemanticForm();
+ }
+ return TopLevelILE;
+}
+
/// Reorders initializers in the brace initialization of an aggregate.
///
/// At the moment partial initialization is not supported.
/// \returns true on success
static bool reorderFieldsInInitListExpr(
- const InitListExpr *InitListEx, ArrayRef<unsigned> NewFieldsOrder,
- const ASTContext &Context,
+ const InitListExpr *InitListEx, const ReorderedStruct &RS,
+ ASTContext &Context,
std::map<std::string, tooling::Replacements> &Replacements) {
assert(InitListEx && "Init list expression is null");
- // We care only about InitListExprs which originate from source code.
- // Implicit InitListExprs are created by the semantic analyzer.
- if (!InitListEx->isExplicit())
+ // Only process semantic forms of initializer lists.
+ if (!InitListEx->isSemanticForm()) {
return true;
- // The method InitListExpr::getSyntacticForm may return nullptr indicating
- // that the current initializer list also serves as its syntactic form.
- if (const auto *SyntacticForm = InitListEx->getSyntacticForm())
- InitListEx = SyntacticForm;
+ }
+
// If there are no initializers we do not need to change anything.
if (!InitListEx->getNumInits())
return true;
- if (InitListEx->getNumInits() != NewFieldsOrder.size()) {
- llvm::errs() << "Currently only full initialization is supported\n";
- return false;
+
+ // We care only about InitListExprs which originate from source code.
+ // Implicit InitListExprs are created by the semantic analyzer.
+ // We find the first parent InitListExpr that exists in source code and
+ // process it. This is necessary because of designated initializer lists and
+ // possible omitted braces.
+ InitListEx = getExplicitILE(InitListEx, Context);
+
+ // Find if there are any designated initializations or implicit values. If all
+ // initializers are present and none have designators then just reorder them
+ // normally. Otherwise, designators are added to all initializers and they are
+ // sorted in the new order.
+ bool ShouldAddDesignators = false;
+ // The method InitListExpr::getSyntacticForm may return nullptr indicating
+ // that the current initializer list also serves as its syntactic form.
+ const InitListExpr *SyntacticInitListEx = InitListEx;
+ if (const InitListExpr *SynILE = InitListEx->getSyntacticForm()) {
+ // Do not rewrite zero initializers. This check is only valid for syntactic
+ // forms.
+ if (SynILE->isIdiomaticZeroInitializer(Context.getLangOpts()))
+ return true;
+
+ ShouldAddDesignators = InitListEx->getNumInits() != SynILE->getNumInits() ||
+ llvm::any_of(SynILE->inits(), [](const Expr *Init) {
+ return isa<DesignatedInitExpr>(Init);
+ });
+
+ SyntacticInitListEx = SynILE;
+ } else {
+ // If there is no syntactic form, there can be no designators. Instead,
+ // there might be implicit values.
+ ShouldAddDesignators =
+ (RS.NewFieldsOrder.size() != InitListEx->getNumInits()) ||
+ llvm::any_of(InitListEx->inits(), [&Context](const Expr *Init) {
+ return isa<ImplicitValueInitExpr>(Init) ||
+ (isa<InitListExpr>(Init) &&
+ isImplicitILE(dyn_cast<InitListExpr>(Init), Context));
+ });
+ }
+
+ if (ShouldAddDesignators) {
+ // Designators are only supported from C++20.
+ if (Context.getLangOpts().CPlusPlus && !Context.getLangOpts().CPlusPlus20) {
+ llvm::errs()
+ << "Currently only full initialization is supported for C++\n";
+ return false;
+ }
+
+ // Handle case when some fields are designated. Some fields can be
+ // missing. Insert any missing designators and reorder the expressions
+ // according to the new order.
+ Designators CurrentDesignators{};
+ // Remember each initializer expression along with its designators. They are
+ // sorted later to determine the correct order.
+ std::vector<std::pair<Designators, const Expr *>> Rewrites;
+ for (const Expr *Init : SyntacticInitListEx->inits()) {
+ if (const auto *DIE = dyn_cast_or_null<DesignatedInitExpr>(Init)) {
+ CurrentDesignators = {DIE, SyntacticInitListEx, Context};
+
+ // Use the child of the DesignatedInitExpr. This way designators are
+ // always replaced.
+ Rewrites.push_back({CurrentDesignators, DIE->getInit()});
+ } else {
+ // Find the next field.
+ if (!CurrentDesignators.increment(SyntacticInitListEx, Init, Context)) {
+ llvm::errs() << "Unsupported initializer list\n";
+ return false;
+ }
+
+ // Do not rewrite implicit values. They just had to be processed to
+ // find the correct designator.
+ if (!isa<ImplicitValueInitExpr>(Init))
+ Rewrites.push_back({CurrentDesignators, Init});
+ }
+ }
+
+ // Sort the designators according to the new order.
+ llvm::sort(Rewrites, [&RS](const auto &Lhs, const auto &Rhs) {
+ return cmpDesignatorLists(Lhs.first, Rhs.first, RS) < 0;
+ });
+
+ for (unsigned i = 0, e = Rewrites.size(); i < e; ++i) {
+ addReplacement(SyntacticInitListEx->getInit(i)->getSourceRange(),
+ Rewrites[i].second->getSourceRange(),
+ Rewrites[i].first.toString(), Context, Replacements);
+ }
+ } else {
+ // Handle excess initializers by leaving them unchanged.
+ assert(SyntacticInitListEx->getNumInits() >= InitListEx->getNumInits());
+
+ // All field initializers are present and none have designators. They can be
+ // reordered normally.
+ for (unsigned i = 0, e = RS.NewFieldsOrder.size(); i < e; ++i) {
+ if (i != RS.NewFieldsOrder[i])
+ addReplacement(SyntacticInitListEx->getInit(i)->getSourceRange(),
+ SyntacticInitListEx->getInit(RS.NewFieldsOrder[i])
+ ->getSourceRange(),
+ Context, Replacements);
+ }
}
- for (unsigned i = 0, e = InitListEx->getNumInits(); i < e; ++i)
- if (i != NewFieldsOrder[i])
- addReplacement(InitListEx->getInit(i)->getSourceRange(),
- InitListEx->getInit(NewFieldsOrder[i])->getSourceRange(),
- Context, Replacements);
return true;
}
@@ -345,7 +547,9 @@ class ReorderingConsumer : public ASTConsumer {
getNewFieldsOrder(RD, DesiredFieldsOrder);
if (NewFieldsOrder.empty())
return;
- if (!reorderFieldsInDefinition(RD, NewFieldsOrder, Context, Replacements))
+ ReorderedStruct RS{RD, NewFieldsOrder};
+
+ if (!reorderFieldsInDefinition(RS, Context, Replacements))
return;
// CXXRD will be nullptr if C code (not C++) is being processed.
@@ -353,24 +557,25 @@ class ReorderingConsumer : public ASTConsumer {
if (CXXRD)
for (const auto *C : CXXRD->ctors())
if (const auto *D = dyn_cast<CXXConstructorDecl>(C->getDefinition()))
- reorderFieldsInConstructor(cast<const CXXConstructorDecl>(D),
- NewFieldsOrder, Context, Replacements);
+ reorderFieldsInConstructor(cast<const CXXConstructorDecl>(D), RS,
+ Context, Replacements);
// We only need to reorder init list expressions for
// plain C structs or C++ aggregate types.
// For other types the order of constructor parameters is used,
// which we don't change at the moment.
// Now (v0) partial initialization is not supported.
- if (!CXXRD || CXXRD->isAggregate())
+ if (!CXXRD || CXXRD->isAggregate()) {
for (auto Result :
match(initListExpr(hasType(equalsNode(RD))).bind("initListExpr"),
Context))
if (!reorderFieldsInInitListExpr(
- Result.getNodeAs<InitListExpr>("initListExpr"), NewFieldsOrder,
- Context, Replacements)) {
+ Result.getNodeAs<InitListExpr>("initListExpr"), RS, Context,
+ Replacements)) {
Replacements.clear();
return;
}
+ }
}
};
} // end anonymous namespace
diff --git a/clang-tools-extra/clang-reorder-fields/utils/Designator.cpp b/clang-tools-extra/clang-reorder-fields/utils/Designator.cpp
new file mode 100644
index 0000000000000..9ad60a3fc5db6
--- /dev/null
+++ b/clang-tools-extra/clang-reorder-fields/utils/Designator.cpp
@@ -0,0 +1,256 @@
+//===-- tools/extra/clang-reorder-fields/utils/Designator.cpp ---*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains the definition of the DesignatorIter and Designators
+/// utility classes.
+///
+//===----------------------------------------------------------------------===//
+
+#include "Designator.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Expr.h"
+
+namespace clang {
+namespace reorder_fields {
+
+DesignatorIter::DesignatorIter(const QualType Type,
+ RecordDecl::field_iterator Field,
+ const RecordDecl *RD)
+ : Tag(STRUCT), Type(Type), StructIt({Field, RD}) {}
+
+DesignatorIter::DesignatorIter(const QualType Type, uint64_t Idx, uint64_t Size)
+ : Tag(ARRAY), Type(Type), ArrayIt({Idx, Size}) {}
+
+DesignatorIter::DesignatorIter(const QualType Type, uint64_t Start,
+ uint64_t End, uint64_t Size)
+ : Tag(ARRAY_RANGE), Type(Type), ArrayRangeIt({Start, End, Size}) {}
+
+DesignatorIter &DesignatorIter::operator++() {
+ assert(!isFinished() && "Iterator is already finished");
+ switch (Tag) {
+ case STRUCT:
+ if (StructIt.Record->isUnion()) {
+ // Union always finishes on first increment.
+ StructIt.Field = StructIt.Record->field_end();
+ Type = QualType();
+ break;
+ }
+ ++StructIt.Field;
+ if (StructIt.Field != StructIt.Record->field_end()) {
+ Type = StructIt.Field->getType();
+ } else {
+ Type = QualType();
+ }
+ break;
+ case ARRAY:
+ ++ArrayIt.Index;
+ break;
+ case ARRAY_RANGE:
+ ArrayIt.Index = ArrayRangeIt.End + 1;
+ ArrayIt.Size = ArrayRangeIt.Size;
+ Tag = ARRAY;
+ break;
+ }
+ return *this;
+}
+
+bool DesignatorIter::isFinished() {
+ switch (Tag) {
+ case STRUCT:
+ return StructIt.Field == StructIt.Record->field_end();
+ case ARRAY:
+ return ArrayIt.Index == ArrayIt.Size;
+ case ARRAY_RANGE:
+ return ArrayRangeIt.End == ArrayRangeIt.Size;
+ }
+ return false;
+}
+
+DesignatorIter::Kind DesignatorIter::getTag() const { return Tag; }
+
+QualType DesignatorIter::getType() const { return Type; }
+
+RecordDecl::field_iterator &DesignatorIter::getStructIter() {
+ assert(Tag == STRUCT && "Must be a field designator");
+ return StructIt.Field;
+}
+
+RecordDecl::field_iterator DesignatorIter::getStructIter() const {
+ assert(Tag == STRUCT && "Must be a field designator");
+ return StructIt.Field;
+}
+
+const RecordDecl *DesignatorIter::getStructDecl() const {
+ assert(Tag == STRUCT && "Must be a field designator");
+ return StructIt.Record;
+}
+
+uint64_t &DesignatorIter::getArrayIndex() {
+ assert(Tag == ARRAY && "Must be an array designator");
+ return ArrayIt.Index;
+}
+
+uint64_t DesignatorIter::getArrayIndex() const {
+ assert(Tag == ARRAY && "Must be an array designator");
+ return ArrayIt.Index;
+}
+
+uint64_t DesignatorIter::getArrayRangeStart() const {
+ assert(Tag == ARRAY_RANGE && "Must be an array range designator");
+ return ArrayRangeIt.Start;
+}
+
+uint64_t DesignatorIter::getArrayRangeEnd() const {
+ assert(Tag == ARRAY_RANGE && "Must be an array range designator");
+ return ArrayRangeIt.End;
+}
+
+uint64_t DesignatorIter::getArraySize() const {
+ assert((Tag == ARRAY || Tag == ARRAY_RANGE) &&
+ "Must be an array or range designator");
+ return ArrayIt.Size;
+}
+
+Designators::Designators(const DesignatedInitExpr *DIE, const InitListExpr *ILE,
+ const ASTContext &Context) {
+ for (const auto &D : DIE->designators()) {
+ if (D.isFieldDesignator()) {
+ RecordDecl *DesignatorRecord = D.getFieldDecl()->getParent();
+ for (auto FieldIt = DesignatorRecord->field_begin();
+ FieldIt != DesignatorRecord->field_end(); ++FieldIt) {
+ if (*FieldIt == D.getFieldDecl()) {
+ DesignatorList.push_back(
+ {FieldIt->getType(), FieldIt, DesignatorRecord});
+ break;
+ }
+ }
+ } else {
+ const QualType CurrentType = DesignatorList.empty()
+ ? ILE->getType()
+ : DesignatorList.back().getType();
+ const ConstantArrayType *CAT =
+ Context.getAsConstantArrayType(CurrentType);
+ if (!CAT) {
+ // Non-constant-sized arrays are not supported.
+ DesignatorList.clear();
+ return;
+ }
+ if (D.isArrayDesignator()) {
+ DesignatorList.push_back({CAT->getElementType(),
+ DIE->getArrayIndex(D)
+ ->EvaluateKnownConstInt(Context)
+ .getZExtValue(),
+ CAT->getSize().getZExtValue()});
+ } else if (D.isArrayRangeDesignator()) {
+ DesignatorList.push_back({CAT->getElementType(),
+ DIE->getArrayRangeStart(D)
+ ->EvaluateKnownConstInt(Context)
+ .getZExtValue(),
+ DIE->getArrayRangeEnd(D)
+ ->EvaluateKnownConstInt(Context)
+ .getZExtValue(),
+ CAT->getSize().getZExtValue()});
+ } else {
+ llvm_unreachable("Unexpected designator kind");
+ }
+ }
+ }
+}
+
+bool Designators::increment(const InitListExpr *ILE, const Expr *Init,
+ const ASTContext &Context) {
+ if (DesignatorList.empty()) {
+ // First field is not designated. Initialize to the first field or
+ // array index.
+ if (ILE->getType()->isArrayType()) {
+ const ConstantArrayType *CAT =
+ Context.getAsConstantArrayType(ILE->getType());
+ // Only constant size arrays are supported.
+ if (!CAT) {
+ DesignatorList.clear();
+ return false;
+ }
+ DesignatorList.push_back(
+ {CAT->getElementType(), 0, CAT->getSize().getZExtValue()});
+ } else {
+ const RecordDecl *DesignatorRD = ILE->getType()->getAsRecordDecl();
+ DesignatorList.push_back({DesignatorRD->field_begin()->getType(),
+ DesignatorRD->field_begin(), DesignatorRD});
+ }
+ } else {
+ while (!DesignatorList.empty()) {
+ auto &CurrentDesignator = DesignatorList.back();
+ ++CurrentDesignator;
+ if (CurrentDesignator.isFinished()) {
+ DesignatorList.pop_back();
+ continue;
+ }
+ break;
+ }
+ }
+
+ // If the designator list is empty at this point, then there must be excess
+ // elements in the initializer list. They are not currently supported.
+ if (DesignatorList.empty())
+ return false;
+
+ // Check for missing braces. If the types don't match then there are
+ // missing braces.
+ while (true) {
+ const QualType T = DesignatorList.back().getType();
+ // If the types match, there are no missing braces.
+ if (Init->getType() == T)
+ break;
+
+ // If the current type is a struct, then get its first field.
+ if (T->isRecordType()) {
+ DesignatorList.push_back({T->getAsRecordDecl()->field_begin()->getType(),
+ T->getAsRecordDecl()->field_begin(),
+ T->getAsRecordDecl()});
+ continue;
+ }
+ // If the current type is an array, then get its first element.
+ if (T->isArrayType()) {
+ DesignatorList.push_back(
+ {Context.getAsArrayType(T)->getElementType(), 0,
+ Context.getAsConstantArrayType(T)->getSize().getZExtValue()});
+ continue;
+ }
+
+ // The initializer doesn't match the expected type. The initializer list is
+ // invalid.
+ return false;
+ }
+
+ return true;
+}
+
+std::string Designators::toString() const {
+ if (DesignatorList.empty())
+ return "";
+ std::string Designator = "";
+ for (auto &I : DesignatorList) {
+ switch (I.getTag()) {
+ case DesignatorIter::STRUCT:
+ Designator += "." + I.getStructIter()->getName().str();
+ break;
+ case DesignatorIter::ARRAY:
+ Designator += "[" + std::to_string(I.getArrayIndex()) + "]";
+ break;
+ case DesignatorIter::ARRAY_RANGE:
+ Designator += "[" + std::to_string(I.getArrayRangeStart()) + "..." +
+ std::to_string(I.getArrayRangeEnd()) + "]";
+ }
+ }
+ Designator += " = ";
+ return Designator;
+}
+
+} // namespace reorder_fields
+} // namespace clang
diff --git a/clang-tools-extra/clang-reorder-fields/utils/Designator.h b/clang-tools-extra/clang-reorder-fields/utils/Designator.h
new file mode 100644
index 0000000000000..173b699ad3e9a
--- /dev/null
+++ b/clang-tools-extra/clang-reorder-fields/utils/Designator.h
@@ -0,0 +1,118 @@
+//===-- tools/extra/clang-reorder-fields/utils/Designator.h -----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains the declarations of the DesignatorIter and Designators
+/// utility class.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_UTILS_DESIGNATOR_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_UTILS_DESIGNATOR_H
+
+#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/Type.h"
+
+namespace clang {
+namespace reorder_fields {
+
+class DesignatorIter {
+public:
+ enum Kind { STRUCT, ARRAY, ARRAY_RANGE };
+
+ DesignatorIter(const QualType Type, RecordDecl::field_iterator Field,
+ const RecordDecl *RD);
+
+ DesignatorIter(const QualType Type, uint64_t Idx, uint64_t Size);
+
+ DesignatorIter(const QualType Type, uint64_t Start, uint64_t End,
+ uint64_t Size);
+
+ /// Moves the iterator to the next element.
+ DesignatorIter &operator++();
+
+ /// Checks if the iterator has iterated through all elements.
+ bool isFinished();
+
+ Kind getTag() const;
+ QualType getType() const;
+
+ RecordDecl::field_iterator &getStructIter();
+ RecordDecl::field_iterator getStructIter() const;
+ const RecordDecl *getStructDecl() const;
+ uint64_t &getArrayIndex();
+ uint64_t getArrayIndex() const;
+ uint64_t getArrayRangeStart() const;
+ uint64_t getArrayRangeEnd() const;
+ uint64_t getArraySize() const;
+
+private:
+ /// Type of the designator.
+ Kind Tag;
+
+ /// Type of the designated entry. For arrays this is the type of the element.
+ QualType Type;
+
+ /// Field designator has the iterator to the field and the record the field
+ /// is declared in.
+ struct StructIter {
+ RecordDecl::field_iterator Field;
+ const RecordDecl *Record;
+ };
+
+ /// Array designator has an index and size of the array.
+ struct ArrayIter {
+ uint64_t Index;
+ uint64_t Size;
+ };
+
+ /// Array range designator has a start and end index and size of the array.
+ struct ArrayRangeIter {
+ uint64_t Start;
+ uint64_t End;
+ uint64_t Size;
+ };
+
+ union {
+ StructIter StructIt;
+ ArrayIter ArrayIt;
+ ArrayRangeIter ArrayRangeIt;
+ };
+};
+
+class Designators {
+public:
+ Designators() = default;
+ Designators(const DesignatedInitExpr *DIE, const InitListExpr *ILE,
+ const ASTContext &Context);
+
+ /// Moves the designators to the next initializer in the struct/array. If the
+ /// type of next initializer doesn't match the expected type then there are
+ /// omitted braces and we add new designators to reflect that.
+ bool increment(const InitListExpr *ILE, const Expr *Init,
+ const ASTContext &Context);
+
+ /// Gets a string representation from a list of designators. This string will
+ /// be inserted before an initializer expression to make it designated.
+ std::string toString() const;
+
+ size_t size() const { return DesignatorList.size(); }
+
+ const DesignatorIter &operator[](unsigned Idx) const {
+ return DesignatorList[Idx];
+ }
+
+private:
+ SmallVector<DesignatorIter, 1> DesignatorList;
+};
+
+} // namespace reorder_fields
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_UTILS_DESIGNATOR_H
diff --git a/clang-tools-extra/test/clang-reorder-fields/DesignatedInitializerList.c b/clang-tools-extra/test/clang-reorder-fields/DesignatedInitializerList.c
new file mode 100644
index 0000000000000..c05c296d1b47b
--- /dev/null
+++ b/clang-tools-extra/test/clang-reorder-fields/DesignatedInitializerList.c
@@ -0,0 +1,31 @@
+// RUN: clang-reorder-fields -record-name Foo -fields-order z,w,y,x %s -- | FileCheck %s
+
+struct Foo {
+ const int* x; // CHECK: {{^ double z;}}
+ int y; // CHECK-NEXT: {{^ int w;}}
+ double z; // CHECK-NEXT: {{^ int y;}}
+ int w; // CHECK-NEXT: {{^ const int\* x}}
+};
+
+struct Bar {
+ char a;
+ struct Foo b;
+ char c;
+};
+
+int main() {
+ const int x = 13;
+ struct Foo foo1 = { .x=&x, .y=0, .z=1.29, .w=17 }; // CHECK: {{^ struct Foo foo1 = { .z = 1.29, .w = 17, .y = 0, .x = &x };}}
+ struct Foo foo2 = { .x=&x, 0, 1.29, 17 }; // CHECK: {{^ struct Foo foo2 = { .z = 1.29, .w = 17, .y = 0, .x = &x };}}
+ struct Foo foo3 = { .y=0, .z=1.29, 17, .x=&x }; // CHECK: {{^ struct Foo foo3 = { .z = 1.29, .w = 17, .y = 0, .x = &x };}}
+ struct Foo foo4 = { .y=0, .z=1.29, 17 }; // CHECK: {{^ struct Foo foo4 = { .z = 1.29, .w = 17, .y = 0 };}}
+
+ struct Foo foos1[1] = { [0] = {.x=&x, 0, 1.29, 17} }; // CHECK: {{^ struct Foo foos1\[1] = { \[0] = {.z = 1.29, .w = 17, .y = 0, .x = &x} };}}
+ struct Foo foos2[1] = { [0].x=&x, [0].y=0, [0].z=1.29, [0].w=17 }; // CHECK: {{^ struct Foo foos2\[1] = { \[0].z = 1.29, \[0].w = 17, \[0].y = 0, \[0].x = &x };}}
+ struct Foo foos3[1] = { &x, 0, 1.29, 17 }; // CHECK: {{^ struct Foo foos3\[1] = { \[0].z = 1.29, \[0].w = 17, \[0].y = 0, \[0].x = &x };}}
+ struct Foo foos4[2] = { &x, 0, 1.29, 17, &x, 0, 1.29, 17 }; // CHECK: {{^ struct Foo foos4\[2] = { \[0].z = 1.29, \[0].w = 17, \[0].y = 0, \[0].x = &x, \[1].z = 1.29, \[1].w = 17, \[1].y = 0, \[1].x = &x };}}
+
+ struct Bar bar1 = { .a='a', &x, 0, 1.29, 17, 'c' }; // CHECK: {{^ struct Bar bar1 = { .a = 'a', .b.z = 1.29, .b.w = 17, .b.y = 0, .b.x = &x, .c = 'c' };}}
+
+ return 0;
+}
More information about the cfe-commits
mailing list