[llvm] [Support] formatv: non-neative-plus for integral numbers (PR #185008)

via llvm-commits llvm-commits at lists.llvm.org
Fri Mar 6 06:10:10 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-support

Author: Konrad Kleine (kwk)

<details>
<summary>Changes</summary>

The older `format()` allows you to print a `+` sign for non-negative integral numbers upon request.

Examples:

```c++
format("%+d", 255); // -> "+255"
format("%+d", -12); // -> "-12"
```

This change adds the ability to do the same with `formatv()`:

```c++
formatv("{0:+d}", 255); // -> "+255"
formatv("{0:+d}", -12); // -> "-12"
```

The default behaviour is not changed. That means, for a format specifier like "{0:d}" we still print the positive integer without a "+" prefix.

In addition to integers, you can do the same with booleans.

```c++
formatv("{0:+d}", true); // -> "+1"
formatv("{0:+d}", false); // -> "+0"
```

The special case of "+0" is exactly like it is handled with `format()`:

```c++
format("%+d", 0); // -> "+0"
```

This work is related to #<!-- -->35980.

---
Full diff: https://github.com/llvm/llvm-project/pull/185008.diff


6 Files Affected:

- (modified) llvm/include/llvm/Support/FormatProviders.h (+15-2) 
- (modified) llvm/include/llvm/Support/NativeFormatting.h (+7-6) 
- (modified) llvm/lib/DebugInfo/DWARF/DWARFCFIPrinter.cpp (+2-1) 
- (modified) llvm/lib/DebugInfo/DWARF/DWARFExpressionPrinter.cpp (+5-4) 
- (modified) llvm/lib/Support/NativeFormatting.cpp (+24-19) 
- (modified) llvm/unittests/Support/FormatVariadicTest.cpp (+69-4) 


``````````diff
diff --git a/llvm/include/llvm/Support/FormatProviders.h b/llvm/include/llvm/Support/FormatProviders.h
index 3377781873b8c..f8e7c1a410ca5 100644
--- a/llvm/include/llvm/Support/FormatProviders.h
+++ b/llvm/include/llvm/Support/FormatProviders.h
@@ -112,6 +112,8 @@ class HelperFunctions {
 ///   | X+ / X  | Hex + prefix, upper  |   42   |   0x2A  | Minimum # digits   |
 ///   | N / n   | Digit grouped number | 123456 | 123,456 | Ignored            |
 ///   | D / d   | Integer              | 100000 | 100000  | Ignored            |
+///   |+D /+d   | Integer always signed| 100000 | +100000 | Ignored            |
+///   |   +     | Same as +D / +d      |        |         |                    |
 ///   | (empty) | Same as D / d        |        |         |                    |
 ///   ==========================================================================
 ///
@@ -130,6 +132,10 @@ struct format_provider<
       return;
     }
 
+    // A + prefix indicates that a plus sign shall be
+    // prefixed to non-negativ numbers
+    bool NonNegativePlus = Style.consume_front('+');
+
     IntegerStyle IS = IntegerStyle::Integer;
     if (Style.consume_front("N") || Style.consume_front("n"))
       IS = IntegerStyle::Number;
@@ -138,7 +144,11 @@ struct format_provider<
 
     Style.consumeInteger(10, Digits);
     assert(Style.empty() && "Invalid integral format style!");
-    write_integer(Stream, V, Digits, IS);
+
+    // We currently only support the + for integer style numbers.
+    NonNegativePlus = NonNegativePlus && IS == IntegerStyle::Integer;
+
+    write_integer(Stream, V, Digits, IS, NonNegativePlus);
   }
 };
 
@@ -248,7 +258,8 @@ struct format_provider<
 ///   ==================================
 ///   |    Y    |       YES / NO       |
 ///   |    y    |       yes / no       |
-///   |  D / d  |    Integer 0 or 1    |
+///   |  D /  d |    Integer 0 or 1    |
+///   | +D / +d |    Integer +0 or +1  |
 ///   |    T    |     TRUE / FALSE     |
 ///   |    t    |     true / false     |
 ///   | (empty) |   Equivalent to 't'  |
