Skip to content

Commit d5d6b44

Browse files
authored
[Support] Add automatic index assignment in formatv (#107459)
Make index in replacement field optional. It will be automatically assigned in incremental order by formatv. Make mixed use of automatic and explicit indices an error that will fail validation. Adopt uses of formatv() within FormatVariadic to use automatic index.
1 parent ddd2af3 commit d5d6b44

File tree

3 files changed

+82
-16
lines changed

3 files changed

+82
-16
lines changed

llvm/include/llvm/Support/FormatVariadic.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,16 +167,20 @@ template <typename Tuple> class formatv_object : public formatv_object_base {
167167
// Formats textual output. `Fmt` is a string consisting of one or more
168168
// replacement sequences with the following grammar:
169169
//
170-
// rep_field ::= "{" index ["," layout] [":" format] "}"
170+
// rep_field ::= "{" [index] ["," layout] [":" format] "}"
171171
// index ::= <non-negative integer>
172172
// layout ::= [[[char]loc]width]
173173
// format ::= <any string not containing "{" or "}">
174174
// char ::= <any character except "{" or "}">
175175
// loc ::= "-" | "=" | "+"
176176
// width ::= <positive integer>
177177
//
178-
// index - A non-negative integer specifying the index of the item in the
179-
// parameter pack to print. Any other value is invalid.
178+
// index - An optional non-negative integer specifying the index of the item
179+
// in the parameter pack to print. Any other value is invalid. If its
180+
// not specified, it will be automatically assigned a value based on
181+
// the order of rep_field seen in the format string. Note that mixing
182+
// automatic and explicit index in the same call is an error and will
183+
// fail validation in assert-enabled builds.
180184
// layout - A string controlling how the field is laid out within the available
181185
// space.
182186
// format - A type-dependent string used to provide additional options to

llvm/lib/Support/FormatVariadic.cpp

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,25 +63,29 @@ static std::optional<ReplacementItem> parseReplacementItem(StringRef Spec) {
6363
unsigned Align = 0;
6464
AlignStyle Where = AlignStyle::Right;
6565
StringRef Options;
66-
unsigned Index = 0;
66+
unsigned Index = ~0U;
6767
RepString = RepString.trim();
68-
if (RepString.consumeInteger(0, Index)) {
69-
assert(false && "Invalid replacement sequence index!");
70-
return std::nullopt;
71-
}
68+
69+
// If index is not specified, keep it ~0U to indicate unresolved index.
70+
RepString.consumeInteger(0, Index);
7271
RepString = RepString.trim();
72+
7373
if (RepString.consume_front(",")) {
74-
if (!consumeFieldLayout(RepString, Where, Align, Pad))
74+
if (!consumeFieldLayout(RepString, Where, Align, Pad)) {
7575
assert(false && "Invalid replacement field layout specification!");
76+
return std::nullopt;
77+
}
7678
}
7779
RepString = RepString.trim();
7880
if (RepString.consume_front(":")) {
7981
Options = RepString.trim();
8082
RepString = StringRef();
8183
}
8284
RepString = RepString.trim();
83-
assert(RepString.empty() &&
84-
"Unexpected characters found in replacement string!");
85+
if (!RepString.empty()) {
86+
assert(0 && "Unexpected characters found in replacement string!");
87+
return std::nullopt;
88+
}
8589

8690
return ReplacementItem(Spec, Index, Align, Where, Pad, Options);
8791
}
@@ -139,6 +143,7 @@ SmallVector<ReplacementItem, 2>
139143
formatv_object_base::parseFormatString(StringRef Fmt, size_t NumArgs,
140144
bool Validate) {
141145
SmallVector<ReplacementItem, 2> Replacements;
146+
unsigned NextAutomaticIndex = 0;
142147

143148
#if ENABLE_VALIDATION
144149
const StringRef SavedFmtStr = Fmt;
@@ -150,6 +155,9 @@ formatv_object_base::parseFormatString(StringRef Fmt, size_t NumArgs,
150155
std::tie(I, Fmt) = splitLiteralAndReplacement(Fmt);
151156
if (!I)
152157
continue;
158+
if (I->Index == ~0U)
159+
I->Index = NextAutomaticIndex++;
160+
153161
Replacements.emplace_back(*I);
154162
#if ENABLE_VALIDATION
155163
if (I->Type == ReplacementType::Format)
@@ -175,9 +183,8 @@ formatv_object_base::parseFormatString(StringRef Fmt, size_t NumArgs,
175183
};
176184

177185
if (NumExpectedArgs != NumArgs) {
178-
errs() << formatv(
179-
"Expected {0} Args, but got {1} for format string '{2}'\n",
180-
NumExpectedArgs, NumArgs, SavedFmtStr);
186+
errs() << formatv("Expected {} Args, but got {} for format string '{}'\n",
187+
NumExpectedArgs, NumArgs, SavedFmtStr);
181188
assert(0 && "Invalid formatv() call");
182189
return getErrorReplacements("Unexpected number of arguments");
183190
}
@@ -195,11 +202,21 @@ formatv_object_base::parseFormatString(StringRef Fmt, size_t NumArgs,
195202

196203
if (Count != NumExpectedArgs) {
197204
errs() << formatv(
198-
"Replacement field indices cannot have holes for format string '{0}'\n",
205+
"Replacement field indices cannot have holes for format string '{}'\n",
199206
SavedFmtStr);
200207
assert(0 && "Invalid format string");
201208
return getErrorReplacements("Replacement indices have holes");
202209
}
210+
211+
// If we had automatic numbering of replacement indices, verify that all
212+
// indices used automatic numbering.
213+
if (NextAutomaticIndex != 0 && NextAutomaticIndex != Count) {
214+
errs() << formatv(
215+
"Cannot mix automatic and explicit indices for format string '{}'\n",
216+
SavedFmtStr);
217+
assert(0 && "Invalid format string");
218+
return getErrorReplacements("Cannot mix automatic and explicit indices");
219+
}
203220
#endif // ENABLE_VALIDATION
204221
return Replacements;
205222
}

llvm/unittests/Support/FormatVariadicTest.cpp

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,14 +269,50 @@ TEST(FormatVariadicTest, MultipleReplacements) {
269269
EXPECT_EQ(ReplacementType::Literal, Replacements[3].Type);
270270
EXPECT_EQ("-", Replacements[3].Spec);
271271

272-
// {2:bar,-3} - Options=bar, Align=-3
272+
// {2,-3:bar} - Options=bar, Align=-3
273273
EXPECT_EQ(ReplacementType::Format, Replacements[4].Type);
274274
EXPECT_EQ(2u, Replacements[4].Index);
275275
EXPECT_EQ(3u, Replacements[4].Width);
276276
EXPECT_EQ(AlignStyle::Left, Replacements[4].Where);
277277
EXPECT_EQ("bar", Replacements[4].Options);
278278
}
279279

280+
TEST(FormatVariadicTest, AutomaticIndices) {
281+
auto Replacements = parseFormatString("{}");
282+
ASSERT_EQ(1u, Replacements.size());
283+
EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
284+
EXPECT_EQ(0u, Replacements[0].Index);
285+
286+
Replacements = parseFormatString("{}{}");
287+
ASSERT_EQ(2u, Replacements.size());
288+
EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
289+
EXPECT_EQ(0u, Replacements[0].Index);
290+
EXPECT_EQ(ReplacementType::Format, Replacements[1].Type);
291+
EXPECT_EQ(1u, Replacements[1].Index);
292+
293+
Replacements = parseFormatString("{}{:foo}{,-3:bar}");
294+
ASSERT_EQ(3u, Replacements.size());
295+
EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
296+
EXPECT_EQ(0u, Replacements[0].Index);
297+
EXPECT_EQ(0u, Replacements[0].Width);
298+
EXPECT_EQ(AlignStyle::Right, Replacements[0].Where);
299+
EXPECT_EQ("", Replacements[0].Options);
300+
301+
// {:foo} - Options=foo
302+
EXPECT_EQ(ReplacementType::Format, Replacements[1].Type);
303+
EXPECT_EQ(1u, Replacements[1].Index);
304+
EXPECT_EQ(0u, Replacements[1].Width);
305+
EXPECT_EQ(AlignStyle::Right, Replacements[1].Where);
306+
EXPECT_EQ("foo", Replacements[1].Options);
307+
308+
// {,-3:bar} - Options=bar, Align=-3
309+
EXPECT_EQ(ReplacementType::Format, Replacements[2].Type);
310+
EXPECT_EQ(2u, Replacements[2].Index);
311+
EXPECT_EQ(3u, Replacements[2].Width);
312+
EXPECT_EQ(AlignStyle::Left, Replacements[2].Where);
313+
EXPECT_EQ("bar", Replacements[2].Options);
314+
}
315+
280316
TEST(FormatVariadicTest, FormatNoReplacements) {
281317
EXPECT_EQ("", formatv("").str());
282318
EXPECT_EQ("Test", formatv("Test").str());
@@ -291,6 +327,12 @@ TEST(FormatVariadicTest, FormatBasicTypesOneReplacement) {
291327
EXPECT_EQ("Test3", formatv("{0}", std::string("Test3")).str());
292328
}
293329

330+
TEST(FormatVariadicTest, FormatAutomaticIndices) {
331+
EXPECT_EQ("1", formatv("{}", 1).str());
332+
EXPECT_EQ("c1", formatv("{}{}", 'c', 1).str());
333+
EXPECT_EQ("c-1rrr-0xFF", formatv("{}-{,r-4}-{:X}", 'c', 1, 255).str());
334+
}
335+
294336
TEST(FormatVariadicTest, IntegralHexFormatting) {
295337
// 1. Trivial cases. Make sure hex is not the default.
296338
EXPECT_EQ("0", formatv("{0}", 0).str());
@@ -717,13 +759,16 @@ TEST(FormatVariadicTest, Validate) {
717759
EXPECT_DEATH(formatv("{0}", 1, 2).str(), "Expected 1 Args, but got 2");
718760
EXPECT_DEATH(formatv("{0} {2}", 1, 2, 3).str(),
719761
"Replacement field indices cannot have holes");
762+
EXPECT_DEATH(formatv("{}{1}", 0, 1).str(),
763+
"Cannot mix automatic and explicit indices");
720764
#else // GTEST_HAS_DEATH_TEST
721765
GTEST_SKIP() << "No support for EXPECT_DEATH";
722766
#endif // GTEST_HAS_DEATH_TEST
723767
#else // NDEBUG
724768
// If asserts are disabled, verify that validation is disabled.
725769
EXPECT_EQ(formatv("{0}", 1, 2).str(), "1");
726770
EXPECT_EQ(formatv("{0} {2}", 1, 2, 3).str(), "1 3");
771+
EXPECT_EQ(formatv("{}{1}", 0, 1).str(), "01");
727772
#endif // NDEBUG
728773
}
729774

0 commit comments

Comments
 (0)