diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -340,6 +340,41 @@ /* A comment. */ double e = 4; +**AlignConsecutiveAssignmentsOptions** (``AlignConsecutiveAssignmentsOptionsT``) :versionbadge:`clang-format 15` + Options for aligning consecutive assignments. They only take effect + when ``AlignConsecutiveAssignments`` is not ``None``. + + Nested configuration flags: + + + * ``bool AlignCompound`` Whether compound assignments like ``+=``'s are aligned along + with ``=``'s. + + .. code-block:: c++ + + true: + a &= 2; + bbb = 2; + + false: + a &= 2; + bbb = 2; + + * ``bool PadOperators`` Whether short assignment operators are left-padded to the same + length as long ones in order to put all assignment operators to + the right of the left hand side. + + .. code-block:: c++ + + true: + a >>= 2; + bbb = 2; + + false: + a >>= 2; + bbb = 2; + + **AlignConsecutiveBitFields** (``AlignConsecutiveStyle``) :versionbadge:`clang-format 11` Style of aligning consecutive bit field. diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -148,6 +148,39 @@ ACS_AcrossEmptyLinesAndComments }; + /// + struct AlignConsecutiveAssignmentsOptionsT { + /// Whether compound assignments like ``+=``'s are aligned along + /// with ``=``'s. + /// \code + /// true: + /// a &= 2; + /// bbb = 2; + /// + /// false: + /// a &= 2; + /// bbb = 2; + /// \endcode + bool AlignCompound; + /// Whether short assignment operators are left-padded to the same + /// length as long ones in order to put all assignment operators to + /// the right of the left hand side. + /// \code + /// true: + /// a >>= 2; + /// bbb = 2; + /// + /// false: + /// a >>= 2; + /// bbb = 2; + /// \endcode + bool PadOperators; + }; + /// Options for aligning consecutive assignments. They only take effect + /// when ``AlignConsecutiveAssignments`` is not ``None``. + /// \version 15 + AlignConsecutiveAssignmentsOptionsT AlignConsecutiveAssignmentsOptions; + /// Style of aligning consecutive macro definitions. /// /// ``Consecutive`` will result in formattings like: @@ -3920,6 +3953,10 @@ AlignAfterOpenBracket == R.AlignAfterOpenBracket && AlignArrayOfStructures == R.AlignArrayOfStructures && AlignConsecutiveAssignments == R.AlignConsecutiveAssignments && + AlignConsecutiveAssignmentsOptions.AlignCompound == + R.AlignConsecutiveAssignmentsOptions.AlignCompound && + AlignConsecutiveAssignmentsOptions.PadOperators == + R.AlignConsecutiveAssignmentsOptions.PadOperators && AlignConsecutiveBitFields == R.AlignConsecutiveBitFields && AlignConsecutiveDeclarations == R.AlignConsecutiveDeclarations && AlignConsecutiveMacros == R.AlignConsecutiveMacros && diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -152,6 +152,15 @@ } }; +template <> +struct MappingTraits { + static void mapping(IO &IO, + FormatStyle::AlignConsecutiveAssignmentsOptionsT &Value) { + IO.mapOptional("AlignCompound", Value.AlignCompound); + IO.mapOptional("PadOperators", Value.PadOperators); + } +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, FormatStyle::AlignConsecutiveStyle &Value) { IO.enumCase(Value, "None", FormatStyle::ACS_None); @@ -600,13 +609,15 @@ IO.mapOptional("AccessModifierOffset", Style.AccessModifierOffset); IO.mapOptional("AlignAfterOpenBracket", Style.AlignAfterOpenBracket); IO.mapOptional("AlignArrayOfStructures", Style.AlignArrayOfStructures); - IO.mapOptional("AlignConsecutiveMacros", Style.AlignConsecutiveMacros); IO.mapOptional("AlignConsecutiveAssignments", Style.AlignConsecutiveAssignments); + IO.mapOptional("AlignConsecutiveAssignmentsOptions", + Style.AlignConsecutiveAssignmentsOptions); IO.mapOptional("AlignConsecutiveBitFields", Style.AlignConsecutiveBitFields); IO.mapOptional("AlignConsecutiveDeclarations", Style.AlignConsecutiveDeclarations); + IO.mapOptional("AlignConsecutiveMacros", Style.AlignConsecutiveMacros); IO.mapOptional("AlignEscapedNewlines", Style.AlignEscapedNewlines); IO.mapOptional("AlignOperands", Style.AlignOperands); IO.mapOptional("AlignTrailingComments", Style.AlignTrailingComments); @@ -1138,6 +1149,8 @@ LLVMStyle.AlignOperands = FormatStyle::OAS_Align; LLVMStyle.AlignTrailingComments = true; LLVMStyle.AlignConsecutiveAssignments = FormatStyle::ACS_None; + LLVMStyle.AlignConsecutiveAssignmentsOptions = {/*AlignCompound=*/false, + /*PadOperators=*/true}; LLVMStyle.AlignConsecutiveBitFields = FormatStyle::ACS_None; LLVMStyle.AlignConsecutiveDeclarations = FormatStyle::ACS_None; LLVMStyle.AlignConsecutiveMacros = FormatStyle::ACS_None; diff --git a/clang/lib/Format/WhitespaceManager.cpp b/clang/lib/Format/WhitespaceManager.cpp --- a/clang/lib/Format/WhitespaceManager.cpp +++ b/clang/lib/Format/WhitespaceManager.cpp @@ -267,10 +267,14 @@ } // Align a single sequence of tokens, see AlignTokens below. +// Column - The token for which Matches returns true is moved to this +// column. +// RightJustify - Whether it is the token's right end or left end that +// gets moved to that column. template static void AlignTokenSequence(const FormatStyle &Style, unsigned Start, unsigned End, - unsigned Column, F &&Matches, + unsigned Column, bool RightJustify, F &&Matches, SmallVector &Changes) { bool FoundMatchOnLine = false; int Shift = 0; @@ -329,7 +333,8 @@ // shifted by the same amount if (!FoundMatchOnLine && !SkipMatchCheck && Matches(Changes[i])) { FoundMatchOnLine = true; - Shift = Column - Changes[i].StartOfTokenColumn; + Shift = Column - RightJustify * Changes[i].TokenLength - + Changes[i].StartOfTokenColumn; Changes[i].Spaces += Shift; // FIXME: This is a workaround that should be removed when we fix // http://llvm.org/PR53699. An assertion later below verifies this. @@ -456,13 +461,35 @@ // However, the special exception is that we do NOT skip function parameters // that are split across multiple lines. See the test case in FormatTest.cpp // that mentions "split function parameter alignment" for an example of this. +// When the parameter RightJustify is true, the operator will be +// right-justified. It is used to align compound assignments like `+=` +// and `=`. +// When RightJustify and PadAnchors are true, operators in each block to +// be aligned will be padded on the left to the same length before +// aligning. template static unsigned AlignTokens( const FormatStyle &Style, F &&Matches, SmallVector &Changes, unsigned StartAt, - const FormatStyle::AlignConsecutiveStyle &ACS = FormatStyle::ACS_None) { - unsigned MinColumn = 0; - unsigned MaxColumn = UINT_MAX; + const FormatStyle::AlignConsecutiveStyle &ACS = FormatStyle::ACS_None, + bool RightJustify = false, bool PadAnchors = false) { + // We arrange each line in 3 parts. The operator to be aligned (the + // anchor), and text to its left and right. In the aligned text the + // width of each part will be the maximum of that over the block that + // has been aligned. + // Maximum widths of each part so far. + // When RightJustify is true and PadAnchors is false, the part from + // start of line to the right end of the anchor. Otherwise, only the + // part to the left of the anchor. Including the space that exists on + // its left from the start. Not including the padding added on the + // left to right-justify the anchor. + unsigned WidthLeft = 0; + // The operator to be aligned when RightJustify is true and PadAnchors + // is false. 0 otherwise. + unsigned WidthAnchor = 0; + // Width to the right of the anchor. Plus width of the anchor when + // RightJustify is false. + unsigned WidthRight = 0; // Line number of the start and the end of the current token sequence. unsigned StartOfSequence = 0; @@ -495,10 +522,12 @@ // containing any matching token to be aligned and located after such token. auto AlignCurrentSequence = [&] { if (StartOfSequence > 0 && StartOfSequence < EndOfSequence) - AlignTokenSequence(Style, StartOfSequence, EndOfSequence, MinColumn, - Matches, Changes); - MinColumn = 0; - MaxColumn = UINT_MAX; + AlignTokenSequence(Style, StartOfSequence, EndOfSequence, + WidthLeft + WidthAnchor, RightJustify, Matches, + Changes); + WidthLeft = 0; + WidthAnchor = 0; + WidthRight = 0; StartOfSequence = 0; EndOfSequence = 0; }; @@ -563,29 +592,44 @@ if (StartOfSequence == 0) StartOfSequence = i; - unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn; - int LineLengthAfter = Changes[i].TokenLength; + unsigned ChangeWidthLeft = Changes[i].StartOfTokenColumn; + unsigned ChangeWidthAnchor = 0; + unsigned ChangeWidthRight = 0; + if (RightJustify) { + if (PadAnchors) + ChangeWidthAnchor = Changes[i].TokenLength; + else + ChangeWidthLeft += Changes[i].TokenLength; + } else + ChangeWidthRight = Changes[i].TokenLength; for (unsigned j = i + 1; j != e && Changes[j].NewlinesBefore == 0; ++j) { - LineLengthAfter += Changes[j].Spaces; + ChangeWidthRight += Changes[j].Spaces; // Changes are generally 1:1 with the tokens, but a change could also be // inside of a token, in which case it's counted more than once: once for // the whitespace surrounding the token (!IsInsideToken) and once for // each whitespace change within it (IsInsideToken). // Therefore, changes inside of a token should only count the space. if (!Changes[j].IsInsideToken) - LineLengthAfter += Changes[j].TokenLength; + ChangeWidthRight += Changes[j].TokenLength; } - unsigned ChangeMaxColumn = Style.ColumnLimit - LineLengthAfter; // If we are restricted by the maximum column width, end the sequence. - if (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn || - CommasBeforeLastMatch != CommasBeforeMatch) { + unsigned NewLeft = std::max(ChangeWidthLeft, WidthLeft); + unsigned NewAnchor = std::max(ChangeWidthAnchor, WidthAnchor); + unsigned NewRight = std::max(ChangeWidthRight, WidthRight); + // `ColumnLimit == 0` means there is no column limit. + if (Style.ColumnLimit != 0 && + Style.ColumnLimit < NewLeft + NewAnchor + NewRight) { AlignCurrentSequence(); StartOfSequence = i; + WidthLeft = ChangeWidthLeft; + WidthAnchor = ChangeWidthAnchor; + WidthRight = ChangeWidthRight; + } else { + WidthLeft = NewLeft; + WidthAnchor = NewAnchor; + WidthRight = NewRight; } - - MinColumn = std::max(MinColumn, ChangeMinColumn); - MaxColumn = std::min(MaxColumn, ChangeMaxColumn); } EndOfSequence = i; @@ -760,9 +804,13 @@ if (Previous && Previous->is(tok::kw_operator)) return false; - return C.Tok->is(tok::equal); + return Style.AlignConsecutiveAssignmentsOptions.AlignCompound + ? C.Tok->getPrecedence() == prec::Assignment + : C.Tok->is(tok::equal); }, - Changes, /*StartAt=*/0, Style.AlignConsecutiveAssignments); + Changes, /*StartAt=*/0, Style.AlignConsecutiveAssignments, + /*RightJustify=*/true, + /*PadAnchors=*/Style.AlignConsecutiveAssignmentsOptions.PadOperators); } void WhitespaceManager::alignConsecutiveBitFields() { diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -16392,6 +16392,157 @@ Alignment)); } +TEST_F(FormatTest, AlignCompoundAssignments) { + FormatStyle Alignment = getLLVMStyle(); + Alignment.AlignConsecutiveAssignments = FormatStyle::ACS_Consecutive; + Alignment.AlignConsecutiveAssignmentsOptions.AlignCompound = true; + Alignment.AlignConsecutiveAssignmentsOptions.PadOperators = false; + verifyFormat("aa <= 5;\n" + "a = 5;\n" + "bcd = 5;\n" + "ghtyf = 5;\n" + "dvfvdb = 5;\n" + "a = 5;\n" + "vdsvsv = 5;\n" + "sfdbddfbdfbb = 5;\n" + "dvsdsv = 5;\n" + "int dsvvdvsdvvv = 123;", + Alignment); + verifyFormat("aa <= 5;\n" + "a &= 5;\n" + "bcd *= 5;\n" + "ghtyf += 5;\n" + "dvfvdb -= 5;\n" + "a /= 5;\n" + "vdsvsv %= 5;\n" + "sfdbddfbdfbb ^= 5;\n" + "dvsdsv |= 5;\n" + "int dsvvdvsdvvv = 123;", + Alignment); + verifyFormat("a &= 5;\n" + "bcd *= 5;\n" + "ghtyf >>= 5;\n" + "dvfvdb -= 5;\n" + "a /= 5;\n" + "aa <= 5;\n" + "vdsvsv %= 5;\n" + "sfdbddfbdfbb ^= 5;\n" + "dvsdsv <<= 5;\n" + "int dsvvdvsdvvv = 123;", + Alignment); + Alignment.AlignConsecutiveAssignmentsOptions.PadOperators = true; + verifyFormat("aa <= 5;\n" + "a = 5;\n" + "bcd = 5;\n" + "ghtyf = 5;\n" + "dvfvdb = 5;\n" + "a = 5;\n" + "vdsvsv = 5;\n" + "sfdbddfbdfbb = 5;\n" + "dvsdsv = 5;\n" + "int dsvvdvsdvvv = 123;", + Alignment); + verifyFormat("aa <= 5;\n" + "a &= 5;\n" + "bcd *= 5;\n" + "ghtyf += 5;\n" + "dvfvdb -= 5;\n" + "a /= 5;\n" + "vdsvsv %= 5;\n" + "sfdbddfbdfbb ^= 5;\n" + "dvsdsv |= 5;\n" + "int dsvvdvsdvvv = 123;", + Alignment); + verifyFormat("a &= 5;\n" + "bcd *= 5;\n" + "ghtyf >>= 5;\n" + "dvfvdb -= 5;\n" + "a /= 5;\n" + "aa <= 5;\n" + "vdsvsv %= 5;\n" + "sfdbddfbdfbb ^= 5;\n" + "dvsdsv <<= 5;\n" + "int dsvvdvsdvvv = 123;", + Alignment); + Alignment.AlignConsecutiveAssignments = FormatStyle::ACS_Consecutive; + EXPECT_EQ("int a += 5;\n" + "int one = 1;\n" + "\n" + "int oneTwoThree = 123;\n", + format("int a += 5;\n" + "int one = 1;\n" + "\n" + "int oneTwoThree = 123;\n", + Alignment)); + EXPECT_EQ("int a += 5;\n" + "int one = 1;\n" + "//\n" + "int oneTwoThree = 123;\n", + format("int a += 5;\n" + "int one = 1;\n" + "//\n" + "int oneTwoThree = 123;\n", + Alignment)); + Alignment.AlignConsecutiveAssignments = FormatStyle::ACS_AcrossEmptyLines; + EXPECT_EQ("int a += 5;\n" + "int one = 1;\n" + "\n" + "int oneTwoThree = 123;\n", + format("int a += 5;\n" + "int one = 1;\n" + "\n" + "int oneTwoThree = 123;\n", + Alignment)); + EXPECT_EQ("int a += 5;\n" + "int one = 1;\n" + "//\n" + "int oneTwoThree = 123;\n", + format("int a += 5;\n" + "int one = 1;\n" + "//\n" + "int oneTwoThree = 123;\n", + Alignment)); + Alignment.AlignConsecutiveAssignments = FormatStyle::ACS_AcrossComments; + EXPECT_EQ("int a += 5;\n" + "int one = 1;\n" + "\n" + "int oneTwoThree = 123;\n", + format("int a += 5;\n" + "int one = 1;\n" + "\n" + "int oneTwoThree = 123;\n", + Alignment)); + EXPECT_EQ("int a += 5;\n" + "int one = 1;\n" + "//\n" + "int oneTwoThree = 123;\n", + format("int a += 5;\n" + "int one = 1;\n" + "//\n" + "int oneTwoThree = 123;\n", + Alignment)); + Alignment.AlignConsecutiveAssignments = + FormatStyle::ACS_AcrossEmptyLinesAndComments; + EXPECT_EQ("int a += 5;\n" + "int one >>= 1;\n" + "\n" + "int oneTwoThree = 123;\n", + format("int a += 5;\n" + "int one >>= 1;\n" + "\n" + "int oneTwoThree = 123;\n", + Alignment)); + EXPECT_EQ("int a += 5;\n" + "int one = 1;\n" + "//\n" + "int oneTwoThree <<= 123;\n", + format("int a += 5;\n" + "int one = 1;\n" + "//\n" + "int oneTwoThree <<= 123;\n", + Alignment)); +} + TEST_F(FormatTest, AlignConsecutiveAssignments) { FormatStyle Alignment = getLLVMStyle(); Alignment.AlignConsecutiveMacros = FormatStyle::ACS_Consecutive; @@ -16410,7 +16561,8 @@ verifyFormat("int a = method();\n" "int oneTwoThree = 133;", Alignment); - verifyFormat("a &= 5;\n" + verifyFormat("aa <= 5;\n" + "a &= 5;\n" "bcd *= 5;\n" "ghtyf += 5;\n" "dvfvdb -= 5;\n" @@ -19297,6 +19449,8 @@ CHECK_PARSE_BOOL(SpaceBeforeSquareBrackets); CHECK_PARSE_BOOL(UseCRLF); + CHECK_PARSE_NESTED_BOOL(AlignConsecutiveAssignmentsOptions, AlignCompound); + CHECK_PARSE_NESTED_BOOL(AlignConsecutiveAssignmentsOptions, PadOperators); CHECK_PARSE_NESTED_BOOL(BraceWrapping, AfterCaseLabel); CHECK_PARSE_NESTED_BOOL(BraceWrapping, AfterClass); CHECK_PARSE_NESTED_BOOL(BraceWrapping, AfterEnum);