@@ -260,6 +271,7 @@ template <> struct format_provider<bool> {
                   .Case("Y", B ? "YES" : "NO")
                   .Case("y", B ? "yes" : "no")
                   .CaseLower("D", B ? "1" : "0")
+                  .CaseLower("+D", B ? "+1" : "+0")
                   .Case("T", B ? "TRUE" : "FALSE")
                   .Cases({"t", ""}, B ? "true" : "false")
                   .Default(B ? "1" : "0");
@@ -295,6 +307,7 @@ struct format_provider<
     : public support::detail::HelperFunctions {
   static void format(const T &V, llvm::raw_ostream &Stream, StringRef Style) {
     FloatStyle S;
+
     if (Style.consume_front("P") || Style.consume_front("p"))
       S = FloatStyle::Percent;
     else if (Style.consume_front("F") || Style.consume_front("f"))
diff --git a/llvm/include/llvm/Support/NativeFormatting.h b/llvm/include/llvm/Support/NativeFormatting.h
index 45336511ae9b7..ae9134b5e154d 100644
--- a/llvm/include/llvm/Support/NativeFormatting.h
+++ b/llvm/include/llvm/Support/NativeFormatting.h
@@ -27,17 +27,18 @@ LLVM_ABI size_t getDefaultPrecision(FloatStyle Style);
 LLVM_ABI bool isPrefixedHexStyle(HexPrintStyle S);
 
 LLVM_ABI void write_integer(raw_ostream &S, unsigned int N, size_t MinDigits,
-                            IntegerStyle Style);
+                            IntegerStyle Style, bool NonNegativePlus = false);
 LLVM_ABI void write_integer(raw_ostream &S, int N, size_t MinDigits,
-                            IntegerStyle Style);
+                            IntegerStyle Style, bool NonNegativePlus = false);
 LLVM_ABI void write_integer(raw_ostream &S, unsigned long N, size_t MinDigits,
-                            IntegerStyle Style);
+                            IntegerStyle Style, bool NonNegativePlus = false);
 LLVM_ABI void write_integer(raw_ostream &S, long N, size_t MinDigits,
-                            IntegerStyle Style);
+                            IntegerStyle Style, bool NonNegativePlus = false);
 LLVM_ABI void write_integer(raw_ostream &S, unsigned long long N,
-                            size_t MinDigits, IntegerStyle Style);
+                            size_t MinDigits, IntegerStyle Style,
+                            bool NonNegativePlus = false);
 LLVM_ABI void write_integer(raw_ostream &S, long long N, size_t MinDigits,
-                            IntegerStyle Style);
+                            IntegerStyle Style, bool NonNegativePlus = false);
 
 LLVM_ABI void write_hex(raw_ostream &S, uint64_t N, HexPrintStyle Style,
                         std::optional<size_t> Width = std::nullopt);
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFCFIPrinter.cpp b/llvm/lib/DebugInfo/DWARF/DWARFCFIPrinter.cpp
index 4d879b69c387d..0b74f352b4bb1 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFCFIPrinter.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFCFIPrinter.cpp
@@ -13,6 +13,7 @@
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/Format.h"
+#include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/raw_ostream.h"
 #include <cassert>
 #include <cinttypes>
