Skip to content

Conversation

@gedare
Copy link
Contributor

@gedare gedare commented Sep 12, 2024

Replace the AlwaysBreak and BlockIndent suboptions of AlignAfterOpenBracket with new style options BreakAfterOpenBracket* and BreakBeforeCloseBracket* for * in BracedList for braced list initializers, if for if conditional statements, Loop for loop control statements (for/while), Switch for switch statements, and Function for function calls/declarations/definitions.

Deprecates AlwaysBreak and BlockIndent.

Fixes #67738
Fixes #79176
Fixes #80123
Fixes #151844

@llvmbot llvmbot added clang Clang issues not falling into any other category clang-format labels Sep 12, 2024
@llvmbot
Copy link
Member

llvmbot commented Sep 12, 2024

@llvm/pr-subscribers-clang-format

@llvm/pr-subscribers-clang

Author: Gedare Bloom (gedare)

Changes

Introduce sub-options for AlignAfterOpenBracketBreak to allow for control of AlignAfterOpenBracket with AlwaysBreak and BlockIndent selectively for if conditional statements (as currently supported), other conditional statements (for/while/switch), and other statements.

Fixes #67738
Fixes #79176
Fixes #80123


Patch is 29.57 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/108332.diff

8 Files Affected:

  • (modified) clang/docs/ClangFormatStyleOptions.rst (+60)
  • (modified) clang/docs/ReleaseNotes.rst (+2)
  • (modified) clang/include/clang/Format/Format.h (+73)
  • (modified) clang/lib/Format/ContinuationIndenter.cpp (+32-9)
  • (modified) clang/lib/Format/Format.cpp (+26)
  • (modified) clang/lib/Format/TokenAnnotator.cpp (+7-1)
  • (modified) clang/unittests/Format/ConfigParseTest.cpp (+12)
  • (modified) clang/unittests/Format/FormatTest.cpp (+143)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index a427d7cd40fcdd..62462c3202bee0 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -246,6 +246,66 @@ the configuration (without a prefix: ``Auto``).
 
 
 
