[clang] [clang-format] Add xxxMaxDigitsNoSeparator (PR #164286)

Björn Schäpers via cfe-commits cfe-commits at lists.llvm.org
Tue Oct 28 14:34:35 PDT 2025


https://github.com/HazardyKnusperkeks updated https://github.com/llvm/llvm-project/pull/164286

>From f4be7c77489ddb427a019eefdae8817501132ef5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Sch=C3=A4pers?= <bjoern at hazardy.de>
Date: Mon, 20 Oct 2025 01:31:04 +0200
Subject: [PATCH 1/3] [clang-format] Add xxxMaxDigitsNoSeparator

This basically adds a Leave option for a specific range of literals.
---
 clang/docs/ClangFormatStyleOptions.rst        | 44 +++++++++++++++++-
 clang/include/clang/Format/Format.h           | 46 ++++++++++++++++++-
 clang/lib/Format/Format.cpp                   | 18 ++++++--
 .../Format/IntegerLiteralSeparatorFixer.cpp   | 38 ++++++++++++---
 .../Format/IntegerLiteralSeparatorTest.cpp    | 16 +++++++
 5 files changed, 148 insertions(+), 14 deletions(-)

diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 570cab262c115..25160b137535e 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -4651,7 +4651,12 @@ the configuration (without a prefix: ``Auto``).
 
   You can also specify a minimum number of digits (``BinaryMinDigits``,
   ``DecimalMinDigits``, and ``HexMinDigits``) the integer literal must
-  have in order for the separators to be inserted.
+  have in order for the separators to be inserted, and a maximum number of
+  digits (``BinaryMaxDigitsNoSeparator``, ``DecimalMaxDigitsNoSeparator``,
+  and ``HexMaxDigitsNoSeparator``) until the separators are removed. This
+  divides the literals in 3 regions, always without separator (up until
+  including ``xxxMaxDigitsNoSeparator``), maybe with, or without separators
+  (up until excluding ``xxxMinDigits``), and finally always with separators.
 
   * ``int8_t Binary`` Format separators in binary literals.
 
@@ -4671,6 +4676,18 @@ the configuration (without a prefix: ``Auto``).
       b1 = 0b101101;
       b2 = 0b1'101'101;
 
+  * ``int8_t BinaryMaxDigitsNoSeparator`` Remove separators in binary literals with a maximum number of digits.
+
+    .. code-block:: text
+
+      // Binary: 3
+      // BinaryMinDigits: 7
+      // BinaryMaxDigitsNoSeparator: 4
+      b0 = 0b1011; // Always removed.
+      b1 = 0b101101; // Not added.
+      b2 = 0b101'101; // Not removed.
+      b3 = 0b1'101'101; // Always added.
+
   * ``int8_t Decimal`` Format separators in decimal literals.
 
     .. code-block:: text
@@ -4688,6 +4705,18 @@ the configuration (without a prefix: ``Auto``).
       d1 = 2023;
       d2 = 10'000;
 
+  * ``int8_t DecimalMaxDigitsNoSeparator`` Remove separators in decimal literals with a maximum number of digits.
+
+    .. code-block:: text
+
+      // Decimal: 3
+      // DecimalMinDigits: 7
+      // DecimalMaxDigitsNoSeparator: 4
+      d0 = 2023; // Always removed.
+      d1 = 123456; // Not added.
+      d2 = 123'456; // Not removed.
+      d3 = 5'000'000; // Always added.
+
   * ``int8_t Hex`` Format separators in hexadecimal literals.
 
     .. code-block:: text
@@ -4706,6 +4735,19 @@ the configuration (without a prefix: ``Auto``).
       h1 = 0xABCDE;
       h2 = 0xAB'CD'EF;
 
+  * ``int8_t HexMaxDigitsNoSeparator`` Remove separators in hexadecimal literals with a maximum number of
+    digits.
+
+    .. code-block:: text
+
+      // Hex: 2
+      // HexMinDigits: 6
+      // HexMaxDigitsNoSeparator: 4
+      h0 = 0xAFFE; // Always removed.
+      h1 = 0xABCDE; // Not added.
+      h2 = 0xA'BC'DE; // Not removed.
+      h3 = 0xAB'CD'EF; // Always added.
+
 
 .. _JavaImportGroups:
 
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 2852c4a2916a4..4e1974b8f9460 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -3174,7 +3174,12 @@ struct FormatStyle {
   ///
   /// You can also specify a minimum number of digits (``BinaryMinDigits``,
   /// ``DecimalMinDigits``, and ``HexMinDigits``) the integer literal must
-  /// have in order for the separators to be inserted.
+  /// have in order for the separators to be inserted, and a maximum number of
+  /// digits (``BinaryMaxDigitsNoSeparator``, ``DecimalMaxDigitsNoSeparator``,
+  /// and ``HexMaxDigitsNoSeparator``) until the separators are removed. This
+  /// divides the literals in 3 regions, always without separator (up until
+  /// including ``xxxMaxDigitsNoSeparator``), maybe with, or without separators
+  /// (up until excluding ``xxxMinDigits``), and finally always with separators.
   struct IntegerLiteralSeparatorStyle {
     /// Format separators in binary literals.
     /// \code{.text}
@@ -3192,6 +3197,17 @@ struct FormatStyle {
     ///   b2 = 0b1'101'101;
     /// \endcode
     int8_t BinaryMinDigits;
+    /// Remove separators in binary literals with a maximum number of digits.
+    /// \code{.text}
+    ///   // Binary: 3
+    ///   // BinaryMinDigits: 7
+    ///   // BinaryMaxDigitsNoSeparator: 4
+    ///   b0 = 0b1011; // Always removed.
+    ///   b1 = 0b101101; // Not added.
+    ///   b2 = 0b101'101; // Not removed.
+    ///   b3 = 0b1'101'101; // Always added.
+    /// \endcode
+    int8_t BinaryMaxDigitsNoSeparator;
     /// Format separators in decimal literals.
     /// \code{.text}
     ///   /* -1: */ d = 18446744073709550592ull;
@@ -3207,6 +3223,17 @@ struct FormatStyle {
     ///   d2 = 10'000;
     /// \endcode
     int8_t DecimalMinDigits;
+    /// Remove separators in decimal literals with a maximum number of digits.
+    /// \code{.text}
+    ///   // Decimal: 3
+    ///   // DecimalMinDigits: 7
+    ///   // DecimalMaxDigitsNoSeparator: 4
+    ///   d0 = 2023; // Always removed.
+    ///   d1 = 123456; // Not added.
+    ///   d2 = 123'456; // Not removed.
+    ///   d3 = 5'000'000; // Always added.
+    /// \endcode
+    int8_t DecimalMaxDigitsNoSeparator;
     /// Format separators in hexadecimal literals.
     /// \code{.text}
     ///   /* -1: */ h = 0xDEADBEEFDEADBEEFuz;
@@ -3223,10 +3250,25 @@ struct FormatStyle {
     ///   h2 = 0xAB'CD'EF;
     /// \endcode
     int8_t HexMinDigits;
+    /// Remove separators in hexadecimal literals with a maximum number of
+    /// digits.
+    /// \code{.text}
+    ///   // Hex: 2
+    ///   // HexMinDigits: 6
+    ///   // HexMaxDigitsNoSeparator: 4
+    ///   h0 = 0xAFFE; // Always removed.
+    ///   h1 = 0xABCDE; // Not added.
+    ///   h2 = 0xA'BC'DE; // Not removed.
+    ///   h3 = 0xAB'CD'EF; // Always added.
+    /// \endcode
+    int8_t HexMaxDigitsNoSeparator;
     bool operator==(const IntegerLiteralSeparatorStyle &R) const {
       return Binary == R.Binary && BinaryMinDigits == R.BinaryMinDigits &&
+             BinaryMaxDigitsNoSeparator == R.BinaryMaxDigitsNoSeparator &&
              Decimal == R.Decimal && DecimalMinDigits == R.DecimalMinDigits &&
-             Hex == R.Hex && HexMinDigits == R.HexMinDigits;
+             DecimalMaxDigitsNoSeparator == R.DecimalMaxDigitsNoSeparator &&
+             Hex == R.Hex && HexMinDigits == R.HexMinDigits &&
+             HexMaxDigitsNoSeparator == R.HexMaxDigitsNoSeparator;
     }
   };
 
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index edd126c7724b8..59495467c01b4 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -399,10 +399,15 @@ template <> struct MappingTraits<FormatStyle::IntegerLiteralSeparatorStyle> {
   static void mapping(IO &IO, FormatStyle::IntegerLiteralSeparatorStyle &Base) {
     IO.mapOptional("Binary", Base.Binary);
     IO.mapOptional("BinaryMinDigits", Base.BinaryMinDigits);
+    IO.mapOptional("BinaryMaxDigitsNoSeparator",
+                   Base.BinaryMaxDigitsNoSeparator);
     IO.mapOptional("Decimal", Base.Decimal);
     IO.mapOptional("DecimalMinDigits", Base.DecimalMinDigits);
+    IO.mapOptional("DecimalMaxDigitsNoSeparator",
+                   Base.DecimalMaxDigitsNoSeparator);
     IO.mapOptional("Hex", Base.Hex);
     IO.mapOptional("HexMinDigits", Base.HexMinDigits);
+    IO.mapOptional("HexMaxDigitsNoSeparator", Base.HexMaxDigitsNoSeparator);
   }
 };
 
@@ -1673,10 +1678,15 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
   LLVMStyle.InsertBraces = false;
   LLVMStyle.InsertNewlineAtEOF = false;
   LLVMStyle.InsertTrailingCommas = FormatStyle::TCS_None;
-  LLVMStyle.IntegerLiteralSeparator = {
-      /*Binary=*/0,  /*BinaryMinDigits=*/0,
-      /*Decimal=*/0, /*DecimalMinDigits=*/0,
-      /*Hex=*/0,     /*HexMinDigits=*/0};
+  LLVMStyle.IntegerLiteralSeparator = {/*Binary=*/0,
+                                       /*BinaryMinDigits=*/0,
+                                       /*BinaryMaxDigitsNoSeparator=*/-1,
+                                       /*Decimal=*/0,
+                                       /*DecimalMinDigits=*/0,
+                                       /*DecimalMaxDigitsNoSeparator=*/-1,
+                                       /*Hex=*/0,
+                                       /*HexMinDigits=*/0,
+                                       /*HexMaxDigitsNoSeparator=*/-1};
   LLVMStyle.JavaScriptQuotes = FormatStyle::JSQS_Leave;
   LLVMStyle.JavaScriptWrapImports = true;
   LLVMStyle.KeepEmptyLines = {
diff --git a/clang/lib/Format/IntegerLiteralSeparatorFixer.cpp b/clang/lib/Format/IntegerLiteralSeparatorFixer.cpp
index b51991bfeff4b..ae3b886d7e491 100644
--- a/clang/lib/Format/IntegerLiteralSeparatorFixer.cpp
+++ b/clang/lib/Format/IntegerLiteralSeparatorFixer.cpp
@@ -72,11 +72,25 @@ IntegerLiteralSeparatorFixer::process(const Environment &Env,
   if (SkipBinary && SkipDecimal && SkipHex)
     return {};
 
-  const auto BinaryMinDigits =
-      std::max((int)Option.BinaryMinDigits, Binary + 1);
-  const auto DecimalMinDigits =
-      std::max((int)Option.DecimalMinDigits, Decimal + 1);
-  const auto HexMinDigits = std::max((int)Option.HexMinDigits, Hex + 1);
+  auto CalcMinAndMax = [](int8_t Digits, int8_t MinDigits,
+                          int8_t MaxDigitsNoSeparator) {
+    std::pair<int, int> Ret;
+    Ret.first = std::max<int>(MinDigits, Digits + 1);
+    if (Ret.first == 0)
+      Ret.second = 0;
+    else if (MaxDigitsNoSeparator < 0)
+      Ret.second = Ret.first - 1;
+    else
+      Ret.second = std::min<int>(MaxDigitsNoSeparator, Ret.first - 1);
+    return Ret;
+  };
+
+  const auto [BinaryMinDigits, BinaryMaxDigitsNoSeparator] = CalcMinAndMax(
+      Binary, Option.BinaryMinDigits, Option.BinaryMaxDigitsNoSeparator);
+  const auto [DecimalMinDigits, DecimalMaxDigitsNoSeparator] = CalcMinAndMax(
+      Decimal, Option.DecimalMinDigits, Option.DecimalMaxDigitsNoSeparator);
+  const auto [HexMinDigits, HexMaxDigitsNoSeparator] =
+      CalcMinAndMax(Hex, Option.HexMinDigits, Option.HexMaxDigitsNoSeparator);
 
   const auto &SourceMgr = Env.getSourceManager();
   AffectedRangeManager AffectedRangeMgr(SourceMgr, Env.getCharRanges());
@@ -139,19 +153,29 @@ IntegerLiteralSeparatorFixer::process(const Environment &Env,
     }
     auto DigitsPerGroup = Decimal;
     auto MinDigits = DecimalMinDigits;
+    auto MaxDigitsNoSeparator = DecimalMaxDigitsNoSeparator;
     if (IsBase2) {
       DigitsPerGroup = Binary;
       MinDigits = BinaryMinDigits;
+      MaxDigitsNoSeparator = BinaryMaxDigitsNoSeparator;
     } else if (IsBase16) {
       DigitsPerGroup = Hex;
       MinDigits = HexMinDigits;
+      MaxDigitsNoSeparator = HexMaxDigitsNoSeparator;
     }
     const auto SeparatorCount = Text.count(Separator);
     const int DigitCount = Length - SeparatorCount;
-    const bool RemoveSeparator = DigitsPerGroup < 0 || DigitCount < MinDigits;
+    const bool RemoveSeparator =
+        DigitsPerGroup < 0 || DigitCount <= MaxDigitsNoSeparator;
+    const bool AddSeparator =
+        DigitsPerGroup > 0 &&
+        (DigitCount >= MinDigits ||
+         (DigitCount > MaxDigitsNoSeparator && SeparatorCount > 0));
+    if (!RemoveSeparator && !AddSeparator)
+      continue;
     if (RemoveSeparator && SeparatorCount == 0)
       continue;
-    if (!RemoveSeparator && SeparatorCount > 0 &&
+    if (AddSeparator && SeparatorCount > 0 &&
         checkSeparator(Text, DigitsPerGroup)) {
       continue;
     }
diff --git a/clang/unittests/Format/IntegerLiteralSeparatorTest.cpp b/clang/unittests/Format/IntegerLiteralSeparatorTest.cpp
index 53b6dd8efadff..d66de86be88b0 100644
--- a/clang/unittests/Format/IntegerLiteralSeparatorTest.cpp
+++ b/clang/unittests/Format/IntegerLiteralSeparatorTest.cpp
@@ -243,6 +243,22 @@ TEST_F(IntegerLiteralSeparatorTest, FloatingPoint) {
                Style);
 }
 
+TEST_F(IntegerLiteralSeparatorTest, MaxDigitsNoSeparator) {
+  auto Style = getLLVMStyle();
+  Style.IntegerLiteralSeparator.Decimal = 3;
+  Style.IntegerLiteralSeparator.DecimalMaxDigitsNoSeparator = 4;
+  Style.IntegerLiteralSeparator.DecimalMinDigits = 7;
+  verifyFormat("d0 = 2023;\n"
+               "d1 = 123456;\n"
+               "d2 = 123'456;\n"
+               "d3 = 5'000'000;",
+               "d0 = 20'2'3;\n"
+               "d1 = 123456;\n"
+               "d2 = 1234'56;\n"
+               "d3 = 5000000;",
+               Style);
+}
+
 } // namespace
 } // namespace test
 } // namespace format

>From cd21d8443f59f4257098b11781ed556e7834b577 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Sch=C3=A4pers?= <bjoern at hazardy.de>
Date: Tue, 21 Oct 2025 00:00:14 +0200
Subject: [PATCH 2/3] Add config parse test

---
 clang/include/clang/Format/Format.h        |  3 +++
 clang/unittests/Format/ConfigParseTest.cpp | 22 ++++++++++++++++++++++
 2 files changed, 25 insertions(+)

diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 4e1974b8f9460..64069dac04e2b 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -3270,6 +3270,9 @@ struct FormatStyle {
              Hex == R.Hex && HexMinDigits == R.HexMinDigits &&
              HexMaxDigitsNoSeparator == R.HexMaxDigitsNoSeparator;
     }
+    bool operator!=(const IntegerLiteralSeparatorStyle &R) const {
+      return !operator==(R);
+    }
   };
 
   /// Format integer literal separators (``'`` for C++ and ``_`` for C#, Java,
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 6488e38badee7..3906d5783bdb3 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -1150,6 +1150,28 @@ TEST(ConfigParseTest, ParsesConfiguration) {
               FormatStyle::BLS_Block);
   CHECK_PARSE("Cpp11BracedListStyle: true", Cpp11BracedListStyle,
               FormatStyle::BLS_AlignFirstComment);
+
+  const FormatStyle::IntegerLiteralSeparatorStyle
+      ExpectedIntegerLiteralSeparatorStyle{/*Binary=*/2,
+                                           /*BinaryMinDigit=*/5,
+                                           /*BinaryMaxDigitNoSeparator=*/2,
+                                           /*Decimal=*/6,
+                                           /*DecimalMinDigit=*/6,
+                                           /*DecimalMaxDigitNoSeparator=*/3,
+                                           /*Hex=*/4,
+                                           /*HexMinDigit=*/2,
+                                           /*HexMaxDigitNoSeparator=*/1};
+  CHECK_PARSE("IntegerLiteralSeparator:\n"
+              "  Binary: 2\n"
+              "  BinaryMinDigits: 5\n"
+              "  BinaryMaxDigitsNoSeparator: 2\n"
+              "  Decimal: 6\n"
+              "  DecimalMinDigits: 6\n"
+              "  DecimalMaxDigitsNoSeparator: 3\n"
+              "  Hex: 4\n"
+              "  HexMinDigits: 2\n"
+              "  HexMaxDigitsNoSeparator: 1",
+              IntegerLiteralSeparator, ExpectedIntegerLiteralSeparatorStyle);
 }
 
 TEST(ConfigParseTest, ParsesConfigurationWithLanguages) {

>From a3b63280d9e4eff4db94e7f8df045ba15b1225f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Sch=C3=A4pers?= <bjoern at hazardy.de>
Date: Tue, 28 Oct 2025 22:34:25 +0100
Subject: [PATCH 3/3] Update doc and add release note

---
 clang/docs/ClangFormatStyleOptions.rst | 3 +++
 clang/docs/ReleaseNotes.rst            | 1 +
 clang/include/clang/Format/Format.h    | 3 +++
 3 files changed, 7 insertions(+)

diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 25160b137535e..bae0f588ce0e8 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -4687,6 +4687,7 @@ the configuration (without a prefix: ``Auto``).
       b1 = 0b101101; // Not added.
       b2 = 0b101'101; // Not removed.
       b3 = 0b1'101'101; // Always added.
+      b4 = 0b10'1101; // Corrected to 0b101'101.
 
   * ``int8_t Decimal`` Format separators in decimal literals.
 
@@ -4716,6 +4717,7 @@ the configuration (without a prefix: ``Auto``).
       d1 = 123456; // Not added.
       d2 = 123'456; // Not removed.
       d3 = 5'000'000; // Always added.
+      d4 = 1'23'45; // Corrected to 12'345.
 
   * ``int8_t Hex`` Format separators in hexadecimal literals.
 
@@ -4747,6 +4749,7 @@ the configuration (without a prefix: ``Auto``).
       h1 = 0xABCDE; // Not added.
       h2 = 0xA'BC'DE; // Not removed.
       h3 = 0xAB'CD'EF; // Always added.
+      h4 = 0xABCD'E; // Corrected to 0xA'BC'DE.
 
 
 .. _JavaImportGroups:
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index fe77f917bb801..aa077f51d9ce5 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -609,6 +609,7 @@ clang-format
   literals.
 - Add ``Leave`` suboption to ``IndentPPDirectives``.
 - Add ``AllowBreakBeforeQtProperty`` option.
+- Add ``(Binary|Decimal|Hex)MaxDigitsSeparator`` suboptions to ``IntegerLiteralSeparator``.
 
 libclang
 --------
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 64069dac04e2b..3fd4cd3ce95dd 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -3206,6 +3206,7 @@ struct FormatStyle {
     ///   b1 = 0b101101; // Not added.
     ///   b2 = 0b101'101; // Not removed.
     ///   b3 = 0b1'101'101; // Always added.
+    ///   b4 = 0b10'1101; // Corrected to 0b101'101.
     /// \endcode
     int8_t BinaryMaxDigitsNoSeparator;
     /// Format separators in decimal literals.
@@ -3232,6 +3233,7 @@ struct FormatStyle {
     ///   d1 = 123456; // Not added.
     ///   d2 = 123'456; // Not removed.
     ///   d3 = 5'000'000; // Always added.
+    ///   d4 = 1'23'45; // Corrected to 12'345.
     /// \endcode
     int8_t DecimalMaxDigitsNoSeparator;
     /// Format separators in hexadecimal literals.
@@ -3260,6 +3262,7 @@ struct FormatStyle {
     ///   h1 = 0xABCDE; // Not added.
     ///   h2 = 0xA'BC'DE; // Not removed.
     ///   h3 = 0xAB'CD'EF; // Always added.
+    ///   h4 = 0xABCD'E; // Corrected to 0xA'BC'DE.
     /// \endcode
     int8_t HexMaxDigitsNoSeparator;
     bool operator==(const IntegerLiteralSeparatorStyle &R) const {



More information about the cfe-commits mailing list