@@ -64,7 +65,7 @@ static void printOperand(raw_ostream &OS, const DIDumpOptions &DumpOpts,
     // The offsets are all encoded in a unsigned form, but in practice
     // consumers use them signed. It's most certainly legacy due to
     // the lack of signed variants in the first Dwarf standards.
-    OS << format(" %+" PRId64, int64_t(Operand));
+    OS << formatv(" {0:+d}", int64_t(Operand));
     break;
   case CFIProgram::OT_FactoredCodeOffset: // Always Unsigned
     if (P.codeAlign())
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFExpressionPrinter.cpp b/llvm/lib/DebugInfo/DWARF/DWARFExpressionPrinter.cpp
index fcd2316c30aef..213e3d13fcdfd 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFExpressionPrinter.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFExpressionPrinter.cpp
@@ -11,6 +11,7 @@
 #include "llvm/DebugInfo/DWARF/DWARFUnit.h"
 #include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h"
 #include "llvm/Support/Format.h"
+#include "llvm/Support/FormatVariadic.h"
 #include <cassert>
 #include <cstdint>
 
@@ -153,7 +154,7 @@ static bool printOp(const DWARFExpression::Operation *Op, raw_ostream &OS,
                        static_cast<uint8_t>(Expr->getData()[Offset++]));
       } else {
         if (Signed)
-          OS << format(" %+" PRId64, (int64_t)Op->getRawOperand(Operand));
+          OS << formatv(" {0:+d}", (int64_t)Op->getRawOperand(Operand));
         else if (Op->getCode() != DW_OP_entry_value &&
                  Op->getCode() != DW_OP_GNU_entry_value)
           OS << format(" 0x%" PRIx64, Op->getRawOperand(Operand));
@@ -257,7 +258,7 @@ static bool printCompactDWARFExpr(
       raw_svector_ostream S(Stack.emplace_back().String);
       S << RegName;
       if (Offset)
-        S << format("%+" PRId64, Offset);
+        S << formatv("{0:+d}", Offset);
       break;
     }
     case dwarf::DW_OP_entry_value:
@@ -310,7 +311,7 @@ static bool printCompactDWARFExpr(
         raw_svector_ostream S(Stack.emplace_back().String);
         S << RegName;
         if (Offset)
-          S << format("%+" PRId64, Offset);
+          S << formatv("{0:+d}", Offset);
       } else {
         return UnknownOpcode(OS, Opcode, std::nullopt);
       }
@@ -364,7 +365,7 @@ bool prettyPrintRegisterOp(DWARFUnit *U, raw_ostream &OS,
   if (!RegName.empty()) {
     if ((Opcode >= DW_OP_breg0 && Opcode <= DW_OP_breg31) ||
         Opcode == DW_OP_bregx || SubOpcode == DW_OP_LLVM_aspace_bregx)
-      OS << ' ' << RegName << format("%+" PRId64, Operands[OpNum]);
+      OS << ' ' << RegName << formatv("{0:+d}", Operands[OpNum]);
     else
       OS << ' ' << RegName.data();
 
diff --git a/llvm/lib/Support/NativeFormatting.cpp b/llvm/lib/Support/NativeFormatting.cpp
index 7a64730434193..d3bb5db4c6728 100644
--- a/llvm/lib/Support/NativeFormatting.cpp
+++ b/llvm/lib/Support/NativeFormatting.cpp
@@ -53,7 +53,8 @@ static void writeWithCommas(raw_ostream &S, ArrayRef<char> Buffer) {
 
 template <typename T>
 static void write_unsigned_impl(raw_ostream &S, T N, size_t MinDigits,
-                                IntegerStyle Style, bool IsNegative) {
+                                IntegerStyle Style, bool IsNegative,
+                                bool NonNegativePlus) {
   static_assert(std::is_unsigned_v<T>, "Value is not unsigned!");
 
   char NumberBuffer[128];
@@ -61,6 +62,8 @@ static void write_unsigned_impl(raw_ostream &S, T N, size_t MinDigits,
 
   if (IsNegative)
     S << '-';
+  else if (NonNegativePlus)
+    S << '+';
 
   if (Len < MinDigits && Style != IntegerStyle::Number) {
     for (size_t I = Len; I < MinDigits; ++I)
@@ -76,59 +79,61 @@ static void write_unsigned_impl(raw_ostream &S, T N, size_t MinDigits,
 
 template <typename T>
 static void write_unsigned(raw_ostream &S, T N, size_t MinDigits,
-                           IntegerStyle Style, bool IsNegative = false) {
+                           IntegerStyle Style, bool IsNegative = false,
+                           bool NonNegativePlus = false) {
   // Output using 32-bit div/mod if possible.
   if (N == static_cast<uint32_t>(N))
     write_unsigned_impl(S, static_cast<uint32_t>(N), MinDigits, Style,
-                        IsNegative);
+                        IsNegative, NonNegativePlus);
   else
-    write_unsigned_impl(S, N, MinDigits, Style, IsNegative);
+    write_unsigned_impl(S, N, MinDigits, Style, IsNegative, NonNegativePlus);
 }
 
 template <typename T>
 static void write_signed(raw_ostream &S, T N, size_t MinDigits,
-                         IntegerStyle Style) {
+                         IntegerStyle Style, bool NonNegativePlus = false) {
   static_assert(std::is_signed_v<T>, "Value is not signed!");
 
   using UnsignedT = std::make_unsigned_t<T>;
 
   if (N >= 0) {
-    write_unsigned(S, static_cast<UnsignedT>(N), MinDigits, Style);
+    write_unsigned(S, static_cast<UnsignedT>(N), MinDigits, Style, false,
+                   NonNegativePlus);
     return;
   }
 
   UnsignedT UN = -(UnsignedT)N;
-  write_unsigned(S, UN, MinDigits, Style, true);
+  write_unsigned(S, UN, MinDigits, Style, true, NonNegativePlus);
 }
 
 void llvm::write_integer(raw_ostream &S, unsigned int N, size_t MinDigits,
-                         IntegerStyle Style) {
-  write_unsigned(S, N, MinDigits, Style);
+                         IntegerStyle Style, bool NonNegativePlus) {
+  write_unsigned(S, N, MinDigits, Style, false, NonNegativePlus);
 }
 
 void llvm::write_integer(raw_ostream &S, int N, size_t MinDigits,
-                         IntegerStyle Style) {
-  write_signed(S, N, MinDigits, Style);
+                         IntegerStyle Style, bool NonNegativePlus) {
+  write_signed(S, N, MinDigits, Style, NonNegativePlus);
 }
 
 void llvm::write_integer(raw_ostream &S, unsigned long N, size_t MinDigits,
-                         IntegerStyle Style) {
-  write_unsigned(S, N, MinDigits, Style);
+                         IntegerStyle Style, bool NonNegativePlus) {
+  write_unsigned(S, N, MinDigits, Style, false, NonNegativePlus);
 }
 
 void llvm::write_integer(raw_ostream &S, long N, size_t MinDigits,
-                         IntegerStyle Style) {
-  write_signed(S, N, MinDigits, Style);
+                         IntegerStyle Style, bool NonNegativePlus) {
+  write_signed(S, N, MinDigits, Style, NonNegativePlus);
 }
 
 void llvm::write_integer(raw_ostream &S, unsigned long long N, size_t MinDigits,
-                         IntegerStyle Style) {
-  write_unsigned(S, N, MinDigits, Style);
+                         IntegerStyle Style, bool NoneNegativePlus) {
+  write_unsigned(S, N, MinDigits, Style, false, NoneNegativePlus);
 }
 
 void llvm::write_integer(raw_ostream &S, long long N, size_t MinDigits,
-                         IntegerStyle Style) {
-  write_signed(S, N, MinDigits, Style);
+                         IntegerStyle Style, bool NonNegativePlus) {
+  write_signed(S, N, MinDigits, Style, NonNegativePlus);
 }
 
 void llvm::write_hex(raw_ostream &S, uint64_t N, HexPrintStyle Style,
diff --git a/llvm/unittests/Support/FormatVariadicTest.cpp b/llvm/unittests/Support/FormatVariadicTest.cpp
index a3e9841536fdc..ea4e046a7350f 100644
--- a/llvm/unittests/Support/FormatVariadicTest.cpp
+++ b/llvm/unittests/Support/FormatVariadicTest.cpp
@@ -391,7 +391,7 @@ TEST(FormatVariadicTest, IntegralHexFormatting) {
 }
 
 template <typename FormatTy>
-std::string printToString(unsigned MaxN, FormatTy &&Fmt) {
+static std::string printToString(FormatTy &&Fmt, unsigned MaxN = 100) {
   std::vector<char> Dst(MaxN + 2);
   int N = Fmt.snprint(Dst.data(), Dst.size());
   Dst.back() = 0;
@@ -402,18 +402,83 @@ TEST(FormatAndFormatvTest, EquivalentHexFormatting) {
   uint8_t HexDigits = 10;
   uint64_t N = 255;
 
+  // with format()
+  // -------------
   // Here's the old format() way of printing a hex number with
   // dynamic width and precision, both being the same.
-  EXPECT_EQ(
-      "0x00000000ff",
-      printToString(100, format("0x%*.*" PRIx64, HexDigits, HexDigits, N)));
+  EXPECT_EQ("0x00000000ff",
+            printToString(format("0x%*.*" PRIx64, HexDigits, HexDigits, N)));
 
+  // with formatv()
+  // --------------
   // Now, do the same with formatv()
   EXPECT_EQ("0x00000000ff",
             formatv("0x{0:x-}", fmt_align(N, AlignStyle::Right, HexDigits, '0'))
                 .str());
 }
 
+TEST(FormatAndFormatvTest, NonNegativePlusBool) {
+  // with format()
+  // -------------
+  // Check that +d works for boolean values as well and the default is not
+  // changed for "d"
+  EXPECT_EQ("+1", printToString(format("%+d", true)));
+  EXPECT_EQ("+0", printToString(format("%+d", false)));
+
+  EXPECT_EQ("1", printToString(format("%d", true)));
+  EXPECT_EQ("0", printToString(format("%d", false)));
+
+  // with formatv()
+  // --------------
+  EXPECT_EQ("+1", formatv("{0:+d}", true).str());
+  EXPECT_EQ("+0", formatv("{0:+d}", false).str());
+
+  EXPECT_EQ("1", formatv("{0:d}", true).str());
+  EXPECT_EQ("0", formatv("{0:d}", false).str());
+}
+
+TEST(FormatAndFormatvTest, NonNegativePlusInteger) {
+  // with format()
+  // -------------
+  EXPECT_EQ("-255", printToString(format("%+d", -255)));
+  EXPECT_EQ("+255", printToString(format("%+d", 255)));
+  EXPECT_EQ("+0", printToString(format("%+d", 0)));
+  EXPECT_EQ("-7", printToString(format("%+d", -7)));
+
+  // with formatv()
+  // --------------
+  // Check that +d works for signed and unsigned integral values.
+  // Ensure the default is not changes (a + is not added without requesting it).
+  EXPECT_EQ("+1", formatv("{0:+d}", static_cast<unsigned int>(1)).str());
+  EXPECT_EQ("+2", formatv("{0:+d}", static_cast<int>(2)).str());
+  EXPECT_EQ("+3", formatv("{0:+d}", static_cast<unsigned long>(3)).str());
+  EXPECT_EQ("+4", formatv("{0:+d}", static_cast<long>(4)).str());
+  EXPECT_EQ("+5", formatv("{0:+d}", static_cast<unsigned long long>(5)).str());
+  EXPECT_EQ("+6", formatv("{0:+d}", static_cast<long long>(6)).str());
+  EXPECT_EQ("-7", formatv("{0:+d}", static_cast<int>(-7)).str());
+  EXPECT_EQ("-8", formatv("{0:+d}", static_cast<long>(-8)).str());
+  EXPECT_EQ("-9", formatv("{0:+d}", static_cast<long long>(-9)).str());
+
+  EXPECT_EQ("11", formatv("{0:d}", static_cast<unsigned int>(11)).str());
+  EXPECT_EQ("22", formatv("{0:d}", static_cast<int>(22)).str());
+  EXPECT_EQ("33", formatv("{0:d}", static_cast<unsigned long>(33)).str());
+  EXPECT_EQ("44", formatv("{0:d}", static_cast<long>(44)).str());
+  EXPECT_EQ("55", formatv("{0:d}", static_cast<unsigned long long>(55)).str());
+  EXPECT_EQ("66", formatv("{0:d}", static_cast<long long>(66)).str());
+  EXPECT_EQ("-77", formatv("{0:d}", static_cast<int>(-77)).str());
+  EXPECT_EQ("-88", formatv("{0:d}", static_cast<long>(-88)).str());
+  EXPECT_EQ("-99", formatv("{0:d}", static_cast<long long>(-99)).str());
+
+  // Ensure that 0 is also prefixed with + (old behaviour from format(), see
+  // above).
+  EXPECT_EQ("+0", formatv("{0:+d}", 0).str());
+  EXPECT_EQ("0", formatv("{0:d}", 0).str());
+
+  // Ensure that an empty or otherwise empty format string is also working as
+  // expected
+  EXPECT_EQ("+333", formatv("{0:+}", 333).str());
+  EXPECT_EQ("444", formatv("{0:}", 444).str());
+}
 TEST(FormatVariadicTest, PointerFormatting) {
   // 1. Trivial cases.  Hex is default.  Default Precision is pointer width.
   if (sizeof(void *) == 4) {

``````````

</details>


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


More information about the llvm-commits mailing list