From 9bff2a9302a8dbf91712fc215eb2e2cf8ec234e7 Mon Sep 17 00:00:00 2001 From: Phoebe Liang Date: Wed, 7 Dec 2022 12:49:08 -0800 Subject: Fixes issue where AbslStringify() breaks formatting with %d for enums PiperOrigin-RevId: 493682437 Change-Id: I30f2ac36b998b86c24fe7513cd952b860560a66e --- absl/strings/internal/str_format/arg.cc | 92 ++++++++++++++++++++++----------- absl/strings/internal/str_format/arg.h | 88 +++++++++++++++++++++++++------ absl/strings/str_format_test.cc | 37 ++++++++++++- 3 files changed, 170 insertions(+), 47 deletions(-) (limited to 'absl') diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc index 967fe9ca..018dd052 100644 --- a/absl/strings/internal/str_format/arg.cc +++ b/absl/strings/internal/str_format/arg.cc @@ -296,6 +296,37 @@ constexpr auto ConvertV(T) { return FormatConversionCharInternal::u; } +template +bool ConvertFloatArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { + if (conv.conversion_char() == FormatConversionCharInternal::v) { + conv.set_conversion_char(FormatConversionCharInternal::g); + } + + return FormatConversionCharIsFloat(conv.conversion_char()) && + ConvertFloatImpl(v, conv, sink); +} + +inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { + if (conv.is_basic()) { + sink->Append(v); + return true; + } + return sink->PutPaddedString(v, conv.width(), conv.precision(), + conv.has_left_flag()); +} + +} // namespace + +bool ConvertBoolArg(bool v, FormatSinkImpl *sink) { + if (v) { + sink->Append("true"); + } else { + sink->Append("false"); + } + return true; +} + template bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { using U = typename MakeUnsigned::type; @@ -354,36 +385,37 @@ bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return ConvertIntImplInnerSlow(as_digits, conv, sink); } -template -bool ConvertFloatArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { - if (conv.conversion_char() == FormatConversionCharInternal::v) { - conv.set_conversion_char(FormatConversionCharInternal::g); - } - - return FormatConversionCharIsFloat(conv.conversion_char()) && - ConvertFloatImpl(v, conv, sink); -} - -inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { - if (conv.is_basic()) { - sink->Append(v); - return true; - } - return sink->PutPaddedString(v, conv.width(), conv.precision(), - conv.has_left_flag()); -} - -} // namespace - -bool ConvertBoolArg(bool v, FormatSinkImpl *sink) { - if (v) { - sink->Append("true"); - } else { - sink->Append("false"); - } - return true; -} +template bool ConvertIntArg(char v, FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(signed char v, + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(unsigned char v, + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(short v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(unsigned short v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(int v, FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(unsigned int v, + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(unsigned long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(long long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(unsigned long long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); // ==================== Strings ==================== StringConvertResult FormatConvertImpl(const std::string &v, diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h index bc4cde96..e4b16628 100644 --- a/absl/strings/internal/str_format/arg.h +++ b/absl/strings/internal/str_format/arg.h @@ -53,6 +53,19 @@ struct ArgConvertResult { bool value; }; +using IntegralConvertResult = ArgConvertResult; +using FloatingConvertResult = ArgConvertResult; +using CharConvertResult = ArgConvertResult; + template struct HasUserDefinedConvert : std::false_type {}; @@ -69,6 +82,44 @@ struct HasUserDefinedConvert +bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); + +// Forward declarations of internal `ConvertIntArg` function template +// instantiations are here to avoid including the template body in the headers +// and instantiating it in large numbers of translation units. Explicit +// instantiations can be found in "absl/strings/internal/str_format/arg.cc" +extern template bool ConvertIntArg(char v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg(signed char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg(unsigned char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg(short v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg( // NOLINT + unsigned short v, FormatConversionSpecImpl conv, // NOLINT + FormatSinkImpl* sink); +extern template bool ConvertIntArg(int v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg(unsigned int v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg( // NOLINT + long v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); // NOLINT +extern template bool ConvertIntArg(unsigned long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg(long long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg( // NOLINT + unsigned long long v, FormatConversionSpecImpl conv, // NOLINT + FormatSinkImpl* sink); + template auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, FormatSinkImpl* sink) @@ -84,11 +135,31 @@ auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, return AbslFormatConvert(v, fcs, &fs); } +template +auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink) + -> std::enable_if_t::value && + std::is_void(), v))>::value, + IntegralConvertResult> { + if (conv.conversion_char() == FormatConversionCharInternal::v) { + using FormatSinkT = + absl::enable_if_t; + auto fs = sink->Wrap(); + AbslStringify(fs, v); + return {true}; + } else { + return {ConvertIntArg( + static_cast::type>(v), conv, sink)}; + } +} + template auto FormatConvertImpl(const T& v, FormatConversionSpecImpl, FormatSinkImpl* sink) - -> std::enable_if_t(), v))>::value, + -> std::enable_if_t::value && + std::is_void(), v))>::value, ArgConvertResult> { using FormatSinkT = absl::enable_if_t; @@ -194,19 +265,6 @@ StringConvertResult FormatConvertImpl(const AbslCord& value, return {true}; } -using IntegralConvertResult = ArgConvertResult; -using FloatingConvertResult = ArgConvertResult; -using CharConvertResult = ArgConvertResult; - bool ConvertBoolArg(bool v, FormatSinkImpl* sink); // Floats. diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 36def1e0..5198fb33 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -1142,18 +1142,51 @@ TEST_F(FormatExtensionTest, AbslStringifyExampleUsingFormat) { EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z"); } -enum class EnumWithStringify { Many = 0, Choices = 1 }; +enum class EnumClassWithStringify { Many = 0, Choices = 1 }; + +template +void AbslStringify(Sink& sink, EnumClassWithStringify e) { + absl::Format(&sink, "%s", + e == EnumClassWithStringify::Many ? "Many" : "Choices"); +} + +enum EnumWithStringify { Many, Choices }; template void AbslStringify(Sink& sink, EnumWithStringify e) { absl::Format(&sink, "%s", e == EnumWithStringify::Many ? "Many" : "Choices"); } -TEST_F(FormatExtensionTest, AbslStringifyWithEnum) { +TEST_F(FormatExtensionTest, AbslStringifyWithEnumWithV) { + const auto e_class = EnumClassWithStringify::Choices; + EXPECT_EQ(absl::StrFormat("My choice is %v", e_class), + "My choice is Choices"); + const auto e = EnumWithStringify::Choices; EXPECT_EQ(absl::StrFormat("My choice is %v", e), "My choice is Choices"); } +TEST_F(FormatExtensionTest, AbslStringifyEnumWithD) { + const auto e_class = EnumClassWithStringify::Many; + EXPECT_EQ(absl::StrFormat("My choice is %d", e_class), "My choice is 0"); + + const auto e = EnumWithStringify::Choices; + EXPECT_EQ(absl::StrFormat("My choice is %d", e), "My choice is 1"); +} + +enum class EnumWithLargerValue { x = 32 }; + +template +void AbslStringify(Sink& sink, EnumWithLargerValue e) { + absl::Format(&sink, "%s", "Many"); +} + +TEST_F(FormatExtensionTest, AbslStringifyEnumOtherSpecifiers) { + const auto e = EnumWithLargerValue::x; + EXPECT_EQ(absl::StrFormat("My choice is %g", e), "My choice is 32"); + EXPECT_EQ(absl::StrFormat("My choice is %x", e), "My choice is 20"); +} + } // namespace // Some codegen thunks that we can use to easily dump the generated assembly for -- cgit v1.2.3