From 37e64747189aa75082630ac01404666d7186ba80 Mon Sep 17 00:00:00 2001 From: Dean Glazeski Date: Sat, 15 Mar 2025 09:34:11 -0700 Subject: [PATCH] Fix dynamic named arg format spec handling (#4361) When dealing with dynamic named format args, need to account for nested named args when skipping the content of the replacement. Fixes #4360 --- include/fmt/base.h | 14 +++++++++++++- test/format-test.cc | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index fe73e377..3b030f84 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -1718,7 +1718,19 @@ class format_string_checker { -> const Char* { context_.advance_to(begin); if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_); - while (begin != end && *begin != '}') ++begin; + + // If id is out of range, it means we do not know the type and cannot parse + // the format at compile time. Instead, skip over content until we finish + // the format spec, accounting for any nested replacements. + auto bracket_count = 0; + while (begin != end && (bracket_count > 0 || *begin != '}')) { + if (*begin == '{') + ++bracket_count; + else if (*begin == '}') + --bracket_count; + + ++begin; + } return begin; } diff --git a/test/format-test.cc b/test/format-test.cc index f83d1626..0bac7ee4 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -582,6 +582,8 @@ TEST(format_test, named_arg) { EXPECT_EQ("1/a/A", fmt::format("{_1}/{a_}/{A_}", fmt::arg("a_", 'a'), fmt::arg("A_", "A"), fmt::arg("_1", 1))); EXPECT_EQ(fmt::format("{0:{width}}", -42, fmt::arg("width", 4)), " -42"); + EXPECT_EQ(fmt::format("{value:{width}}", fmt::arg("value", -42), + fmt::arg("width", 4)), " -42"); EXPECT_EQ("st", fmt::format("{0:.{precision}}", "str", fmt::arg("precision", 2))); EXPECT_EQ(fmt::format("{} {two}", 1, fmt::arg("two", 2)), "1 2");