+.. _AlignAfterOpenBracketBreak:
+
+**AlignAfterOpenBracketBreak** (``AlignAfterOpenBracketCustom``) :versionbadge:`clang-format 20` :ref:`¶ <AlignAfterOpenBracketBreak>`
+  Control of when ``AlignAfterOpenBracket`` breaks an opening bracket.
+
+  If ``AlignAfterOpenBracket`` is set to ``AlwaysBreak`` or ``BlockIndent``,
+  use this to specify how different cases of breaking the opening brackets
+  should be handled. Otherwise, this is ignored. Setting any of these to
+  ``false`` will cause them to not break. At least one of these must be set
+  to ``true``, otherwise a default (backward compatible) breaking behavior
+  is used. This is ignored for ``Align`` and ``DontAlign``.
+
+  .. code-block:: c++
+
+    # Example of usage:
+    AlignAfterOpenBracket: AlwaysBreak
+    AlignAfterOpenBracketBreak:
+      InIfConditionalStatements: true
+      InOtherConditionalStatements: false
+      Other: true
+
+  Nested configuration flags:
+
+  Precise control over breaking the opening bracket of
+  ``AlignAfterOpenBracket``.
+
+  .. code-block:: c++
+
+    # Should be declared this way:
+    AlignAfterOpenBracketBreak:
+      InIfConditionalStatements: true
+      InOtherConditionalStatements: false
+      Other: true
+
+  * ``bool InIfConditionalStatements`` Break inside if/else if statements.
+
+    .. code-block:: c++
+
+       true:                                  false:
+       if constexpr (                   vs.   if constexpr (a ||
+          a || b)                                           b)
+
+  * ``bool InOtherConditionalStatements`` Break inside conditional statements not covered by preceding options.
+    (``for/while/switch...``).
+
+    .. code-block:: c++
+
+       true:                                  false:
+       while (                          vs.   while (a &&
+          a && b ) {                                 b) {
+
+  * ``bool Other`` Break inside brackets not covered by preceding options.
+
+    .. code-block:: c++
+
+       true:                                  false:
+       someLongFunction(                vs.   someLongFunction(argument1,
+          argument1, argument2);                               argument2);
+
+
 .. _AlignArrayOfStructures:
 
 **AlignArrayOfStructures** (``ArrayInitializerAlignmentStyle``) :versionbadge:`clang-format 13` :ref:`¶ <AlignArrayOfStructures>`
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 9860b25f2e7fa6..2141f6b2ad1d41 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -502,6 +502,8 @@ clang-format
 ------------
 
 - Adds ``BreakBinaryOperations`` option.
+- Adds ``AlignAfterOpenBracketBreak`` sub-options for better control of
+  ``AlignAfterOpenBracket`` with ``AlwaysBreak`` or ``BlockIndent`` modes.
 
 libclang
 --------
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index d8b62c7652a0f6..6e173d6d2b459f 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -106,6 +106,78 @@ struct FormatStyle {
   /// \version 3.8
   BracketAlignmentStyle AlignAfterOpenBracket;
 
+  /// Precise control over breaking the opening bracket of
+  /// ``AlignAfterOpenBracket``.
+  /// \code
+  ///   # Should be declared this way:
+  ///   AlignAfterOpenBracketBreak:
+  ///     InIfConditionalStatements: true
+  ///     InOtherConditionalStatements: false
+  ///     Other: true
+  /// \endcode
+  struct AlignAfterOpenBracketCustom {
+    /// Break inside if/else if statements.
+    /// \code
+    ///    true:                                  false:
+    ///    if constexpr (                   vs.   if constexpr (a ||
+    ///       a || b)                                           b)
+    /// \endcode
+    bool InIfConditionalStatements;
+    /// Break inside conditional statements not covered by preceding options.
+    /// (``for/while/switch...``).
+    /// \code
+    ///    true:                                  false:
+    ///    while (                          vs.   while (a &&
+    ///       a && b ) {                                 b) {
+    /// \endcode
+    bool InOtherConditionalStatements;
+    /// Break inside brackets not covered by preceding options.
+    /// \code
+    ///    true:                                  false:
+    ///    someLongFunction(                vs.   someLongFunction(argument1,
+    ///       argument1, argument2);                               argument2);
+    /// \endcode
+    bool Other;
+
+    AlignAfterOpenBracketCustom()
+        : InIfConditionalStatements(false), InOtherConditionalStatements(false),
+          Other(false) {}
+
+    AlignAfterOpenBracketCustom(bool InIfConditionalStatements,
+                                bool InOtherConditionalStatements, bool Other)
+        : InIfConditionalStatements(InIfConditionalStatements),
+          InOtherConditionalStatements(InOtherConditionalStatements),
+          Other(Other) {}
+
+    bool operator==(const AlignAfterOpenBracketCustom &R) const {
+      return InIfConditionalStatements == R.InIfConditionalStatements &&
+             InOtherConditionalStatements == R.InOtherConditionalStatements &&
+             Other == R.Other;
+    }
+    bool operator!=(const AlignAfterOpenBracketCustom &R) const {
+      return !(*this == R);
+    }
+  };
+
+  /// Control of when ``AlignAfterOpenBracket`` breaks an opening bracket.
+  ///
+  /// If ``AlignAfterOpenBracket`` is set to ``AlwaysBreak`` or ``BlockIndent``,
+  /// use this to specify how different cases of breaking the opening brackets
+  /// should be handled. Otherwise, this is ignored. Setting any of these to
+  /// ``false`` will cause them to not break. At least one of these must be set
+  /// to ``true``, otherwise a default (backward compatible) breaking behavior
+  /// is used. This is ignored for ``Align`` and ``DontAlign``.
+  /// \code
+  ///   # Example of usage:
+  ///   AlignAfterOpenBracket: AlwaysBreak
+  ///   AlignAfterOpenBracketBreak:
+  ///     InIfConditionalStatements: true
+  ///     InOtherConditionalStatements: false
+  ///     Other: true
+  /// \endcode
+  /// \version 20
+  AlignAfterOpenBracketCustom AlignAfterOpenBracketBreak;
+
   /// Different style for aligning array initializers.
   enum ArrayInitializerAlignmentStyle : int8_t {
     /// Align array column and left justify the columns e.g.:
@@ -5060,6 +5132,7 @@ struct FormatStyle {
   bool operator==(const FormatStyle &R) const {
     return AccessModifierOffset == R.AccessModifierOffset &&
            AlignAfterOpenBracket == R.AlignAfterOpenBracket &&
+           AlignAfterOpenBracketBreak == R.AlignAfterOpenBracketBreak &&
            AlignArrayOfStructures == R.AlignArrayOfStructures &&
            AlignConsecutiveAssignments == R.AlignConsecutiveAssignments &&
            AlignConsecutiveBitFields == R.AlignConsecutiveBitFields &&
diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp
index f29f8796ea9290..673e8ca54ddc03 100644
--- a/clang/lib/Format/ContinuationIndenter.cpp
+++ b/clang/lib/Format/ContinuationIndenter.cpp
@@ -795,6 +795,9 @@ void ContinuationIndenter::addTokenOnCurrentLine(LineState &State, bool DryRun,
   // parenthesis by disallowing any further line breaks if there is no line
   // break after the opening parenthesis. Don't break if it doesn't conserve
   // columns.
+  auto IsOtherConditional = [](const FormatToken &Tok) {
+    return Tok.isOneOf(tok::kw_for, tok::kw_while, tok::kw_switch);
+  };
   auto IsOpeningBracket = [&](const FormatToken &Tok) {
     auto IsStartOfBracedList = [&]() {
       return Tok.is(tok::l_brace) && Tok.isNot(BK_Block) &&
@@ -807,9 +810,11 @@ void ContinuationIndenter::addTokenOnCurrentLine(LineState &State, bool DryRun,
     if (!Tok.Previous)
       return true;
     if (Tok.Previous->isIf())
-      return Style.AlignAfterOpenBracket == FormatStyle::BAS_AlwaysBreak;
-    return !Tok.Previous->isOneOf(TT_CastRParen, tok::kw_for, tok::kw_while,
-                                  tok::kw_switch);
+      return Style.AlignAfterOpenBracketBreak.InIfConditionalStatements;
+    if (IsOtherConditional(*Tok.Previous))
+      return Style.AlignAfterOpenBracketBreak.InOtherConditionalStatements;
+    return !Tok.Previous->is(TT_CastRParen) &&
+           Style.AlignAfterOpenBracketBreak.Other;
   };
   auto IsFunctionCallParen = [](const FormatToken &Tok) {
     return Tok.is(tok::l_paren) && Tok.ParameterCount > 0 && Tok.Previous &&
@@ -844,10 +849,15 @@ void ContinuationIndenter::addTokenOnCurrentLine(LineState &State, bool DryRun,
          Tok.isOneOf(tok::ellipsis, Keywords.kw_await))) {
       return true;
     }
-    const auto *Previous = Tok.Previous;
-    if (!Previous || (!Previous->isOneOf(TT_FunctionDeclarationLParen,
-                                         TT_LambdaDefinitionLParen) &&
-                      !IsFunctionCallParen(*Previous))) {
+    const auto *Previous = TokAfterLParen.Previous;
+    assert(Previous); // IsOpeningBracket(Previous)
+    if (Previous->Previous && (Previous->Previous->isIf() ||
+                               IsOtherConditional(*Previous->Previous))) {
+      return false;
+    }
+    if (!Previous->isOneOf(TT_FunctionDeclarationLParen,
+                           TT_LambdaDefinitionLParen) &&
+        !IsFunctionCallParen(*Previous)) {
       return true;
     }
     if (IsOpeningBracket(Tok) || IsInTemplateString(Tok))
@@ -1225,8 +1235,21 @@ unsigned ContinuationIndenter::addTokenOnNewLine(LineState &State,
   }
 
   if (PreviousNonComment && PreviousNonComment->is(tok::l_paren)) {
-    CurrentState.BreakBeforeClosingParen =
-        Style.AlignAfterOpenBracket == FormatStyle::BAS_BlockIndent;
+    CurrentState.BreakBeforeClosingParen = false;
+    if (Style.AlignAfterOpenBracket == FormatStyle::BAS_BlockIndent) {
+      auto Previous = PreviousNonComment->Previous;
+      if (Previous && Previous->isIf()) {
+        CurrentState.BreakBeforeClosingParen =
+            Style.AlignAfterOpenBracketBreak.InIfConditionalStatements;
+      } else if (Previous && Previous->isOneOf(tok::kw_for, tok::kw_while,
+                                               tok::kw_switch)) {
+        CurrentState.BreakBeforeClosingParen =
+            Style.AlignAfterOpenBracketBreak.InOtherConditionalStatements;
+      } else {
+        CurrentState.BreakBeforeClosingParen =
+            Style.AlignAfterOpenBracketBreak.Other;
+      }
+    }
   }
 
   if (CurrentState.AvoidBinPacking) {
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index d2463b892fbb96..79069cb1678c91 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -216,6 +216,16 @@ template <> struct ScalarEnumerationTraits<FormatStyle::BracketAlignmentStyle> {
   }
 };
 
+template <> struct MappingTraits<FormatStyle::AlignAfterOpenBracketCustom> {
+  static void mapping(IO &IO, FormatStyle::AlignAfterOpenBracketCustom &Value) {
+    IO.mapOptional("InIfConditionalStatements",
+                   Value.InIfConditionalStatements);
+    IO.mapOptional("InOtherConditionalStatements",
+                   Value.InOtherConditionalStatements);
+    IO.mapOptional("Other", Value.Other);
+  }
+};
+
 template <>
 struct ScalarEnumerationTraits<
     FormatStyle::BraceWrappingAfterControlStatementStyle> {
@@ -922,6 +932,8 @@ template <> struct MappingTraits<FormatStyle> {
 
     IO.mapOptional("AccessModifierOffset", Style.AccessModifierOffset);
     IO.mapOptional("AlignAfterOpenBracket", Style.AlignAfterOpenBracket);
+    IO.mapOptional("AlignAfterOpenBracketBreak",
+                   Style.AlignAfterOpenBracketBreak);
     IO.mapOptional("AlignArrayOfStructures", Style.AlignArrayOfStructures);
     IO.mapOptional("AlignConsecutiveAssignments",
                    Style.AlignConsecutiveAssignments);
@@ -1155,6 +1167,18 @@ template <> struct MappingTraits<FormatStyle> {
     IO.mapOptional("WhitespaceSensitiveMacros",
                    Style.WhitespaceSensitiveMacros);
 
+    // If AlignAfterOpenBracket was specified but AlignAfterOpenBracketBreak
+    // was not, initialize the latter for backwards compatibility.
+    if ((Style.AlignAfterOpenBracket == FormatStyle::BAS_AlwaysBreak ||
+         Style.AlignAfterOpenBracket == FormatStyle::BAS_BlockIndent) &&
+        Style.AlignAfterOpenBracketBreak ==
+            FormatStyle::AlignAfterOpenBracketCustom()) {
+      if (Style.AlignAfterOpenBracket == FormatStyle::BAS_AlwaysBreak)
+        Style.AlignAfterOpenBracketBreak.InIfConditionalStatements = true;
+      Style.AlignAfterOpenBracketBreak.InOtherConditionalStatements = false;
+      Style.AlignAfterOpenBracketBreak.Other = true;
+    }
+
     // If AlwaysBreakAfterDefinitionReturnType was specified but
     // BreakAfterReturnType was not, initialize the latter from the former for
     // backwards compatibility.
@@ -1438,6 +1462,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
   FormatStyle LLVMStyle;
   LLVMStyle.AccessModifierOffset = -2;
   LLVMStyle.AlignAfterOpenBracket = FormatStyle::BAS_Align;
+  LLVMStyle.AlignAfterOpenBracketBreak = {};
   LLVMStyle.AlignArrayOfStructures = FormatStyle::AIAS_None;
   LLVMStyle.AlignConsecutiveAssignments = {};
   LLVMStyle.AlignConsecutiveAssignments.AcrossComments = false;
@@ -1750,6 +1775,7 @@ FormatStyle getGoogleStyle(FormatStyle::LanguageKind Language) {
     GoogleStyle.SpacesBeforeTrailingComments = 1;
   } else if (Language == FormatStyle::LK_JavaScript) {
     GoogleStyle.AlignAfterOpenBracket = FormatStyle::BAS_AlwaysBreak;
+    GoogleStyle.AlignAfterOpenBracketBreak = {true, false, true};
     GoogleStyle.AlignOperands = FormatStyle::OAS_DontAlign;
     GoogleStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_Empty;
     // TODO: still under discussion whether to switch to SLS_All.
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index dfa703aed0d34d..caa840f461da8f 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -6259,7 +6259,13 @@ bool TokenAnnotator::canBreakBefore(const AnnotatedLine &Line,
     if (Next && Next->is(tok::l_paren))
       return false;
     const FormatToken *Previous = Right.MatchingParen->Previous;
-    return !(Previous && (Previous->is(tok::kw_for) || Previous->isIf()));
+    if (!Previous)
+      return true;
+    if (Previous->isOneOf(tok::kw_for, tok::kw_while, tok::kw_switch))
+      return Style.AlignAfterOpenBracketBreak.InOtherConditionalStatements;
+    if (Previous->isIf())
+      return Style.AlignAfterOpenBracketBreak.InIfConditionalStatements;
+    return Style.AlignAfterOpenBracketBreak.Other;
   }
 
   if (Left.isOneOf(tok::r_paren, TT_TrailingAnnotation) &&
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index b8bdfaaa74e10e..c46a3e277e9134 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -203,6 +203,11 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
   CHECK_PARSE_BOOL(SpaceBeforeSquareBrackets);
   CHECK_PARSE_BOOL(VerilogBreakBetweenInstancePorts);
 
+  CHECK_PARSE_NESTED_BOOL(AlignAfterOpenBracketBreak,
+                          InIfConditionalStatements);
+  CHECK_PARSE_NESTED_BOOL(AlignAfterOpenBracketBreak,
+                          InOtherConditionalStatements);
+  CHECK_PARSE_NESTED_BOOL(AlignAfterOpenBracketBreak, Other);
   CHECK_PARSE_NESTED_BOOL(AlignConsecutiveShortCaseStatements, Enabled);
   CHECK_PARSE_NESTED_BOOL(AlignConsecutiveShortCaseStatements,
                           AcrossEmptyLines);
@@ -500,6 +505,13 @@ TEST(ConfigParseTest, ParsesConfiguration) {
               FormatStyle::BAS_DontAlign);
   CHECK_PARSE("AlignAfterOpenBracket: true", AlignAfterOpenBracket,
               FormatStyle::BAS_Align);
+  Style.AlignAfterOpenBracket = FormatStyle::BAS_Align;
+  Style.AlignAfterOpenBracketBreak = {};
+  CHECK_PARSE("AlignAfterOpenBracket: AlwaysBreak", AlignAfterOpenBracketBreak,
+              FormatStyle::AlignAfterOpenBracketCustom(true, false, true));
+  Style.AlignAfterOpenBracketBreak = {};
+  CHECK_PARSE("AlignAfterOpenBracket: BlockIndent", AlignAfterOpenBracketBreak,
+              FormatStyle::AlignAfterOpenBracketCustom(false, false, true));
 
   Style.AlignEscapedNewlines = FormatStyle::ENAS_Left;
   CHECK_PARSE("AlignEscapedNewlines: DontAlign", AlignEscapedNewlines,
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 5ebf0d7068dd6c..641b685127f2e4 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -5072,6 +5072,7 @@ TEST_F(FormatTest, BracedInitializerIndentWidth) {
   auto Style = getLLVMStyleWithColumns(60);
   Style.BinPackArguments = true;
   Style.AlignAfterOpenBracket = FormatStyle::BAS_AlwaysBreak;
+  Style.AlignAfterOpenBracketBreak = {true, false, true};
   Style.BracedInitializerIndentWidth = 6;
 
   // Non-initializing braces are unaffected by BracedInitializerIndentWidth.
@@ -7320,6 +7321,7 @@ TEST_F(FormatTest, ExpressionIndentationBreakingBeforeOperators) {
   Style = getLLVMStyleWithColumns(20);
   Style.AlignAfterOpenBracket = FormatStyle::BAS_AlwaysBreak;
   Style.BinPackParameters = FormatStyle::BPPS_OnePerLine;
+  Style.AlignAfterOpenBracketBreak = {true, false, true};
   Style.BreakBeforeBinaryOperators = FormatStyle::BOS_NonAssignment;
   Style.ContinuationIndentWidth = 2;
   verifyFormat("struct Foo {\n"
@@ -7989,12 +7991,14 @@ TEST_F(FormatTest, AllowAllArgumentsOnNextLineDontAlign) {
   // However, BAS_AlwaysBreak and BAS_BlockIndent should take precedence over
   // AllowAllArgumentsOnNextLine.
   Style.AlignAfterOpenBracket = FormatStyle::BAS_AlwaysBreak;
+  Style.AlignAfterOpenBracketBreak = {true, false, true};
   verifyFormat(StringRef("functionCall(\n"
                          "    paramA, paramB, paramC);\n"
                          "void functionDecl(\n"
                          "    int A, int B, int C);"),
                Input, Style);
   Style.AlignAfterOpenBracket = FormatStyle::BAS_BlockIndent;
+  Style.AlignAfterOpenBracketBreak = {false, false, true};
   verifyFormat("functionCall(\n"
                "    paramA, paramB, paramC\n"
                ");\n"
@@ -8007,6 +8011,7 @@ TEST_F(FormatTest, AllowAllArgumentsOnNextLineDontAlign) {
   // first argument.
   Style.AllowAllArgumentsOnNextLine = true;
   Style.AlignAfterOpenBracket = FormatStyle::BAS_AlwaysBreak;
+  Style.AlignAfterOpenBracketBreak = {true, false, true};
   verifyFormat(StringRef("functionCall(\n"
                          "    paramA, paramB, paramC);\n"
                          "void functionDecl(\n"
@@ -8928,6 +8933,7 @@ TEST_F(FormatTest, FormatsOneParameterPerLineIfNecessary) {
 TEST_F(FormatTest, FormatsDeclarationBreakAlways) {
   FormatStyle BreakAlways = getGoogleStyle();
   BreakAlways.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;
+  BreakAlways.AlignAfterOpenBracketBreak = {true, false, true};
   verifyFormat("void f(int a,\n"
                "       int b);",
                BreakAlways);
@@ -8956,6 +8962,7 @@ TEST_F(FormatTest, FormatsDeclarationBreakAlways) {
 TEST_F(FormatTest, FormatsDefinitionBreakAlways) {
   FormatStyle BreakAlways = getGoogleStyle();
   BreakAlways.BinPackParameters = FormatStyle::BPPS_AlwaysOnePerLine;
+  BreakAlways.AlignAfterOpenBracketBreak = {true, false, true};
   verifyFormat("void f(int a,\n"
                "       int b) {\n"
                "  f(a, b);\n"
@@ -9346,6 +9353,7 @@ TEST_F(FormatTest, AlignsAfterOpenBracket) {
   Style.ColumnLimit = 80;
 
   Style.AlignAfterOpenBracket = FormatStyle::BAS_AlwaysBreak;
+  Style.AlignAfterOpenBracketBreak = {true, false, true};
   Style.BinPackArguments = false;
   Style.BinPackParameters = FormatStyle::BPPS_OnePerLine;
   verifyFormat("void aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(\n"
@@ -9397,6 +9405,7 @@ TEST_F(FormatTest, AlignsAfterOpenBracket) {
       Style);
 
   Style.AlignAfterOpenBracket = FormatStyle::BAS_BlockIndent;
+  Style.AlignAfterOpenBracketBreak = {false, false, true};
   Style.BinPackArgum...
[truncated]

@gedare
Copy link
Contributor Author

gedare commented Sep 23, 2024

Rebased to main to pick up recent regression fixes BlockIndent/AlwaysBreak.

@gedare
Copy link
Contributor Author

gedare commented Dec 2, 2024

Rebased to main. This PR addresses several long-standing issues. It would be great to get a review.

Copy link
Contributor

@HazardyKnusperkeks HazardyKnusperkeks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the way to go, or at least not with this name.

How about going the Custom way?

@gedare
Copy link
Contributor Author

gedare commented Dec 14, 2024

I don't think this is the way to go, or at least not with this name.

How about going the Custom way?

Adding a Custom option to AlignAfterOpenBracket is an interesting proposal. There have been some ideas floated in other Issues and PRs related to this, for example:

My concern with a Custom option is what to include. At this point it could be an enumeration of the different kinds of blocks (functions, if/else, for, other) and the different kinds of openers (parens, braces, square brackets, angles), and alignment of the parameters (Align, DontAlign), and breaking behavior (DontBreak, AlwaysBreak or better yet InitialBreak and BlockIndent or better LastBreak).

At the moment, these alignment and breaking options are fairly complex and bug-prone.

@gedare
Copy link
Contributor Author

gedare commented Jan 27, 2025

There was a problem between this implementation and the fix in #119989 that I needed to figure out. Handling block indentation within template strings needed to be split in two cases, one case to limit the check for a template string to the current scope for determining how much to indent, and one case to search beyond the current scope for an enclosing template string.

@gedare
Copy link
Contributor Author

gedare commented Jan 29, 2025

Rebased to main for 21.0.0git

@HazardyKnusperkeks
Copy link
Contributor

Was there a change which I should look at again? I find the GitHub UI for reviews terrible. If it was just a rebase my vote stands.

@gedare
Copy link
Contributor Author

gedare commented Feb 11, 2025

Was there a change which I should look at again? I find the GitHub UI for reviews terrible. If it was just a rebase my vote stands.

@HazardyKnusperkeks I think the only changes since your reviewer were to bump to version 21 and regenerate the files. Otherwise this was just a rebase and conflict fix.

@owenca
Copy link
Contributor

owenca commented Feb 16, 2025

I prefer that we limit this to breaking after the left parenthesis of a control statement, with true/false or Always, Never, Multiline, BlockIndent, etc.

@gedare
Copy link
Contributor Author

gedare commented Feb 19, 2025

I prefer that we limit this to breaking after the left parenthesis of a control statement, with true/false or Always, Never, Multiline, BlockIndent, etc.

if I understand you correctly, you would like a new style option added for setting break option for all control statements in the same way?

@owenca
Copy link
Contributor

owenca commented Feb 20, 2025

I prefer that we limit this to breaking after the left parenthesis of a control statement, with true/false or Always, Never, Multiline, BlockIndent, etc.

if I understand you correctly, you would like a new style option added for setting break option for all control statements in the same way?

Yes.

@gedare
Copy link
Contributor Author

gedare commented Apr 29, 2025

I prefer that we limit this to breaking after the left parenthesis of a control statement, with true/false or Always, Never, Multiline, BlockIndent, etc.

if I understand you correctly, you would like a new style option added for setting break option for all control statements in the same way?

Yes.

This is basically re-written as requested. The Alignment is determined by the value of AlignAfterOpenBracket, except that because there is a forced break with MultiLine the alignment will happen at the Continuation Indent. So using the new option with MultiLine and AlignAfterOpenBracket.Align is equivalent to using AlignAfterOpenBracket.AlwaysBreak with MultiLine.

@github-actions
Copy link

github-actions bot commented Apr 29, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@github-actions
Copy link

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions cpp,h -- clang/include/clang/Format/Format.h clang/lib/Format/ContinuationIndenter.cpp clang/lib/Format/Format.cpp clang/lib/Format/TokenAnnotator.cpp clang/unittests/Format/ConfigParseTest.cpp clang/unittests/Format/FormatTest.cpp
View the diff from clang-format here.
diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp
index 08558d655..f4e48b7e3 100644
--- a/clang/lib/Format/ContinuationIndenter.cpp
+++ b/clang/lib/Format/ContinuationIndenter.cpp
@@ -834,16 +834,15 @@ void ContinuationIndenter::addTokenOnCurrentLine(LineState &State, bool DryRun,
       /* For backward compatibility, use AlignAfterOpenBracket
        * in case AlignAfterControlStatement is not initialized */
       return Style.AlignAfterControlStatement == FormatStyle::BACSS_MultiLine ||
-        (Style.AlignAfterControlStatement == FormatStyle::BACSS_Default &&
-         Style.AlignAfterOpenBracket == FormatStyle::BAS_AlwaysBreak);
+             (Style.AlignAfterControlStatement == FormatStyle::BACSS_Default &&
+              Style.AlignAfterOpenBracket == FormatStyle::BAS_AlwaysBreak);
     }
-    if (IsOtherConditional(*Tok.Previous)) {
+    if (IsOtherConditional(*Tok.Previous))
       return Style.AlignAfterControlStatement == FormatStyle::BACSS_MultiLine;
-    }
     if (Style.AlignAfterOpenBracket == FormatStyle::BAS_AlwaysBreak ||
-       Style.AlignAfterOpenBracket == FormatStyle::BAS_BlockIndent) {
+        Style.AlignAfterOpenBracket == FormatStyle::BAS_BlockIndent) {
       return !Tok.Previous->is(TT_CastRParen) &&
-         !(Style.isJavaScript() && Tok.is(Keywords.kw_await));
+             !(Style.isJavaScript() && Tok.is(Keywords.kw_await));
     }
     return false;
   };
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index e6a82a093..cd7e4cb06 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -202,8 +202,10 @@ template <> struct MappingTraits<FormatStyle::BraceWrappingFlags> {
   }
 };
 
-template <> struct ScalarEnumerationTraits<FormatStyle::BreakAfterControlStatementStyle> {
-  static void enumeration(IO &IO, FormatStyle::BreakAfterControlStatementStyle &Value) {
+template <>
+struct ScalarEnumerationTraits<FormatStyle::BreakAfterControlStatementStyle> {
+  static void enumeration(IO &IO,
+                          FormatStyle::BreakAfterControlStatementStyle &Value) {
     IO.enumCase(Value, "Default", FormatStyle::BACSS_Default);
     IO.enumCase(Value, "MultiLine", FormatStyle::BACSS_MultiLine);
     IO.enumCase(Value, "No", FormatStyle::BACSS_No);
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 39ddb63d4..881d40abf 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -6221,7 +6221,8 @@ bool TokenAnnotator::canBreakBefore(const AnnotatedLine &Line,
     const FormatToken *Previous = Right.MatchingParen->Previous;
     if (!Previous)
       return true;
-    if (Previous->isIf() || Previous->isOneOf(tok::kw_for, tok::kw_while, tok::kw_switch)) {
+    if (Previous->isIf() ||
+        Previous->isOneOf(tok::kw_for, tok::kw_while, tok::kw_switch)) {
       return Style.AlignAfterControlStatement == FormatStyle::BACSS_MultiLine;
     }
     return true;
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 7841caefd..a314a0bff 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -9797,7 +9797,6 @@ TEST_F(FormatTest, AlignAfterConditionalStatements) {
                "}",
                Style);
 
-
   Style.AlignAfterOpenBracket = FormatStyle::BAS_AlwaysBreak;
   Style.AlignAfterControlStatement = FormatStyle::BACSS_MultiLine;
 

Copy link
Contributor

@HazardyKnusperkeks HazardyKnusperkeks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from these small comments LGTM.
@owenca please take a look.

@gedare
Copy link
Contributor Author

gedare commented Sep 24, 2025

rebased for release notes conflict.
waiting on @mydeveloperday and @owenca

@marco-antognini-sonarsource
Copy link
Contributor

I tested this PR, the results look nice, thanks @gedare for putting it together.

@gedare
Copy link
Contributor Author

gedare commented Oct 13, 2025

Rebased to fix conflicts with isNoneOf changes, also squashed a lot of the WIP commits together. Waiting on reviews from @mydeveloperday and @owenca

@marco-antognini-sonarsource
Copy link
Contributor

BTW, I think this also solves #151844 in addition to those you already mentioned in the PR description.

Replace the AlwaysBreak and BlockIndent suboptions of
AlignAfterOpenBracket with new style options BreakAfterOpenBracket* and
BreakBeforeCloseBracket* for * in BracedList for braced list
initializers, if for if conditional statements, Loop for loop control
statements (for/while), Switch for switch statements, and Function for
function calls/declarations/definitions.

Deprecates AlwaysBreak and BlockIndent.

Fixes llvm#67738.
Fixes llvm#79176.
Fixes llvm#80123.
@gedare
Copy link
Contributor Author

gedare commented Oct 29, 2025

Rebased to fix merge conflicts

Copy link
Contributor

@HazardyKnusperkeks HazardyKnusperkeks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This last change and we can merge.

@HazardyKnusperkeks HazardyKnusperkeks merged commit 8067b5c into llvm:main Oct 30, 2025
11 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Oct 30, 2025

LLVM Buildbot has detected a new failure on builder lldb-x86_64-debian running on lldb-x86_64-debian while building clang at step 6 "test".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/162/builds/34247

Here is the relevant piece of the build log for the reference
Step 6 (test) failure: build (failure)
...
XFAIL: lldb-api :: functionalities/longjmp/TestLongjmp.py (222 of 3262)
PASS: lldb-unit :: Host/./HostTests/92/141 (223 of 3262)
PASS: lldb-api :: commands/watchpoints/watchpoint_commands/TestWatchpointCommands.py (224 of 3262)
PASS: lldb-api :: lang/c/bitfields/TestBitfields.py (225 of 3262)
PASS: lldb-api :: lang/c/shared_lib_stripped_symbols/TestSharedLibStrippedSymbols.py (226 of 3262)
PASS: lldb-api :: python_api/process/cancel_attach/TestCancelAttach.py (227 of 3262)
PASS: lldb-api :: tools/lldb-dap/module-event/TestDAP_module_event.py (228 of 3262)
PASS: lldb-api :: functionalities/thread/concurrent_events/TestConcurrentWatchpointDelayWatchpointOneBreakpoint.py (229 of 3262)
PASS: lldb-api :: commands/process/attach-resume/TestAttachResume.py (230 of 3262)
UNRESOLVED: lldb-api :: tools/lldb-dap/server/TestDAP_server.py (231 of 3262)
******************** TEST 'lldb-api :: tools/lldb-dap/server/TestDAP_server.py' FAILED ********************
Script:
--
/usr/bin/python3 /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/test/API/dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=/home/worker/2.0.1/lldb-x86_64-debian/build/./lib --env LLVM_INCLUDE_DIR=/home/worker/2.0.1/lldb-x86_64-debian/build/include --env LLVM_TOOLS_DIR=/home/worker/2.0.1/lldb-x86_64-debian/build/./bin --arch x86_64 --build-dir /home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex --lldb-module-cache-dir /home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/module-cache-lldb/lldb-api --clang-module-cache-dir /home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/module-cache-clang/lldb-api --executable /home/worker/2.0.1/lldb-x86_64-debian/build/./bin/lldb --compiler /home/worker/2.0.1/lldb-x86_64-debian/build/./bin/clang --dsymutil /home/worker/2.0.1/lldb-x86_64-debian/build/./bin/dsymutil --make /usr/bin/gmake --llvm-tools-dir /home/worker/2.0.1/lldb-x86_64-debian/build/./bin --lldb-obj-root /home/worker/2.0.1/lldb-x86_64-debian/build/tools/lldb --lldb-libs-dir /home/worker/2.0.1/lldb-x86_64-debian/build/./lib --cmake-build-type Release -t /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/test/API/tools/lldb-dap/server -p TestDAP_server.py
--
Exit Code: 1

Command Output (stdout):
--
lldb version 22.0.0git (https://github.com/llvm/llvm-project.git revision 8067b5cff73ea96189b5bbca1110cfe4dce03552)
  clang revision 8067b5cff73ea96189b5bbca1110cfe4dce03552
  llvm revision 8067b5cff73ea96189b5bbca1110cfe4dce03552
Skipping the following test categories: ['libc++', 'msvcstl', 'dsym', 'gmodules', 'debugserver', 'objc']

--
Command Output (stderr):
--
Change dir to: /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/test/API/tools/lldb-dap/server
runCmd: settings clear --all

output: 

runCmd: settings set symbols.enable-external-lookup false

output: 

runCmd: settings set target.inherit-tcc true

output: 

runCmd: settings set target.disable-aslr false

output: 

runCmd: settings set target.detach-on-error false

output: 

runCmd: settings set target.auto-apply-fixits false

@llvm-ci
Copy link
Collaborator

llvm-ci commented Oct 30, 2025

LLVM Buildbot has detected a new failure on builder clang-m68k-linux-cross running on suse-gary-m68k-cross while building clang at step 5 "ninja check 1".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/27/builds/18269

Here is the relevant piece of the build log for the reference
Step 5 (ninja check 1) failure: stage 1 checked (failure)
******************** TEST 'LLVM-Unit :: CAS/./CASTests/7/22' FAILED ********************
Script(shard):
--
GTEST_OUTPUT=json:/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/stage1/unittests/CAS/./CASTests-LLVM-Unit-3129095-7-22.json GTEST_SHUFFLE=0 GTEST_TOTAL_SHARDS=22 GTEST_SHARD_INDEX=7 /var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/stage1/unittests/CAS/./CASTests
--

Script:
--
/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/stage1/unittests/CAS/./CASTests --gtest_filter=CASProgramTest.MappedFileRegionArenaTest
--
Note: Google Test filter = CASProgramTest.MappedFileRegionArenaTest
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from CASProgramTest
/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/llvm/llvm/unittests/CAS/ProgramTest.cpp:147: Failure
Value of: PI.ReturnCode == 0
  Actual: false
Expected: true


/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/llvm/llvm/unittests/CAS/ProgramTest.cpp:147
Value of: PI.ReturnCode == 0
  Actual: false
Expected: true



********************


luciechoi pushed a commit to luciechoi/llvm-project that referenced this pull request Nov 1, 2025
llvm#108332)

Replace the `AlwaysBreak` and `BlockIndent` suboptions of
`AlignAfterOpenBracket` with new style options `BreakAfterOpenBracket*`
and `BreakBeforeCloseBracket*` for `*` in `BracedList` for braced list
initializers, `if` for if conditional statements, `Loop` for loop
control statements (for/while), `Switch` for switch statements, and
`Function` for function calls/declarations/definitions.

Deprecates `AlwaysBreak` and `BlockIndent`.
 
Fixes llvm#67738 
Fixes llvm#79176 
Fixes llvm#80123 
Fixes llvm#151844
DEBADRIBASAK pushed a commit to DEBADRIBASAK/llvm-project that referenced this pull request Nov 3, 2025
llvm#108332)

Replace the `AlwaysBreak` and `BlockIndent` suboptions of
`AlignAfterOpenBracket` with new style options `BreakAfterOpenBracket*`
and `BreakBeforeCloseBracket*` for `*` in `BracedList` for braced list
initializers, `if` for if conditional statements, `Loop` for loop
control statements (for/while), `Switch` for switch statements, and
`Function` for function calls/declarations/definitions.

Deprecates `AlwaysBreak` and `BlockIndent`.
 
Fixes llvm#67738 
Fixes llvm#79176 
Fixes llvm#80123 
Fixes llvm#151844
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang Clang issues not falling into any other category clang-format

Projects

None yet

7 participants