[clang] [clang-format] Support fine-grained alignment of variable declarations (PR #160270)

via cfe-commits cfe-commits at lists.llvm.org
Tue Sep 23 03:28:32 PDT 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang-format

Author: James Henderson (jh7370)

<details>
<summary>Changes</summary>

This allows disabling variable alignment declarations while still having function alignment. It also distinguishes between class member variables and other variables (e.g. function local variables).

Fixes #<!-- -->143947.

---

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


8 Files Affected:

- (modified) clang/docs/ClangFormatStyleOptions.rst (+210) 
- (modified) clang/include/clang/Format/Format.h (+31) 
- (modified) clang/lib/Format/Format.cpp (+51-30) 
- (modified) clang/lib/Format/FormatToken.h (+5-1) 
- (modified) clang/lib/Format/TokenAnnotator.cpp (+2) 
- (modified) clang/lib/Format/WhitespaceManager.cpp (+6) 
- (modified) clang/unittests/Format/ConfigParseTest.cpp (+47-35) 
- (modified) clang/unittests/Format/FormatTest.cpp (+59) 


``````````diff
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index b746df5dab264..58d1fb43821b7 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -396,6 +396,21 @@ the configuration (without a prefix: ``Auto``).
       a &= 2;
       bbb = 2;
 
+  * ``bool AlignFreeVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether non-member variable
+    declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int v1;
+      float        v2;
+      size_t       v3;
+
+      false:
+      unsigned int v1;
+      float v2;
+      size_t v3;
+
   * ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
     are aligned.
 
@@ -428,6 +443,21 @@ the configuration (without a prefix: ``Auto``).
       int     *p;
       int (*f)();
 
+  * ``bool AlignMemberVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether class/struct member
+    variable declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int member1;
+      float        member2;
+      size_t       member3;
+
+      false:
+      unsigned int member1;
+      float member2;
+      size_t member3;
+
   * ``bool PadOperators`` Only for ``AlignConsecutiveAssignments``.  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.
@@ -554,6 +584,21 @@ the configuration (without a prefix: ``Auto``).
       a &= 2;
       bbb = 2;
 
+  * ``bool AlignFreeVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether non-member variable
+    declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int v1;
+      float        v2;
+      size_t       v3;
+
+      false:
+      unsigned int v1;
+      float v2;
+      size_t v3;
+
   * ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
     are aligned.
 
@@ -586,6 +631,21 @@ the configuration (without a prefix: ``Auto``).
       int     *p;
       int (*f)();
 
+  * ``bool AlignMemberVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether class/struct member
+    variable declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int member1;
+      float        member2;
+      size_t       member3;
+
+      false:
+      unsigned int member1;
+      float member2;
+      size_t member3;
+
   * ``bool PadOperators`` Only for ``AlignConsecutiveAssignments``.  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.
@@ -712,6 +772,21 @@ the configuration (without a prefix: ``Auto``).
       a &= 2;
       bbb = 2;
 
+  * ``bool AlignFreeVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether non-member variable
+    declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int v1;
+      float        v2;
+      size_t       v3;
+
+      false:
+      unsigned int v1;
+      float v2;
+      size_t v3;
+
   * ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
     are aligned.
 
@@ -744,6 +819,21 @@ the configuration (without a prefix: ``Auto``).
       int     *p;
       int (*f)();
 
+  * ``bool AlignMemberVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether class/struct member
+    variable declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int member1;
+      float        member2;
+      size_t       member3;
+
+      false:
+      unsigned int member1;
+      float member2;
+      size_t member3;
+
   * ``bool PadOperators`` Only for ``AlignConsecutiveAssignments``.  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.
@@ -871,6 +961,21 @@ the configuration (without a prefix: ``Auto``).
       a &= 2;
       bbb = 2;
 
+  * ``bool AlignFreeVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether non-member variable
+    declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int v1;
+      float        v2;
+      size_t       v3;
+
+      false:
+      unsigned int v1;
+      float v2;
+      size_t v3;
+
   * ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
     are aligned.
 
@@ -903,6 +1008,21 @@ the configuration (without a prefix: ``Auto``).
       int     *p;
       int (*f)();
 
+  * ``bool AlignMemberVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether class/struct member
+    variable declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int member1;
+      float        member2;
+      size_t       member3;
+
+      false:
+      unsigned int member1;
+      float member2;
+      size_t member3;
+
   * ``bool PadOperators`` Only for ``AlignConsecutiveAssignments``.  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.
@@ -1149,6 +1269,21 @@ the configuration (without a prefix: ``Auto``).
       a &= 2;
       bbb = 2;
 
+  * ``bool AlignFreeVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether non-member variable
+    declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int v1;
+      float        v2;
+      size_t       v3;
+
+      false:
+      unsigned int v1;
+      float v2;
+      size_t v3;
+
   * ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
     are aligned.
 
@@ -1181,6 +1316,21 @@ the configuration (without a prefix: ``Auto``).
       int     *p;
       int (*f)();
 
+  * ``bool AlignMemberVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether class/struct member
+    variable declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int member1;
+      float        member2;
+      size_t       member3;
+
+      false:
+      unsigned int member1;
+      float member2;
+      size_t member3;
+
   * ``bool PadOperators`` Only for ``AlignConsecutiveAssignments``.  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.
@@ -1305,6 +1455,21 @@ the configuration (without a prefix: ``Auto``).
       a &= 2;
       bbb = 2;
 
+  * ``bool AlignFreeVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether non-member variable
+    declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int v1;
+      float        v2;
+      size_t       v3;
+
+      false:
+      unsigned int v1;
+      float v2;
+      size_t v3;
+
   * ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
     are aligned.
 
@@ -1337,6 +1502,21 @@ the configuration (without a prefix: ``Auto``).
       int     *p;
       int (*f)();
 
+  * ``bool AlignMemberVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether class/struct member
+    variable declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int member1;
+      float        member2;
+      size_t       member3;
+
+      false:
+      unsigned int member1;
+      float member2;
+      size_t member3;
+
   * ``bool PadOperators`` Only for ``AlignConsecutiveAssignments``.  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.
@@ -1461,6 +1641,21 @@ the configuration (without a prefix: ``Auto``).
       a &= 2;
       bbb = 2;
 
+  * ``bool AlignFreeVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether non-member variable
+    declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int v1;
+      float        v2;
+      size_t       v3;
+
+      false:
+      unsigned int v1;
+      float v2;
+      size_t v3;
+
   * ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
     are aligned.
 
@@ -1493,6 +1688,21 @@ the configuration (without a prefix: ``Auto``).
       int     *p;
       int (*f)();
 
+  * ``bool AlignMemberVariableDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether class/struct member
+    variable declarations are aligned.
+
+    .. code-block:: c++
+
+      true:
+      unsigned int member1;
+      float        member2;
+      size_t       member3;
+
+      false:
+      unsigned int member1;
+      float member2;
+      size_t member3;
+
   * ``bool PadOperators`` Only for ``AlignConsecutiveAssignments``.  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.
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 3df5b92654094..b18524b7f67a2 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -226,6 +226,20 @@ struct FormatStyle {
     ///   bbb = 2;
     /// \endcode
     bool AlignCompound;
+    /// Only for ``AlignConsecutiveDeclarations``. Whether non-member variable
+    /// declarations are aligned.
+    /// \code
+    ///   true:
+    ///   unsigned int v1;
+    ///   float        v2;
+    ///   size_t       v3;
+    ///
+    ///   false:
+    ///   unsigned int v1;
+    ///   float v2;
+    ///   size_t v3;
+    /// \endcode
+    bool AlignFreeVariableDeclarations;
     /// Only for ``AlignConsecutiveDeclarations``. Whether function declarations
     /// are aligned.
     /// \code
@@ -256,6 +270,20 @@ struct FormatStyle {
     ///   int (*f)();
     /// \endcode
     bool AlignFunctionPointers;
+    /// Only for ``AlignConsecutiveDeclarations``. Whether class/struct member
+    /// variable declarations are aligned.
+    /// \code
+    ///   true:
+    ///   unsigned int member1;
+    ///   float        member2;
+    ///   size_t       member3;
+    ///
+    ///   false:
+    ///   unsigned int member1;
+    ///   float member2;
+    ///   size_t member3;
+    /// \endcode
+    bool AlignMemberVariableDeclarations;
     /// Only for ``AlignConsecutiveAssignments``.  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.
@@ -279,8 +307,11 @@ struct FormatStyle {
       return Enabled == R.Enabled && AcrossEmptyLines == R.AcrossEmptyLines &&
              AcrossComments == R.AcrossComments &&
              AlignCompound == R.AlignCompound &&
+             AlignFreeVariableDeclarations == R.AlignFreeVariableDeclarations &&
              AlignFunctionDeclarations == R.AlignFunctionDeclarations &&
              AlignFunctionPointers == R.AlignFunctionPointers &&
+             AlignMemberVariableDeclarations ==
+                 R.AlignMemberVariableDeclarations &&
              PadOperators == R.PadOperators;
     }
     bool operator!=(const AlignConsecutiveStyle &R) const {
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index b38f2810c0a74..c374445939efe 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -47,38 +47,53 @@ struct ScalarEnumerationTraits<FormatStyle::BreakBeforeNoexceptSpecifierStyle> {
 template <> struct MappingTraits<FormatStyle::AlignConsecutiveStyle> {
   static void enumInput(IO &IO, FormatStyle::AlignConsecutiveStyle &Value) {
     IO.enumCase(Value, "None", FormatStyle::AlignConsecutiveStyle({}));
-    IO.enumCase(Value, "Consecutive",
-                FormatStyle::AlignConsecutiveStyle(
-                    {/*Enabled=*/true, /*AcrossEmptyLines=*/false,
-                     /*AcrossComments=*/false, /*AlignCompound=*/false,
-                     /*AlignFunctionDeclarations=*/true,
-                     /*AlignFunctionPointers=*/false, /*PadOperators=*/true}));
-    IO.enumCase(Value, "AcrossEmptyLines",
-                FormatStyle::AlignConsecutiveStyle(
-                    {/*Enabled=*/true, /*AcrossEmptyLines=*/true,
-                     /*AcrossComments=*/false, /*AlignCompound=*/false,
-                     /*AlignFunctionDeclarations=*/true,
-                     /*AlignFunctionPointers=*/false, /*PadOperators=*/true}));
-    IO.enumCase(Value, "AcrossComments",
-                FormatStyle::AlignConsecutiveStyle(
-                    {/*Enabled=*/true, /*AcrossEmptyLines=*/false,
-                     /*AcrossComments=*/true, /*AlignCompound=*/false,
-                     /*AlignFunctionDeclarations=*/true,
-                     /*AlignFunctionPointers=*/false, /*PadOperators=*/true}));
-    IO.enumCase(Value, "AcrossEmptyLinesAndComments",
-                FormatStyle::AlignConsecutiveStyle(
-                    {/*Enabled=*/true, /*AcrossEmptyLines=*/true,
-                     /*AcrossComments=*/true, /*AlignCompound=*/false,
-                     /*AlignFunctionDeclarations=*/true,
-                     /*AlignFunctionPointers=*/false, /*PadOperators=*/true}));
+    IO.enumCase(
+        Value, "Consecutive",
+        FormatStyle::AlignConsecutiveStyle(
+            {/*Enabled=*/true, /*AcrossEmptyLines=*/false,
+             /*AcrossComments=*/false, /*AlignCompound=*/false,
+             /*AlignFreeVariableDeclarations=*/true,
+             /*AlignFunctionDeclarations=*/true,
+             /*AlignFunctionPointers=*/false,
+             /*AlignMemberVariableDeclarations=*/true, /*PadOperators=*/true}));
+    IO.enumCase(
+        Value, "AcrossEmptyLines",
+        FormatStyle::AlignConsecutiveStyle(
+            {/*Enabled=*/true, /*AcrossEmptyLines=*/true,
+             /*AcrossComments=*/false, /*AlignCompound=*/false,
+             /*AlignFreeVariableDeclarations=*/true,
+             /*AlignFunctionDeclarations=*/true,
+             /*AlignFunctionPointers=*/false,
+             /*AlignMemberVariableDeclarations=*/true, /*PadOperators=*/true}));
+    IO.enumCase(
+        Value, "AcrossComments",
+        FormatStyle::AlignConsecutiveStyle(
+            {/*Enabled=*/true, /*AcrossEmptyLines=*/false,
+             /*AcrossComments=*/true, /*AlignCompound=*/false,
+             /*AlignFreeVariableDeclarations=*/true,
+             /*AlignFunctionDeclarations=*/true,
+             /*AlignFunctionPointers=*/false,
+             /*AlignMemberVariableDeclarations=*/true, /*PadOperators=*/true}));
+    IO.enumCase(
+        Value, "AcrossEmptyLinesAndComments",
+        FormatStyle::AlignConsecutiveStyle(
+            {/*Enabled=*/true, /*AcrossEmptyLines=*/true,
+             /*AcrossComments=*/true, /*AlignCompound=*/false,
+             /*AlignFreeVariableDeclarations=*/true,
+             /*AlignFunctionDeclarations=*/true,
+             /*AlignFunctionPointers=*/false,
+             /*AlignMemberVariableDeclarations=*/true, /*PadOperators=*/true}));
 
     // For backward compatibility.
-    IO.enumCase(Value, "true",
-                FormatStyle::AlignConsecutiveStyle(
-                    {/*Enabled=*/true, /*AcrossEmptyLines=*/false,
-                     /*AcrossComments=*/false, /*AlignCompound=*/false,
-                     /*AlignFunctionDeclarations=*/true,
-                     /*AlignFunctionPointers=*/false, /*PadOperators=*/true}));
+    IO.enumCase(
+        Value, "true",
+        FormatStyle::AlignConsecutiveStyle(
+            {/*Enabled=*/true, /*AcrossEmptyLines=*/false,
+             /*AcrossComments=*/false, /*AlignCompound=*/false,
+             /*AlignFreeVariableDeclarations=*/true,
+             /*AlignFunctionDeclarations=*/true,
+             /*AlignFunctionPointers=*/false,
+             /*AlignMemberVariableDeclarations=*/true, /*PadOperators=*/true}));
     IO.enumCase(Value, "false", FormatStyle::AlignConsecutiveStyle({}));
   }
 
@@ -87,9 +102,13 @@ template <> struct MappingTraits<FormatStyle::AlignConsecutiveStyle> {
     IO.mapOptional("AcrossEmptyLines", Value.AcrossEmptyLines);
     IO.mapOptional("AcrossComments", Value.AcrossComments);
     IO.mapOptional("AlignCompound", Value.AlignCompound);
+    IO.mapOptional("AlignFreeVariableDeclarations",
+                   Value.AlignFreeVariableDeclarations);
     IO.mapOptional("AlignFunctionDeclarations",
                    Value.AlignFunctionDeclarations);
     IO.mapOptional("AlignFunctionPointers", Value.AlignFunctionPointers);
+    IO.mapOptional("AlignMemberVariableDeclarations",
+                   Value.AlignMemberVariableDeclarations);
     IO.mapOptional("PadOperators", Value.PadOperators);
   }
 };
@@ -1555,7 +1574,9 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
   LLVMStyle.AlignConsecutiveAssignments.PadOperators = true;
   LLVMStyle.AlignConsecutiveBitFields = {};
   LLVMStyle.AlignConsecutiveDeclarations = {};
+  LLVMStyle.AlignConsecutiveDeclarations.AlignFreeVariableDeclarations = true;
   LLVMStyle.AlignConsecutiveDeclarations.AlignFunctionDeclarations = true;
+  LLVMStyle.AlignConsecutiveDeclarations.AlignMemberVariableDeclarations = true;
   LLVMStyle.AlignConsecutiveMacros = {};
   LLVMStyle.AlignConsecutiveShortCaseStatements = {};
   LLVMStyle.AlignConsecutiveTableGenBreakingDAGArgColons = {};
diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h
index e04b0e7af10c0..3a94de29461d0 100644
--- a/clang/lib/Format/FormatToken.h
+++ b/clang/lib/Format/FormatToken.h
@@ -313,7 +313,7 @@ struct FormatToken {
         EndsBinaryExpression(false), PartOfMultiVariableDeclStmt(false),
         ContinuesLineCommentSection(false), Finalized(false),
         ClosesRequiresClause(false), EndsCppAttributeGroup(false),
-        BlockKind(BK_Unknown), Decision(FD_Unformatted),
+        IsInClassScope(false), BlockKind(BK_Unknown), Decision(FD_Unformatted),
         PackingKind(PPK_Inconclusive), TypeIsFinalized(false),
         Type(TT_Unknown) {}
 
@@ -391,6 +391,10 @@ struct FormatToken {
   /// \c true if this token ends a group of C++ attributes.
   unsigned EndsCppAttributeGroup : 1;
 
+  /// \c true if this token is within a class-like scope and not within a
+  /// function inside that scope.
+  unsigned IsInClassScope : 1;
+
 private:
   /// Contains the kind of block if this token is a brace.
   unsigned BlockKind : 2;
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 4bfb803ebedf7..da7f50d5f7c2f 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -1313,6 +1313,8 @@ class AnnotatingParser {
     }
     FormatToken *Tok = CurrentToken;
     next();
+    if (!Scopes.empty() && Scopes.back() == ST_Class)
+      Tok->IsInClassScope = true;
     // In Verilog primitives' state tables, `:`, `?`, and `-` aren't normal
     // operators.
     if (Tok->is(TT_VerilogTableItem))
diff --git a/clang/lib/Format/WhitespaceManager.cpp b/clang/lib/Format/WhitespaceManager.cpp
index cc3cc0f6906cc..2c4ad3d1dfbbc 100644
--- a/clang/lib/Format/WhitespaceManager.cpp
+++ b/clang/lib/Format/WhitespaceManager.cpp
@@ -1016,6 +1016,12 @@ void WhitespaceManager::alignConsecutiveDeclarations() {
           return Style.AlignConsecutiveDeclarations.AlignFunctionPointers;
         if (C.Tok->is(TT_FunctionDeclarationName))
           return Style.AlignConsecutiveDeclarations.AlignFunctionDeclarations;
+        if (C.Tok->IsInClassScope && !Style.AlignConsecutiveDeclarations
+                                          .AlignMemberVariableDeclarations)
+          return false;
+        if (!C.Tok->IsInClassScope &&
+            !Style.AlignConsecutiveDeclarations.AlignFreeVariableDeclarations)
+          return false;
         if (C.Tok->isNot(TT_StartOfName))
           return false;
         if (C.Tok->Previous &&
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index bb4d38bb741ec..1c63c3c580179 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ ...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/160270


More information about the cfe-commits mailing list