Author: Vladimir Vuksanovic Date: 2025-09-01T00:26:42-07:00 New Revision: 4d9578b8ed20f293641a5917447885ab97aeefbe
URL: https://github.com/llvm/llvm-project/commit/4d9578b8ed20f293641a5917447885ab97aeefbe DIFF: https://github.com/llvm/llvm-project/commit/4d9578b8ed20f293641a5917447885ab97aeefbe.diff LOG: [clang-reorder-fields] Support designated initializers (#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 } ``` Added: clang-tools-extra/clang-reorder-fields/Designator.cpp clang-tools-extra/clang-reorder-fields/Designator.h clang-tools-extra/test/clang-reorder-fields/AggregatePartialInitialization.c clang-tools-extra/test/clang-reorder-fields/DesignatedInitializerList.c clang-tools-extra/test/clang-reorder-fields/DesignatedInitializerList.cpp clang-tools-extra/test/clang-reorder-fields/IdiomaticZeroInitializer.c clang-tools-extra/test/clang-reorder-fields/InitializerListExcessElements.c Modified: clang-tools-extra/clang-reorder-fields/CMakeLists.txt clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp clang-tools-extra/test/clang-reorder-fields/AggregatePartialInitialization.cpp Removed: ################################################################################ diff --git a/clang-tools-extra/clang-reorder-fields/CMakeLists.txt b/clang-tools-extra/clang-reorder-fields/CMakeLists.txt index 2fdeb65d89767..dec0287b26873 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 + Designator.cpp ReorderFieldsAction.cpp DEPENDS diff --git a/clang-tools-extra/clang-reorder-fields/Designator.cpp b/clang-tools-extra/clang-reorder-fields/Designator.cpp new file mode 100644 index 0000000000000..fc070f7f6746d --- /dev/null +++ b/clang-tools-extra/clang-reorder-fields/Designator.cpp @@ -0,0 +1,219 @@ +//===-- 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 Designator and Designators utility +/// classes. +/// +//===----------------------------------------------------------------------===// + +#include "Designator.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace reorder_fields { + +void Designator::advanceToNextField() { + 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; + } +} + +bool Designator::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; +} + +Designators::Designators(const Expr *Init, const InitListExpr *ILE, + const ASTContext *Context) + : ILE(ILE), Context(Context) { + if (ILE->getType()->isArrayType()) { + const ConstantArrayType *CAT = + Context->getAsConstantArrayType(ILE->getType()); + // Only constant size arrays are supported. + if (!CAT) { + DesignatorList.clear(); + return; + } + 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}); + } + + // 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; + + if (!enterImplicitInitLists(Init)) + DesignatorList.clear(); +} + +Designators::Designators(const DesignatedInitExpr *DIE, const InitListExpr *ILE, + const ASTContext *Context) + : ILE(ILE), Context(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::advanceToNextField(const Expr *Init) { + // Remove all designators that refer to the last field of a struct or final + // element of the array. + while (!DesignatorList.empty()) { + auto &CurrentDesignator = DesignatorList.back(); + CurrentDesignator.advanceToNextField(); + 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; + + if (!enterImplicitInitLists(Init)) { + DesignatorList.clear(); + return false; + } + + return true; +} + +bool Designators::enterImplicitInitLists(const Expr *Init) { + // Check for missing braces by comparing the type of the last designator and + // type of Init. + 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; + llvm::raw_string_ostream OS(Designator); + for (auto &I : DesignatorList) { + switch (I.getTag()) { + case Designator::STRUCT: + OS << '.' << I.getStructIter()->getName(); + break; + case Designator::ARRAY: + OS << '[' << I.getArrayIndex() << ']'; + break; + case Designator::ARRAY_RANGE: + OS << '[' << I.getArrayRangeStart() << "..." << I.getArrayRangeEnd() + << ']'; + } + } + OS << " = "; + return Designator; +} + +} // namespace reorder_fields +} // namespace clang diff --git a/clang-tools-extra/clang-reorder-fields/Designator.h b/clang-tools-extra/clang-reorder-fields/Designator.h new file mode 100644 index 0000000000000..09c0a6ce27e26 --- /dev/null +++ b/clang-tools-extra/clang-reorder-fields/Designator.h @@ -0,0 +1,165 @@ +//===-- 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 Designator and Designators +/// utility classes. +/// +//===----------------------------------------------------------------------===// + +#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 { + +/// Represents a part of a designation in a C99/C++20 designated initializer. It +/// is a tagged union of diff erent kinds of designators: struct, array and array +/// range. Holds enough information to be able to advance to the next field and +/// to know when all fields have been iterated through. +class Designator { +public: + enum Kind { STRUCT, ARRAY, ARRAY_RANGE }; + + Designator(const QualType Type, RecordDecl::field_iterator Field, + const RecordDecl *RD) + : Tag(STRUCT), Type(Type), StructIt({Field, RD}) {} + + Designator(const QualType Type, uint64_t Idx, uint64_t Size) + : Tag(ARRAY), Type(Type), ArrayIt({Idx, Size}) {} + + Designator(const QualType Type, uint64_t Start, uint64_t End, uint64_t Size) + : Tag(ARRAY_RANGE), Type(Type), ArrayRangeIt({Start, End, Size}) {} + + /// Moves the iterator to the next element. + void advanceToNextField(); + + /// Checks if the iterator has iterated through all elements. + bool isFinished(); + + Kind getTag() const { return Tag; } + QualType getType() const { return Type; } + + const RecordDecl::field_iterator getStructIter() const { + assert(Tag == STRUCT && "Must be a field designator"); + return StructIt.Field; + } + + const RecordDecl *getStructDecl() const { + assert(Tag == STRUCT && "Must be a field designator"); + return StructIt.Record; + } + + uint64_t getArrayIndex() const { + assert(Tag == ARRAY && "Must be an array designator"); + return ArrayIt.Index; + } + + uint64_t getArrayRangeStart() const { + assert(Tag == ARRAY_RANGE && "Must be an array range designator"); + return ArrayRangeIt.Start; + } + + uint64_t getArrayRangeEnd() const { + assert(Tag == ARRAY_RANGE && "Must be an array range designator"); + return ArrayRangeIt.End; + } + + uint64_t getArraySize() const { + assert((Tag == ARRAY || Tag == ARRAY_RANGE) && + "Must be an array or range designator"); + if (Tag == ARRAY) + return ArrayIt.Size; + return ArrayRangeIt.Size; + } + +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; + }; +}; + +/// List of designators. +class Designators { +public: + /// Initialize to the first member of the struct/array. Enters implicit + /// initializer lists until a type that matches Init is found. + Designators(const Expr *Init, const InitListExpr *ILE, + const ASTContext *Context); + + /// Initialize to the designators of the given expression. + Designators(const DesignatedInitExpr *DIE, const InitListExpr *ILE, + const ASTContext *Context); + + /// Return whether this designator list is valid. + bool isValid() const { return !DesignatorList.empty(); } + + /// 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 advanceToNextField(const Expr *Init); + + /// 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(); } + + SmallVector<Designator>::const_iterator begin() const { + return DesignatorList.begin(); + } + SmallVector<Designator>::const_iterator end() const { + return DesignatorList.end(); + } + +private: + /// Enters any implicit initializer lists until a type that matches the given + /// expression is found. + bool enterImplicitInitLists(const Expr *Init); + + const InitListExpr *ILE; + const ASTContext *Context; + SmallVector<Designator, 1> DesignatorList; +}; + +} // namespace reorder_fields +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_UTILS_DESIGNATOR_H diff --git a/clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp b/clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp index ada9122b587ae..affa276a0c550 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 "Designator.h" #include "clang/AST/AST.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" @@ -25,6 +26,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 { @@ -162,7 +164,91 @@ 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; + } + + /// 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. + bool operator()(const Designator &Lhs, const Designator &Rhs) const; + + /// 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. + bool operator()(const Designators &Lhs, const Designators &Rhs) const; + + const RecordDecl *Definition; + ArrayRef<unsigned> NewFieldsOrder; + SmallVector<unsigned, 4> NewFieldsPositions; +}; + +bool ReorderedStruct::operator()(const Designator &Lhs, + const Designator &Rhs) const { + switch (Lhs.getTag()) { + case Designator::STRUCT: + assert(Rhs.getTag() == Designator::STRUCT && "Incompatible designators"); + assert(Lhs.getStructDecl() == Rhs.getStructDecl() && + "Incompatible structs"); + // Use the new layout for reordered struct. + if (Definition == Lhs.getStructDecl()) { + return NewFieldsPositions[Lhs.getStructIter()->getFieldIndex()] < + NewFieldsPositions[Rhs.getStructIter()->getFieldIndex()]; + } + return Lhs.getStructIter()->getFieldIndex() < + Rhs.getStructIter()->getFieldIndex(); + case Designator::ARRAY: + case Designator::ARRAY_RANGE: + // Array designators can be compared to array range designators. + assert((Rhs.getTag() == Designator::ARRAY || + Rhs.getTag() == Designator::ARRAY_RANGE) && + "Incompatible designators"); + size_t LhsIdx = Lhs.getTag() == Designator::ARRAY + ? Lhs.getArrayIndex() + : Lhs.getArrayRangeStart(); + size_t RhsIdx = Rhs.getTag() == Designator::ARRAY + ? Rhs.getArrayIndex() + : Rhs.getArrayRangeStart(); + return LhsIdx < RhsIdx; + } + llvm_unreachable("Invalid designator tag"); +} + +bool ReorderedStruct::operator()(const Designators &Lhs, + const Designators &Rhs) const { + return std::lexicographical_compare(Lhs.begin(), Lhs.end(), Rhs.begin(), + Rhs.end(), *this); +} + // 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, @@ -174,10 +260,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 @@ -279,33 +362,33 @@ static SourceRange getFullFieldSourceRange(const FieldDecl &Field, /// diff erent 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 diff erent 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; } @@ -316,7 +399,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"); @@ -328,10 +411,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()) { @@ -342,8 +421,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() + @@ -361,8 +440,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() == @@ -374,35 +453,188 @@ static void reorderFieldsInConstructor( Replacements); } +/// Replacement for broken InitListExpr::isExplicit function. +/// FIXME: 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; +} + +/// 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; +} + +static void reportError(const Twine &Message, SourceLocation Loc, + const SourceManager &SM) { + if (Loc.isValid()) { + llvm::errs() << SM.getFilename(Loc) << ":" << SM.getPresumedLineNumber(Loc) + << ":" << SM.getPresumedColumnNumber(Loc) << ": "; + } + llvm::errs() << Message; +} + /// 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 HasImplicitInit = false; + bool HasDesignatedInit = 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; + + HasImplicitInit = InitListEx->getNumInits() != SynILE->getNumInits(); + HasDesignatedInit = 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. + HasImplicitInit = + (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 (HasImplicitInit || HasDesignatedInit) { + // Designators are only supported from C++20. + if (!HasDesignatedInit && Context.getLangOpts().CPlusPlus && + !Context.getLangOpts().CPlusPlus20) { + reportError( + "Only full initialization without implicit values is supported\n", + InitListEx->getBeginLoc(), Context.getSourceManager()); + 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. + std::optional<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.emplace(DIE, SyntacticInitListEx, &Context); + if (!CurrentDesignators->isValid()) { + reportError("Unsupported initializer list\n", DIE->getBeginLoc(), + Context.getSourceManager()); + return false; + } + + // Use the child of the DesignatedInitExpr. This way designators are + // always replaced. + Rewrites.emplace_back(*CurrentDesignators, DIE->getInit()); + } else { + // If designators are not initialized then initialize to the first + // field, otherwise move the next field. + if (!CurrentDesignators) { + CurrentDesignators.emplace(Init, SyntacticInitListEx, &Context); + if (!CurrentDesignators->isValid()) { + reportError("Unsupported initializer list\n", + InitListEx->getBeginLoc(), Context.getSourceManager()); + return false; + } + } else if (!CurrentDesignators->advanceToNextField(Init)) { + reportError("Unsupported initializer list\n", + InitListEx->getBeginLoc(), Context.getSourceManager()); + return false; + } + + // Do not rewrite implicit values. They just had to be processed to + // find the correct designator. + if (!isa<ImplicitValueInitExpr>(Init)) + Rewrites.emplace_back(*CurrentDesignators, Init); + } + } + + // Sort the designators according to the new order. + llvm::stable_sort(Rewrites, [&RS](const auto &Lhs, const auto &Rhs) { + return RS(Lhs.first, Rhs.first); + }); + + 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; } @@ -432,7 +664,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. @@ -440,24 +674,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/test/clang-reorder-fields/AggregatePartialInitialization.c b/clang-tools-extra/test/clang-reorder-fields/AggregatePartialInitialization.c new file mode 100644 index 0000000000000..7b531d9230c79 --- /dev/null +++ b/clang-tools-extra/test/clang-reorder-fields/AggregatePartialInitialization.c @@ -0,0 +1,12 @@ +// RUN: clang-reorder-fields -record-name Foo -fields-order z,y,x %s -- | FileCheck %s + +struct Foo { + int x; // CHECK: {{^ int z;}} + int y; // CHECK-NEXT: {{^ int y;}} + int z; // CHECK-NEXT: {{^ int x;}} +}; + +int main() { + struct Foo foo = { 0, 1 }; // CHECK: {{^ struct Foo foo = { .y = 1, .x = 0 };}} + return 0; +} diff --git a/clang-tools-extra/test/clang-reorder-fields/AggregatePartialInitialization.cpp b/clang-tools-extra/test/clang-reorder-fields/AggregatePartialInitialization.cpp index 9d09c8187751c..cda5f56e3ce8d 100644 --- a/clang-tools-extra/test/clang-reorder-fields/AggregatePartialInitialization.cpp +++ b/clang-tools-extra/test/clang-reorder-fields/AggregatePartialInitialization.cpp @@ -1,4 +1,5 @@ -// RUN: clang-reorder-fields -record-name Foo -fields-order z,y,x %s -- | FileCheck %s +// RUN: clang-reorder-fields --extra-arg="-std=c++17" -record-name Foo -fields-order z,y,x %s -- 2>&1 | FileCheck --check-prefix=CHECK-MESSAGES %s +// RUN: clang-reorder-fields --extra-arg="-std=c++17" -record-name Foo -fields-order z,y,x %s -- | FileCheck %s // The order of fields should not change. class Foo { @@ -9,6 +10,7 @@ class Foo { }; int main() { + // CHECK-MESSAGES: :[[@LINE+1]]:13: Only full initialization without implicit values is supported Foo foo = { 0, 1 }; // CHECK: {{^ Foo foo = { 0, 1 };}} return 0; } 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; +} diff --git a/clang-tools-extra/test/clang-reorder-fields/DesignatedInitializerList.cpp b/clang-tools-extra/test/clang-reorder-fields/DesignatedInitializerList.cpp new file mode 100644 index 0000000000000..15dac775b6115 --- /dev/null +++ b/clang-tools-extra/test/clang-reorder-fields/DesignatedInitializerList.cpp @@ -0,0 +1,24 @@ +// RUN: clang-reorder-fields --extra-arg="-std=c++20" -record-name Bar -fields-order c,a,b %s -- | FileCheck %s + +class Foo { +public: + const int* x; + int y; +}; + +class Bar { +public: + char a; // CHECK: {{^ int c;}} + Foo b; // CHECK-NEXT: {{^ char a;}} + int c; // CHECK-NEXT: {{^ Foo b;}} +}; + +int main() { + const int x = 13; + Bar bar1 = { 'a', { &x, 0 }, 123 }; // CHECK: {{^ Bar bar1 = { 123, 'a', { &x, 0 } };}} + Bar bar2 = { .a = 'a', { &x, 0 }, 123 }; // CHECK: {{^ Bar bar2 = { .c = 123, .a = 'a', .b = { &x, 0 } };}} + Bar bar3 = { 'a', .b { &x, 0 }, 123 }; // CHECK: {{^ Bar bar3 = { .c = 123, .a = 'a', .b = { &x, 0 } };}} + Bar bar4 = { .c = 123, .b { &x, 0 }, .a = 'a' }; // CHECK: {{^ Bar bar4 = { .c = 123, .a = 'a', .b = { &x, 0 } };}} + + return 0; +} diff --git a/clang-tools-extra/test/clang-reorder-fields/IdiomaticZeroInitializer.c b/clang-tools-extra/test/clang-reorder-fields/IdiomaticZeroInitializer.c new file mode 100644 index 0000000000000..59c12e81afc0a --- /dev/null +++ b/clang-tools-extra/test/clang-reorder-fields/IdiomaticZeroInitializer.c @@ -0,0 +1,14 @@ +// RUN: clang-reorder-fields -record-name Foo -fields-order y,x %s -- | FileCheck %s + +struct Foo { + int x; // CHECK: {{^ int y;}} + int y; // CHECK-NEXT: {{^ int x;}} +}; + +int main() { + // The idiomatic zero initializer should remain the same. + struct Foo foo0 = { 0 }; // CHECK: {{^ struct Foo foo0 = { 0 };}} + struct Foo foo1 = { 1 }; // CHECK: {{^ struct Foo foo1 = { .x = 1 };}} + + return 0; +} diff --git a/clang-tools-extra/test/clang-reorder-fields/InitializerListExcessElements.c b/clang-tools-extra/test/clang-reorder-fields/InitializerListExcessElements.c new file mode 100644 index 0000000000000..b626e84fea4a3 --- /dev/null +++ b/clang-tools-extra/test/clang-reorder-fields/InitializerListExcessElements.c @@ -0,0 +1,15 @@ +// RUN: clang-reorder-fields -record-name Foo -fields-order y,x %s -- 2>&1 | FileCheck --check-prefix=CHECK-MESSAGES %s +// RUN: clang-reorder-fields -record-name Foo -fields-order y,x %s -- | FileCheck %s + +// The order of fields should not change. +struct Foo { + int x; // CHECK: {{^ int x;}} + int y; // CHECK-NEXT: {{^ int y;}} +}; + +int main() { + // CHECK-MESSAGES: :[[@LINE+1]]:20: Unsupported initializer list + struct Foo foo = { .y=9, 123, .x=1 }; // CHECK: {{^ struct Foo foo = { .y=9, 123, .x=1 };}} + + return 0; +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits