https://github.com/zeule updated 
https://github.com/llvm/llvm-project/pull/191217

>From 0913efa295922b36508301fcd574127a21bd25e6 Mon Sep 17 00:00:00 2001
From: Eugene Shalygin <[email protected]>
Date: Wed, 8 Apr 2026 17:22:52 +0200
Subject: [PATCH] [clang-format] treat continuation as indent for aligned lines

This allows to inherit tabbed indent from the lines we break by the
lines we want to align. Thus in the AlignWithSpaces mode aligned lines
do not generate smaller indent than those they are aligned to.
---
 clang/lib/Format/BreakableToken.cpp         | 15 +++---
 clang/lib/Format/ContinuationIndenter.cpp   | 29 ++++++------
 clang/lib/Format/ContinuationIndenter.h     | 33 +++++++------
 clang/lib/Format/FormatToken.h              | 18 ++++++++
 clang/lib/Format/UnwrappedLineFormatter.cpp |  4 +-
 clang/lib/Format/WhitespaceManager.cpp      | 51 +++++++++++++--------
 clang/lib/Format/WhitespaceManager.h        |  7 +--
 clang/unittests/Format/AlignmentTest.cpp    | 22 +++++++++
 8 files changed, 117 insertions(+), 62 deletions(-)

diff --git a/clang/lib/Format/BreakableToken.cpp 
b/clang/lib/Format/BreakableToken.cpp
index b60daffc0eb1c..20a380effa6cb 100644
--- a/clang/lib/Format/BreakableToken.cpp
+++ b/clang/lib/Format/BreakableToken.cpp
@@ -1048,7 +1048,7 @@ void BreakableLineCommentSection::reflow(unsigned 
LineIndex,
       // tokens by the empty string.
       Whitespaces.replaceWhitespace(
           *Tokens[LineIndex], /*Newlines=*/0, /*Spaces=*/0,
-          /*StartOfTokenColumn=*/StartColumn, /*IsAligned=*/true,
+          /*StartOfTokenColumn=*/StartColumn, /*AlignedTo=*/nullptr,
           /*InPPDirective=*/false);
     } else {
       // In case we're reflowing after the '\' in:
@@ -1114,12 +1114,13 @@ void BreakableLineCommentSection::adaptStartOfLine(
     // token, even if LineColumn is the same as the original column of the
     // token. This is because WhitespaceManager doesn't align trailing
     // comments if they are untouchable.
-    Whitespaces.replaceWhitespace(*Tokens[LineIndex],
-                                  /*Newlines=*/1,
-                                  /*Spaces=*/LineColumn,
-                                  /*StartOfTokenColumn=*/LineColumn,
-                                  /*IsAligned=*/tokenAt(0).NewlinesBefore == 0,
-                                  /*InPPDirective=*/false);
+    Whitespaces.replaceWhitespace(
+        *Tokens[LineIndex],
+        /*Newlines=*/1,
+        /*Spaces=*/LineColumn,
+        /*StartOfTokenColumn=*/LineColumn,
+        /*AlignedTo=*/tokenAt(0).NewlinesBefore == 0 ? &tokenAt(0) : nullptr,
+        /*InPPDirective=*/false);
   }
   if (OriginalPrefix[LineIndex] != Prefix[LineIndex]) {
     // Adjust the prefix if necessary.
diff --git a/clang/lib/Format/ContinuationIndenter.cpp 
b/clang/lib/Format/ContinuationIndenter.cpp
index fc5e1000ad750..c30b3e58c3e89 100644
--- a/clang/lib/Format/ContinuationIndenter.cpp
+++ b/clang/lib/Format/ContinuationIndenter.cpp
@@ -879,7 +879,7 @@ void ContinuationIndenter::addTokenOnCurrentLine(LineState 
&State, bool DryRun,
         State.Line->InMacroBody && Current.isNot(TT_LineComment);
     Whitespaces.replaceWhitespace(Current, /*Newlines=*/0, Spaces,
                                   State.Column + Spaces + PPColumnCorrection,
-                                  /*IsAligned=*/false, ContinuePPDirective);
+                                  /*AlignTo=*/nullptr, ContinuePPDirective);
   }
 
   // If "BreakBeforeInheritanceComma" mode, don't break within the inheritance
@@ -1024,7 +1024,7 @@ void 
ContinuationIndenter::addTokenOnCurrentLine(LineState &State, bool DryRun,
        Previous.is(TT_VerilogMultiLineListLParen)) &&
       !IsInTemplateString(Current, false)) {
     CurrentState.Indent = State.Column + Spaces;
-    CurrentState.IsAligned = true;
+    CurrentState.AlignedTo = &Previous;
   }
   if (CurrentState.AvoidBinPacking && startsNextParameter(Current, Style))
     CurrentState.NoLineBreak = true;
