From f7404cd33f2457bd561fdcfbb024e43852c94bcf Mon Sep 17 00:00:00 2001
From: Abseil Team <absl-team@google.com>
Date: Wed, 28 Sep 2022 11:33:29 -0700
Subject: [PATCH] Allows absl::StrFormat to accept types which implement
 AbslStringify()

PiperOrigin-RevId: 477507777
Change-Id: I5ecde3163ca560ac8774034e55654774e36ad230
---
 absl/strings/internal/str_format/arg.h | 34 ++++++++++++++++++--------
 absl/strings/str_format_test.cc        | 19 +++++++++++++-
 2 files changed, 42 insertions(+), 11 deletions(-)

diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h
index 2dfbf728..3d616dba 100644
--- a/absl/strings/internal/str_format/arg.h
+++ b/absl/strings/internal/str_format/arg.h
@@ -45,6 +45,11 @@ class FormatConversionSpec;
 
 namespace str_format_internal {
 
+template <FormatConversionCharSet C>
+struct ArgConvertResult {
+  bool value;
+};
+
 template <typename T, typename = void>
 struct HasUserDefinedConvert : std::false_type {};
 
@@ -71,6 +76,20 @@ auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv,
   return AbslFormatConvert(v, fcs, &fs);
 }
 
+void AbslStringify();  // Stops the lexical name lookup
+template <typename T>
+auto FormatConvertImpl(const T& v, FormatConversionSpecImpl,
+                       FormatSinkImpl* sink)
+    -> std::enable_if_t<std::is_void<decltype(AbslStringify(
+                            std::declval<FormatSink&>(), v))>::value,
+                        ArgConvertResult<FormatConversionCharSetInternal::v>> {
+  using FormatSinkT =
+      absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatSink>;
+  auto fs = sink->Wrap<FormatSinkT>();
+  AbslStringify(fs, v);
+  return {true};
+}
+
 template <typename T>
 class StreamedWrapper;
 
@@ -95,11 +114,6 @@ struct VoidPtr {
   uintptr_t value;
 };
 
-template <FormatConversionCharSet C>
-struct ArgConvertResult {
-  bool value;
-};
-
 template <FormatConversionCharSet C>
 constexpr FormatConversionCharSet ExtractCharSet(FormatConvertResult<C>) {
   return C;
@@ -316,11 +330,11 @@ struct FormatArgImplFriend {
 
 template <typename Arg>
 constexpr FormatConversionCharSet ArgumentToConv() {
-  return absl::str_format_internal::ExtractCharSet(
-      decltype(str_format_internal::FormatConvertImpl(
-          std::declval<const Arg&>(),
-          std::declval<const FormatConversionSpecImpl&>(),
-          std::declval<FormatSinkImpl*>())){});
+  using ConvResult = decltype(str_format_internal::FormatConvertImpl(
+      std::declval<const Arg&>(),
+      std::declval<const FormatConversionSpecImpl&>(),
+      std::declval<FormatSinkImpl*>()));
+  return absl::str_format_internal::ExtractCharSet(ConvResult{});
 }
 
 // A type-erased handle to a format argument.
diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc
index 4b778056..0c4f10c8 100644
--- a/absl/strings/str_format_test.cc
+++ b/absl/strings/str_format_test.cc
@@ -1074,7 +1074,8 @@ using FormatExtensionTest = ::testing::Test;
 
 struct Point {
   friend absl::FormatConvertResult<absl::FormatConversionCharSet::kString |
-                                   absl::FormatConversionCharSet::kIntegral>
+                                   absl::FormatConversionCharSet::kIntegral |
+                                   absl::FormatConversionCharSet::v>
   AbslFormatConvert(const Point& p, const absl::FormatConversionSpec& spec,
                     absl::FormatSink* s) {
     if (spec.conversion_char() == absl::FormatConversionChar::s) {
@@ -1093,6 +1094,7 @@ TEST_F(FormatExtensionTest, AbslFormatConvertExample) {
   Point p;
   EXPECT_EQ(absl::StrFormat("a %s z", p), "a x=10 y=20 z");
   EXPECT_EQ(absl::StrFormat("a %d z", p), "a 10,20 z");
+  EXPECT_EQ(absl::StrFormat("a %v z", p), "a 10,20 z");
 
   // Typed formatting will fail to compile an invalid format.
   // StrFormat("%f", p);  // Does not compile.
@@ -1101,6 +1103,21 @@ TEST_F(FormatExtensionTest, AbslFormatConvertExample) {
   // FormatUntyped will return false for bad character.
   EXPECT_FALSE(absl::FormatUntyped(&actual, f1, {absl::FormatArg(p)}));
 }
+
+struct PointStringify {
+  template <typename FormatSink>
+  friend void AbslStringify(FormatSink& sink, const PointStringify& p) {
+    sink.Append(absl::StrCat("(", p.x, ", ", p.y, ")"));
+  }
+
+  double x = 10.0;
+  double y = 20.0;
+};
+
+TEST_F(FormatExtensionTest, AbslStringifyExample) {
+  PointStringify p;
+  EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z");
+}
 }  // namespace
 
 // Some codegen thunks that we can use to easily dump the generated assembly for