@@ -1241,19 +1241,20 @@ unsigned 
ContinuationIndenter::addTokenOnNewLine(LineState &State,
   switch (Style.BreakInheritanceList) {
   case FormatStyle::BILS_BeforeColon:
   case FormatStyle::BILS_AfterComma:
-    CurrentState.IsAligned = CurrentState.IsAligned ||
-                             Current.is(TT_InheritanceColon) ||
-                             Previous.is(TT_InheritanceComma);
+    if (Current.is(TT_InheritanceColon) || Previous.is(TT_InheritanceComma)) {
+      CurrentState.AlignedTo = Previous.getPreviousOneOf(
+          tok::kw_class, tok::kw_struct, tok::kw_union);
+    }
     break;
   case FormatStyle::BILS_BeforeComma:
-    CurrentState.IsAligned =
-        CurrentState.IsAligned ||
-        Current.isOneOf(TT_InheritanceColon, TT_InheritanceComma);
+    if (Current.isOneOf(TT_InheritanceColon, TT_InheritanceComma)) {
+      CurrentState.AlignedTo = Previous.getPreviousOneOf(
+          tok::kw_class, tok::kw_struct, tok::kw_union);
+    }
     break;
   case FormatStyle::BILS_AfterColon:
-    CurrentState.IsAligned =
-        CurrentState.IsAligned ||
-        Previous.isOneOf(TT_InheritanceColon, TT_InheritanceComma);
+    if (Previous.isOneOf(TT_InheritanceColon, TT_InheritanceComma))
+      CurrentState.AlignedTo = &Previous;
     break;
   }
 
@@ -1275,7 +1276,7 @@ unsigned 
ContinuationIndenter::addTokenOnNewLine(LineState &State,
   }
   if (Current.is(TT_BinaryOperator) && Current.CanBreakBefore) {
     CurrentState.BreakBeforeParameter = false;
-    CurrentState.IsAligned = true;
+    CurrentState.AlignedTo = &Current;
   }
 
   if (!DryRun) {
@@ -1295,7 +1296,7 @@ unsigned 
ContinuationIndenter::addTokenOnNewLine(LineState &State,
                                      State.Line->Type != LT_ImportStatement &&
                                      Current.isNot(TT_LineComment);
     Whitespaces.replaceWhitespace(Current, Newlines, State.Column, 
State.Column,
-                                  CurrentState.IsAligned, ContinuePPDirective,
+                                  CurrentState.AlignedTo, ContinuePPDirective,
                                   IndentedFromColumn);
   }
 
@@ -2007,7 +2008,7 @@ void 
ContinuationIndenter::moveStatePastFakeLParens(LineState &State,
         NewParenState.UnindentOperator = true;
       // Mark indentation as alignment if the expression is aligned.
       if (Style.AlignOperands != FormatStyle::OAS_DontAlign)
-        NewParenState.IsAligned = true;
+        NewParenState.AlignedTo = Previous;
     }
 
     // Do not indent relative to the fake parentheses inserted for "." or "->".
diff --git a/clang/lib/Format/ContinuationIndenter.h 
b/clang/lib/Format/ContinuationIndenter.h
index 1554fb441dff0..fcd9b51bc6f8f 100644
--- a/clang/lib/Format/ContinuationIndenter.h
+++ b/clang/lib/Format/ContinuationIndenter.h
@@ -232,18 +232,17 @@ class ContinuationIndenter {
 struct ParenState {
   ParenState(const FormatToken *Tok, IndentationAndAlignment Indent,
              unsigned LastSpace, bool AvoidBinPacking, bool NoLineBreak)
-      : Tok(Tok), Indent(Indent), LastSpace(LastSpace),
-        NestedBlockIndent(Indent.Total), IsAligned(false),
-        BreakBeforeClosingBrace(false), BreakBeforeClosingParen(false),
-        BreakBeforeClosingAngle(false), AvoidBinPacking(AvoidBinPacking),
-        BreakBeforeParameter(false), NoLineBreak(NoLineBreak),
-        NoLineBreakInOperand(false), LastOperatorWrapped(true),
-        ContainsLineBreak(false), ContainsUnwrappedBuilder(false),
-        AlignColons(true), ObjCSelectorNameFound(false),
-        HasMultipleNestedBlocks(false), NestedBlockInlined(false),
-        IsInsideObjCArrayLiteral(false), IsCSharpGenericTypeConstraint(false),
-        IsChainedConditional(false), IsWrappedConditional(false),
-        UnindentOperator(false) {}
+      : Tok(Tok), Indent(Indent), AlignedTo(nullptr), LastSpace(LastSpace),
+        NestedBlockIndent(Indent.Total), BreakBeforeClosingBrace(false),
+        BreakBeforeClosingParen(false), BreakBeforeClosingAngle(false),
+        AvoidBinPacking(AvoidBinPacking), BreakBeforeParameter(false),
+        NoLineBreak(NoLineBreak), NoLineBreakInOperand(false),
+        LastOperatorWrapped(true), ContainsLineBreak(false),
+        ContainsUnwrappedBuilder(false), AlignColons(true),
+        ObjCSelectorNameFound(false), HasMultipleNestedBlocks(false),
+        NestedBlockInlined(false), IsInsideObjCArrayLiteral(false),
+        IsCSharpGenericTypeConstraint(false), IsChainedConditional(false),
+        IsWrappedConditional(false), UnindentOperator(false) {}
 
   /// The token opening this parenthesis level, or nullptr if this level is
   /// opened by fake parenthesis.
@@ -256,6 +255,9 @@ struct ParenState {
   /// indented.
   IndentationAndAlignment Indent;
 
+  /// The token in one of the previous lines this state wants to align to.
+  const FormatToken *AlignedTo;
+
   /// The position of the last space on each level.
   ///
   /// Used e.g. to break like:
@@ -299,9 +301,6 @@ struct ParenState {
   /// Used to align further variables if necessary.
   unsigned VariablePos = 0;
 
-  /// Whether this block's indentation is used for alignment.
-  bool IsAligned : 1;
-
   /// Whether a newline needs to be inserted before the block's closing
   /// brace.
   ///
@@ -399,8 +398,8 @@ struct ParenState {
       return NestedBlockIndent < Other.NestedBlockIndent;
     if (FirstLessLess != Other.FirstLessLess)
       return FirstLessLess < Other.FirstLessLess;
-    if (IsAligned != Other.IsAligned)
-      return IsAligned;
+    if (AlignedTo != Other.AlignedTo)
+      return AlignedTo < Other.AlignedTo;
     if (BreakBeforeClosingBrace != Other.BreakBeforeClosingBrace)
       return BreakBeforeClosingBrace;
     if (BreakBeforeClosingParen != Other.BreakBeforeClosingParen)
diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h
index 240bb31148f6c..3ec53be386dde 100644
--- a/clang/lib/Format/FormatToken.h
+++ b/clang/lib/Format/FormatToken.h
@@ -545,6 +545,9 @@ struct FormatToken {
   /// The indent level of this token. Copied from the surrounding line.
   unsigned IndentLevel = 0;
 
+  /// Accumulated indent applied by to this token by WhitespaceManager.
+  unsigned AccumulatedIndent = 0;
+
   /// Penalty for inserting a line break before this token.
   unsigned SplitPenalty = 0;
 
@@ -860,6 +863,21 @@ struct FormatToken {
                               /*CPlusPlus11=*/true);
   }
 
+  template <typename T> [[nodiscard]] FormatToken *getPrevious(T A1) const {
+    FormatToken *Tok = Previous;
+    while (Tok && !Tok->is(A1))
+      Tok = Tok->Previous;
+    return Tok;
+  }
+
+  template <typename... Ts>
+  [[nodiscard]] FormatToken *getPreviousOneOf(Ts... Ks) const {
+    FormatToken *Tok = Previous;
+    while (Tok && !((Tok->is(Ks) || ...)))
+      Tok = Tok->Previous;
+    return Tok;
+  }
+
   /// Returns the previous token ignoring comments.
   [[nodiscard]] FormatToken *getPreviousNonComment() const {
     FormatToken *Tok = Previous;
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp 
b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 79d4217f78a36..42eabc065b1a8 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -1244,7 +1244,7 @@ class LineFormatter {
     if (!DryRun) {
       Whitespaces->replaceWhitespace(
           *Child->First, /*Newlines=*/0, /*Spaces=*/1,
-          /*StartOfTokenColumn=*/State.Column, /*IsAligned=*/false,
+          /*StartOfTokenColumn=*/State.Column, /*AlignedTo=*/nullptr,
           State.Line->InPPDirective);
     }
     Penalty +=
@@ -1771,7 +1771,7 @@ void UnwrappedLineFormatter::formatFirstToken(
   }
 
   Whitespaces->replaceWhitespace(RootToken, RootToken.Newlines, Indent, Indent,
-                                 /*IsAligned=*/false,
+                                 /*AlignedTo=*/nullptr,
                                  Line.InPPDirective &&
                                      !RootToken.HasUnescapedNewline);
 }
diff --git a/clang/lib/Format/WhitespaceManager.cpp 
b/clang/lib/Format/WhitespaceManager.cpp
index 93f354a9f7256..5e78bc20022b8 100644
--- a/clang/lib/Format/WhitespaceManager.cpp
+++ b/clang/lib/Format/WhitespaceManager.cpp
@@ -21,6 +21,13 @@
 namespace clang {
 namespace format {
 
+static const FormatToken &getLineStart(const FormatToken &Tok) {
+  const FormatToken *Result = &Tok;
+  while (Result->getDecision() != FormatDecision::FD_Break && Result->Previous)
+    Result = Result->Previous;
+  return *Result;
+}
+
 bool WhitespaceManager::Change::IsBeforeInFile::operator()(
     const Change &C1, const Change &C2) const {
   return SourceMgr.isBeforeInTranslationUnit(
@@ -33,21 +40,19 @@ bool WhitespaceManager::Change::IsBeforeInFile::operator()(
               C2.OriginalWhitespaceRange.getEnd()));
 }
 
-WhitespaceManager::Change::Change(const FormatToken &Tok,
-                                  bool CreateReplacement,
-                                  SourceRange OriginalWhitespaceRange,
-                                  int Spaces, unsigned StartOfTokenColumn,
-                                  unsigned IndentedFromColumn,
-                                  unsigned NewlinesBefore,
-                                  StringRef PreviousLinePostfix,
-                                  StringRef CurrentLinePrefix, bool IsAligned,
-                                  bool ContinuesPPDirective, bool 
IsInsideToken)
+WhitespaceManager::Change::Change(
+    const FormatToken &Tok, bool CreateReplacement,
+    SourceRange OriginalWhitespaceRange, int Spaces,
+    unsigned StartOfTokenColumn, unsigned IndentedFromColumn,
+    unsigned NewlinesBefore, StringRef PreviousLinePostfix,
+    StringRef CurrentLinePrefix, const FormatToken *AlignedTo,
+    bool ContinuesPPDirective, bool IsInsideToken)
     : Tok(&Tok), CreateReplacement(CreateReplacement),
       OriginalWhitespaceRange(OriginalWhitespaceRange),
       StartOfTokenColumn(StartOfTokenColumn),
       IndentedFromColumn(IndentedFromColumn), NewlinesBefore(NewlinesBefore),
       PreviousLinePostfix(PreviousLinePostfix),
-      CurrentLinePrefix(CurrentLinePrefix), IsAligned(IsAligned),
+      CurrentLinePrefix(CurrentLinePrefix), AlignedTo(AlignedTo),
       ContinuesPPDirective(ContinuesPPDirective), Spaces(Spaces),
       IsInsideToken(IsInsideToken), IsTrailingComment(false), TokenLength(0),
       PreviousEndOfTokenColumn(0), EscapedNewlineColumn(0),
@@ -57,14 +62,17 @@ WhitespaceManager::Change::Change(const FormatToken &Tok,
 void WhitespaceManager::replaceWhitespace(FormatToken &Tok, unsigned Newlines,
                                           unsigned Spaces,
                                           unsigned StartOfTokenColumn,
-                                          bool IsAligned, bool InPPDirective,
+                                          const FormatToken *AlignedTo,
+                                          bool InPPDirective,
                                           unsigned IndentedFromColumn) {
   if (Tok.Finalized || (Tok.MacroCtx && Tok.MacroCtx->Role == MR_ExpandedArg))
     return;
   Tok.setDecision((Newlines > 0) ? FD_Break : FD_Continue);
+  if (!AlignedTo)
+    Tok.AccumulatedIndent += Spaces;
   Changes.push_back(Change(Tok, /*CreateReplacement=*/true, 
Tok.WhitespaceRange,
                            Spaces, StartOfTokenColumn, IndentedFromColumn,
-                           Newlines, "", "", IsAligned,
+                           Newlines, "", "", AlignedTo,
                            InPPDirective && !Tok.IsFirst,
                            /*IsInsideToken=*/false));
 }
@@ -76,7 +84,7 @@ void WhitespaceManager::addUntouchableToken(const FormatToken 
&Tok,
   Changes.push_back(Change(
       Tok, /*CreateReplacement=*/false, Tok.WhitespaceRange, /*Spaces=*/0,
       Tok.OriginalColumn, /*IndentedFromColumn=*/0, Tok.NewlinesBefore, "", "",
-      /*IsAligned=*/false, InPPDirective && !Tok.IsFirst,
+      /*AlignedTo=*/nullptr, InPPDirective && !Tok.IsFirst,
       /*IsInsideToken=*/false));
 }
 
@@ -103,7 +111,7 @@ void WhitespaceManager::replaceWhitespaceInToken(
              SourceRange(Start, Start.getLocWithOffset(ReplaceChars)), Spaces,
              std::max(0, Spaces), /*IndentedFromColumn=*/0, Newlines,
              PreviousPostfix, CurrentPrefix,
-             /*IsAligned=*/true, InPPDirective && !Tok.IsFirst,
+             /*IsAligned=*/&Tok, InPPDirective && !Tok.IsFirst,
              /*IsInsideToken=*/true));
 }
 
@@ -678,7 +686,7 @@ static unsigned AlignTokens(const FormatStyle &Style, F 
&&Matches,
       const FormatToken *MatchingParenToEncounter = nullptr;
       for (unsigned J = IndexToAlign + 1;
            J != E && (Changes[J].NewlinesBefore == 0 ||
-                      MatchingParenToEncounter || Changes[J].IsAligned);
+                      MatchingParenToEncounter || Changes[J].AlignedTo);
            ++J) {
         const auto &Change = Changes[J];
         const auto *Tok = Change.Tok;
@@ -1598,10 +1606,15 @@ void WhitespaceManager::generateChanges() {
       }
       // FIXME: This assert should hold if we computed the column correctly.
       // assert((int)C.StartOfTokenColumn >= C.Spaces);
-      appendIndentText(
-          ReplacementText, C.Tok->IndentLevel, std::max(0, C.Spaces),
-          std::max((int)C.StartOfTokenColumn, C.Spaces) - std::max(0, 
C.Spaces),
-          C.IsAligned);
+      unsigned Indent =
+          std::max(C.Tok->IndentLevel,
+                   C.AlignedTo ? getLineStart(*C.AlignedTo).AccumulatedIndent /
+                                     Style.IndentWidth
+                               : 0);
+      appendIndentText(ReplacementText, Indent, std::max(0, C.Spaces),
+                       std::max((int)C.StartOfTokenColumn, C.Spaces) -
+                           std::max(0, C.Spaces),
+                       C.AlignedTo);
       ReplacementText.append(C.CurrentLinePrefix);
       storeReplacement(C.OriginalWhitespaceRange, ReplacementText);
     }
diff --git a/clang/lib/Format/WhitespaceManager.h 
b/clang/lib/Format/WhitespaceManager.h
index 9b6cde54af0af..66a064ae81dbf 100644
--- a/clang/lib/Format/WhitespaceManager.h
+++ b/clang/lib/Format/WhitespaceManager.h
@@ -55,7 +55,8 @@ class WhitespaceManager {
   /// from. It is used for determining what lines the alignment process should
   /// move.
   void replaceWhitespace(FormatToken &Tok, unsigned Newlines, unsigned Spaces,
-                         unsigned StartOfTokenColumn, bool IsAligned = false,
+                         unsigned StartOfTokenColumn,
+                         const FormatToken *AlignedTo = nullptr,
                          bool InPPDirective = false,
                          unsigned IndentedFromColumn = 0);
 
@@ -117,7 +118,7 @@ class WhitespaceManager {
            SourceRange OriginalWhitespaceRange, int Spaces,
            unsigned StartOfTokenColumn, unsigned IndentedFromColumn,
            unsigned NewlinesBefore, StringRef PreviousLinePostfix,
-           StringRef CurrentLinePrefix, bool IsAligned,
+           StringRef CurrentLinePrefix, const FormatToken *AlignedTo,
            bool ContinuesPPDirective, bool IsInsideToken);
 
     // The kind of the token whose whitespace this change replaces, or in which
@@ -139,7 +140,7 @@ class WhitespaceManager {
     unsigned NewlinesBefore;
     std::string PreviousLinePostfix;
     std::string CurrentLinePrefix;
-    bool IsAligned;
+    const FormatToken *AlignedTo;
     bool ContinuesPPDirective;
 
     // The number of spaces in front of the token or broken part of the token.
diff --git a/clang/unittests/Format/AlignmentTest.cpp 
b/clang/unittests/Format/AlignmentTest.cpp
index e3a3435424914..5fb85d7d2f3e0 100644
--- a/clang/unittests/Format/AlignmentTest.cpp
+++ b/clang/unittests/Format/AlignmentTest.cpp
@@ -3560,6 +3560,28 @@ TEST_F(AlignmentTest, AlignInsidePreprocessorElseBlock) {
                Style);
 }
 
+TEST_F(AlignmentTest, ContinuedAligned) {
+  FormatStyle Style = getLLVMStyleWithColumns(60);
+  Style.UseTab = FormatStyle::UT_AlignWithSpaces;
+  Style.TabWidth = Style.IndentWidth = Style.ContinuationIndentWidth = 4;
+
+  verifyFormat("for (;;) {\n"
+               "\tif (bar(aaaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbb,\n"
+               "\t        cccccccccccccccccccccccccccccc)) {\n"
+               "\t\treturn {};\n"
+               "\t}\n"
+               "}",
+               Style);
+  verifyFormat("bar([]() {\n"
+               "\tconst AAAAAA aaaaa =\n"
+               "\t\tAAAAAAAAAA(foo(bbbbbbbbbbbbbbbbbbbbbbbbbb),\n"
+               "\t\t           foo(cccccccccccccccccccccccccc),\n"
+               "\t\t           foo(ddddddddddddddddddddddddd)) +\n"
+               "\t\teeeeeeeee;\n"
+               "});",
+               Style);
+}
+
 } // namespace
 } // namespace test
 } // namespace format

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to