absl/strings: Prepare helper for printing objects to string representations.

PiperOrigin-RevId: 847935770
Change-Id: I7f96940e5ba11d6a602d34e7dc3dbfde112bb142
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake
index b8b137c..cd633a1 100644
--- a/CMake/AbseilDll.cmake
+++ b/CMake/AbseilDll.cmake
@@ -331,6 +331,9 @@
   "strings/internal/cordz_update_tracker.h"
   "strings/internal/damerau_levenshtein_distance.h"
   "strings/internal/damerau_levenshtein_distance.cc"
+  "strings/internal/generic_printer.cc"
+  "strings/internal/generic_printer.h"
+  "strings/internal/generic_printer_internal.h"
   "strings/internal/stl_type_traits.h"
   "strings/internal/string_constant.h"
   "strings/internal/stringify_sink.h"
diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel
index c5bdd67..32ae277 100644
--- a/absl/log/internal/BUILD.bazel
+++ b/absl/log/internal/BUILD.bazel
@@ -554,6 +554,9 @@
     hdrs = ["container.h"],
     copts = ABSL_DEFAULT_COPTS,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    visibility = [
+        "//absl:__subpackages__",
+    ],
     deps = [
         "//absl/base:config",
         "//absl/meta:requires",
diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel
index 49051bd..a1e5021 100644
--- a/absl/strings/BUILD.bazel
+++ b/absl/strings/BUILD.bazel
@@ -1551,3 +1551,43 @@
         "@googletest//:gtest_main",
     ],
 )
+
+cc_library(
+    name = "generic_printer",
+    srcs = [
+        "internal/generic_printer.cc",
+        "internal/generic_printer_internal.h",
+    ],
+    hdrs = ["internal/generic_printer.h"],
+    copts = ABSL_DEFAULT_COPTS,
+    linkopts = ABSL_DEFAULT_LINKOPTS,
+    visibility = [
+        "//absl:__subpackages__",
+    ],
+    deps = [
+        ":str_format",
+        ":strings",
+        "//absl/base:config",
+        "//absl/log/internal:container",
+        "//absl/meta:requires",
+    ],
+)
+
+cc_test(
+    name = "generic_printer_test",
+    srcs = ["internal/generic_printer_test.cc"],
+    copts = ABSL_TEST_COPTS,
+    linkopts = ABSL_DEFAULT_LINKOPTS,
+    deps = [
+        ":generic_printer",
+        ":strings",
+        "//absl/base:config",
+        "//absl/base:core_headers",
+        "//absl/container:flat_hash_map",
+        "//absl/log",
+        "//absl/status",
+        "//absl/status:statusor",
+        "@googletest//:gtest",
+        "@googletest//:gtest_main",
+    ],
+)
diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt
index 94e58ea..a03943d 100644
--- a/absl/strings/CMakeLists.txt
+++ b/absl/strings/CMakeLists.txt
@@ -1256,3 +1256,40 @@
     absl::strings
     GTest::gmock_main
 )
+
+absl_cc_library(
+  NAME
+    generic_printer_internal
+  SRCS
+    "internal/generic_printer.cc"
+    "internal/generic_printer_internal.h"
+  HDRS
+    "internal/generic_printer.h"
+  COPTS
+    ${ABSL_DEFAULT_COPTS}
+  DEPS
+    absl::config
+    absl::strings
+    absl::str_format
+    absl::log_internal_container
+    absl::requires_internal
+)
+
+absl_cc_test(
+  NAME
+    generic_printer_test
+  SRCS
+    "internal/generic_printer_test.cc"
+  COPTS
+    ${ABSL_TEST_COPTS}
+  DEPS
+    absl::base
+    absl::config
+    absl::flat_hash_map
+    absl::generic_printer_internal
+    absl::log
+    absl::status
+    absl::statusor
+    absl::strings
+    GTest::gmock_main
+)
diff --git a/absl/strings/internal/generic_printer.cc b/absl/strings/internal/generic_printer.cc
new file mode 100644
index 0000000..16ca228
--- /dev/null
+++ b/absl/strings/internal/generic_printer.cc
@@ -0,0 +1,107 @@
+// Copyright 2025 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/internal/generic_printer.h"
+
+#include <cstddef>
+#include <cstdlib>
+#include <ostream>
+#include <string>
+
+#include "absl/base/config.h"
+#include "absl/strings/ascii.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_format.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace internal_generic_printer {
+
+// Out-of-line helper for PrintAsStringWithEscaping.
+std::ostream& PrintEscapedString(std::ostream& os, absl::string_view v) {
+  return os << "\"" << absl::CHexEscape(v) << "\"";
+}
+
+// Retuns a string representation of 'v', shortened if possible.
+template <class T, class F>
+std::string TryShorten(T v, F strtox) {
+  std::string printed =
+      absl::StrFormat("%.*g", std::numeric_limits<T>::max_digits10 / 2, v);
+  T parsed = strtox(printed.data());
+  if (parsed != v) {
+    printed =
+        absl::StrFormat("%.*g", std::numeric_limits<T>::max_digits10 + 1, v);
+  }
+  return printed;
+}
+
+// Out-of-line helpers for floating point values. These don't necessarily
+// ensure that values are precise, but rather that they are wide enough to
+// represent distinct values. go/c++17std/numeric.limits.members.html
+std::ostream& PrintPreciseFP(std::ostream& os, float v) {
+  return os << TryShorten(v, [](const char* buf) {
+           char* unused;
+           return std::strtof(buf, &unused);
+         }) << "f";
+}
+std::ostream& PrintPreciseFP(std::ostream& os, double v) {
+  return os << TryShorten(v, [](const char* buf) {
+           char* unused;
+           return std::strtod(buf, &unused);
+         });
+}
+std::ostream& PrintPreciseFP(std::ostream& os, long double v) {
+  return os << TryShorten(v, [](const char* buf) {
+           char* unused;
+           return std::strtold(buf, &unused);
+         }) << "L";
+}
+
+// Prints a nibble of 'v' in hexadecimal.
+inline char hexnib(int v) {
+  return static_cast<char>((v < 10 ? '0' : ('a' - 10)) + v);
+}
+
+template <typename T>
+static std::ostream& PrintCharImpl(std::ostream& os, T v) {
+  // Specialization for chars: print as 'c' if printable, otherwise
+  // hex-escaped.
+  return (absl::ascii_isprint(static_cast<unsigned char>(v))
+              ? (os << (v == '\'' ? "'\\" : "'") << v)
+              : (os << "'\\x" << hexnib((v >> 4) & 0xf) << hexnib(v & 0xf)))
+         << "' (0x" << hexnib((v >> 4) & 0xf) << hexnib(v & 0xf) << " "
+         << static_cast<int>(v) << ")";
+}
+
+std::ostream& PrintChar(std::ostream& os, char c) {
+  return PrintCharImpl(os, c);
+}
+
+std::ostream& PrintChar(std::ostream& os, signed char c) {
+  return PrintCharImpl(os, c);
+}
+
+std::ostream& PrintChar(std::ostream& os, unsigned char c) {
+  return PrintCharImpl(os, c);
+}
+
+std::ostream& PrintByte(std::ostream& os, std::byte b) {
+  auto v = std::to_integer<int>(b);
+  os << "0x" << hexnib((v >> 4) & 0xf) << hexnib(v & 0xf);
+  return os;
+}
+
+}  // namespace internal_generic_printer
+ABSL_NAMESPACE_END
+}  // namespace absl
diff --git a/absl/strings/internal/generic_printer.h b/absl/strings/internal/generic_printer.h
new file mode 100644
index 0000000..ed60155
--- /dev/null
+++ b/absl/strings/internal/generic_printer.h
@@ -0,0 +1,115 @@
+// Copyright 2025 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_H_
+#define ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_H_
+
+#include "absl/strings/internal/generic_printer_internal.h"  // IWYU pragma: export
+
+// Helpers for printing objects in generic code.
+//
+// These functions help with streaming in generic code. It may be desirable, for
+// example, to log values from generic functions; however, operator<< may not be
+// overloaded for all types.
+//
+// The helpers in this library use, in order of precedence:
+//
+//  1. std::string and std::string_view are quoted and escaped. (The specific
+//     format is not guaranteed to be stable.)
+//  2. A defined AbslStringify() method.
+//  3. absl::log_internal::LogContainer, if the object is a STL container.
+//  4. For std::tuple, std::pair, and std::optional, recursively calls
+//     GenericPrint for each element.
+//  5. Floating point values are printed with enough precision for round-trip.
+//  6. char values are quoted, with non-printable values escaped, and the
+//     char's numeric value is included.
+//  7. A defined operator<< overload (which should be found by ADL).
+//  8. A defined DebugString() const method.
+//  9. A fallback value with basic information otherwise.
+//
+// Note that the fallback value means that if no formatting conversion is
+// defined, you will not see a compile-time error. This also means that
+// GenericPrint() can safely be used in generic template contexts, and can
+// format any types needed (even though the output will vary).
+//
+// Example usage:
+//
+//   // All values after GenericPrint() are formatted:
+//   LOG(INFO) << GenericPrint()
+//             << int_var         // <- printed normally
+//             << float_var       // <- sufficient precision for round-trip
+//             << " unchanged literal text ";
+//
+//   // Just one value is formatted:
+//   LOG(INFO) << GenericPrint(string("this is quoted and escaped\t\n"))
+//             << GenericPrint("but not this, ");
+//             << string("and not this.");
+//
+// To make a type loggable with GenericPrint, prefer defining operator<< as a
+// friend function. For example:
+//
+//   class TypeToLog {
+//    public:
+//     string ToString() const;  // Many types already implement this.
+//                               // Define out-of-line if it is complex.
+//     friend std::ostream& operator<<(std::ostream& os, const TypeToLog& v) {
+//       return os << v.ToString();  // OK to define in-line instead, if simple.
+//     }
+//   };
+//
+// (Defining operator<< as an inline friend free function allows it to be found
+// by Argument-Dependent Lookup, or ADL, which is the mechanism typically used
+// for operator overload resolution. An inline friend function is the tightest
+// scope possible for overloading the left-hand side of an operator.)
+
+#include <ostream>
+#include <utility>
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace strings_internal {
+
+// Helper for logging values in generic code.
+//
+// Example usage:
+//
+//   template <typename T>
+//   void GenericFunction() {
+//     T v1, v2;
+//     VLOG(1) << GenericPrint() << v1 << v2;  // GenericPrint everything
+//     VLOG(1) << GenericPrint(v1) << v2;  // GenericPrint just v1
+//   }
+//
+inline constexpr internal_generic_printer::GenericPrintAdapterFactory
+    GenericPrint{};
+
+// Generic printer type: this class can be used, for example, as a template
+// argument to allow users to provide alternative printing strategies.
+//
+// For example, to allow callers to provide a custom strategy:
+//
+//   template <typename T, typename PrinterT = GenericPrinter<T>>
+//   void GenericFunction() {
+//     T value;
+//     VLOG(1) << PrinterT{value};
+//   }
+//
+template <typename T>
+using GenericPrinter = internal_generic_printer::GenericPrinter<T>;
+
+}  // namespace strings_internal
+ABSL_NAMESPACE_END
+}  // namespace absl
+
+#endif  // ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_H_
diff --git a/absl/strings/internal/generic_printer_internal.h b/absl/strings/internal/generic_printer_internal.h
new file mode 100644
index 0000000..1a2d0bd
--- /dev/null
+++ b/absl/strings/internal/generic_printer_internal.h
@@ -0,0 +1,423 @@
+// Copyright 2025 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_INTERNAL_H_
+#define ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_INTERNAL_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <ostream>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include "absl/base/config.h"
+#include "absl/log/internal/container.h"
+#include "absl/meta/internal/requires.h"
+#include "absl/strings/has_absl_stringify.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+
+// NOTE: we do not want to expand the dependencies of this library. All
+// compatibility detection must be done in a generic way, without having to
+// include the headers of other libraries.
+
+// Internal implementation details: see generic_printer.h.
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace internal_generic_printer {
+
+template <typename T>
+std::ostream& GenericPrintImpl(std::ostream& os, const T& v);
+
+// Scope blocker: we must always use ADL in our predicates below.
+struct Anonymous;
+std::ostream& operator<<(const Anonymous&, const Anonymous&) = delete;
+
+// Logging policy for LogContainer. Our SFINAE overload will not fail
+// if the contained type cannot be printed, so make sure to circle back to
+// GenericPrinter.
+struct ContainerLogPolicy {
+  void LogOpening(std::ostream& os) const { os << "["; }
+  void LogClosing(std::ostream& os) const { os << "]"; }
+  void LogFirstSeparator(std::ostream& os) const { os << ""; }
+  void LogSeparator(std::ostream& os) const { os << ", "; }
+  int64_t MaxElements() const { return 15; }
+  template <typename T>
+  void Log(std::ostream& os, const T& element) const {
+    internal_generic_printer::GenericPrintImpl(os, element);
+  }
+  void LogEllipsis(std::ostream& os) const { os << "..."; }
+};
+
+// Out-of-line helper for PrintAsStringWithEscaping.
+std::ostream& PrintEscapedString(std::ostream& os, absl::string_view v);
+
+// Trait to recognize a string, possibly with a custom allocator.
+template <class T>
+inline constexpr bool is_any_string = false;
+template <class A>
+inline constexpr bool
+    is_any_string<std::basic_string<char, std::char_traits<char>, A>> = true;
+
+// Trait to recognize a supported pointer. Below are documented reasons why
+// raw pointers and std::shared_ptr are not currently supported.
+// See also http://b/239459272#comment9 and the discussion in the comment
+// threads of cl/485200996.
+//
+// The tl;dr:
+// 1. Pointers that logically represent an object (or nullptr) are safe to print
+//    out.
+// 2. Pointers that may represent some other concept (delineating memory bounds,
+//    overridden managed-memory deleters to mimic RAII, ...) may be unsafe to
+//    print out.
+//
+// raw pointers:
+//  - A pointer one-past the last element of an array
+//  - Non-null but non-dereferenceable: https://gcc.godbolt.org/z/sqsqGvKbP
+//
+// `std::shared_ptr`:
+//  - "Aliasing" / similar to raw pointers: https://gcc.godbolt.org/z/YbWPzvhae
+template <class T, class = void>
+inline constexpr bool is_supported_ptr = false;
+// `std::unique_ptr` has the same theoretical risks as raw pointers, but those
+// risks are far less likely (typically requiring a custom deleter), and the
+// benefits of supporting unique_ptr outweigh the costs.
+//  - Note: `std::unique_ptr<T[]>` cannot safely be and is not dereferenced.
+template <class A, class... Deleter>
+inline constexpr bool is_supported_ptr<std::unique_ptr<A, Deleter...>> = true;
+// `ArenaSafeUniquePtr` is at least as safe as `std::unique_ptr`.
+template <class T>
+inline constexpr bool is_supported_ptr<
+    T,
+    // Check for `ArenaSafeUniquePtr` without having to include its header here.
+    // This does match any type named `ArenaSafeUniquePtr`, regardless of the
+    // namespace it is defined in, but it's pretty plausible that any such type
+    // would be a fork.
+    decltype(T().~ArenaSafeUniquePtr())> = true;
+
+// Specialization for floats: print floating point types using their
+// max_digits10 precision. This ensures each distinct underlying values
+// can be represented uniquely, even though it's not (strictly speaking)
+// the most precise representation.
+std::ostream& PrintPreciseFP(std::ostream& os, float v);
+std::ostream& PrintPreciseFP(std::ostream& os, double v);
+std::ostream& PrintPreciseFP(std::ostream& os, long double v);
+
+std::ostream& PrintChar(std::ostream& os, char c);
+std::ostream& PrintChar(std::ostream& os, signed char c);
+std::ostream& PrintChar(std::ostream& os, unsigned char c);
+
+std::ostream& PrintByte(std::ostream& os, std::byte b);
+
+template <class... Ts>
+std::ostream& PrintTuple(std::ostream& os, const std::tuple<Ts...>& tuple) {
+  absl::string_view sep = "";
+  const auto print_one = [&](const auto& v) {
+    os << sep;
+    (GenericPrintImpl)(os, v);
+    sep = ", ";
+  };
+  os << "<";
+  std::apply([&](const auto&... v) { (print_one(v), ...); }, tuple);
+  os << ">";
+  return os;
+}
+
+template <typename T, typename U>
+std::ostream& PrintPair(std::ostream& os, const std::pair<T, U>& p) {
+  os << "<";
+  (GenericPrintImpl)(os, p.first);
+  os << ", ";
+  (GenericPrintImpl)(os, p.second);
+  os << ">";
+  return os;
+}
+
+template <typename T>
+std::ostream& PrintOptionalLike(std::ostream& os, const T& v) {
+  if (v.has_value()) {
+    os << "<";
+    (GenericPrintImpl)(os, *v);
+    os << ">";
+  } else {
+    (GenericPrintImpl)(os, std::nullopt);
+  }
+  return os;
+}
+
+template <typename... Ts>
+std::ostream& PrintVariant(std::ostream& os, const std::variant<Ts...>& v) {
+  os << "(";
+  os << "'(index = " << v.index() << ")' ";
+
+  // NOTE(derekbailey): This may throw a std::bad_variant_access if the variant
+  // is "valueless", which only occurs if exceptions are thrown. This is
+  // non-relevant when not using exceptions, but it is worth mentioning if that
+  // invariant is ever changed.
+  std::visit([&](const auto& arg) { (GenericPrintImpl)(os, arg); }, v);
+  os << ")";
+  return os;
+}
+
+template <typename StatusOrLike>
+std::ostream& PrintStatusOrLike(std::ostream& os, const StatusOrLike& v) {
+  os << "<";
+  if (v.ok()) {
+    os << "OK: ";
+    (GenericPrintImpl)(os, *v);
+  } else {
+    (GenericPrintImpl)(os, v.status());
+  }
+  os << ">";
+  return os;
+}
+
+template <typename SmartPointer>
+std::ostream& PrintSmartPointerContents(std::ostream& os,
+                                        const SmartPointer& v) {
+  os << "<";
+  if (v == nullptr) {
+    (GenericPrintImpl)(os, nullptr);
+  } else {
+    // Cast to void* so that every type (e.g. `char*`) is printed as an address.
+    os << absl::implicit_cast<const void*>(v.get()) << " pointing to ";
+
+    if constexpr (meta_internal::Requires<SmartPointer>(
+                      [](auto&& p) -> decltype(p[0]) {})) {
+      // e.g. std::unique_ptr<int[]>, which only has operator[]
+      os << "an array";
+    } else if constexpr (std::is_object_v<
+                             typename SmartPointer::element_type>) {
+      (GenericPrintImpl)(os, *v);
+    } else {
+      // e.g. std::unique_ptr<void, MyCustomDeleter>
+      os << "a non-object type";
+    }
+  }
+  os << ">";
+  return os;
+}
+
+template <typename T>
+std::ostream& GenericPrintImpl(std::ostream& os, const T& v) {
+  if constexpr (is_any_string<T> || std::is_same_v<T, absl::string_view>) {
+    // Specialization for strings: prints with plausible quoting and escaping.
+    return PrintEscapedString(os, v);
+  } else if constexpr (absl::HasAbslStringify<T>::value) {
+    // If someone has specified `AbslStringify`, we should prefer that.
+    return os << absl::StrCat(v);
+  } else if constexpr (meta_internal::Requires<const T>(
+                           [&](auto&& w)
+                               -> decltype((
+                                   PrintTuple)(std::declval<std::ostream&>(),
+                                               w)) {})) {
+    // For tuples, use `< elem0, ..., elemN >`.
+    return (PrintTuple)(os, v);
+
+  } else if constexpr (meta_internal::Requires<const T>(
+                           [&](auto&& w)
+                               -> decltype((
+                                   PrintPair)(std::declval<std::ostream&>(),
+                                              w)) {})) {
+    // For pairs, use `< first, second >`.
+    return (PrintPair)(os, v);
+
+  } else if constexpr (meta_internal::Requires<const T>(
+                           [&](auto&& w)
+                               -> decltype((
+                                   PrintVariant)(std::declval<std::ostream&>(),
+                                                 w)) {})) {
+    // For std::variant, use `std::visit(v)`
+    return (PrintVariant)(os, v);
+  } else if constexpr (is_supported_ptr<T>) {
+    return (PrintSmartPointerContents)(os, v);
+  } else if constexpr (meta_internal::Requires<const T>(
+                           [&](auto&& w) -> decltype(w.ok(), w.status(), *w) {
+                           })) {
+    return (PrintStatusOrLike)(os, v);
+  } else if constexpr (meta_internal::Requires<const T>(
+                           [&](auto&& w) -> decltype(w.has_value(), *w) {})) {
+    return (PrintOptionalLike)(os, v);
+  } else if constexpr (std::is_same_v<T, std::nullopt_t>) {
+    // Specialization for nullopt.
+    return os << "nullopt";
+
+  } else if constexpr (std::is_same_v<T, std::nullptr_t>) {
+    // Specialization for nullptr.
+    return os << "nullptr";
+
+  } else if constexpr (std::is_floating_point_v<T>) {
+    // For floating point print with enough precision for a roundtrip.
+    return PrintPreciseFP(os, v);
+
+  } else if constexpr (std::is_same_v<T, char> ||
+                       std::is_same_v<T, signed char> ||
+                       std::is_same_v<T, unsigned char>) {
+    // Chars are printed as the char (if a printable char) and the integral
+    // representation in hex and decimal.
+    return PrintChar(os, v);
+
+  } else if constexpr (std::is_same_v<T, std::byte>) {
+    return PrintByte(os, v);
+
+  } else if constexpr (std::is_same_v<T, bool> ||
+                       std::is_same_v<T,
+                                      typename std::vector<bool>::reference> ||
+                       std::is_same_v<
+                           T, typename std::vector<bool>::const_reference>) {
+    return os << (v ? "true" : "false");
+
+  } else if constexpr (meta_internal::Requires<const T>(
+                           [&](auto&& w)
+                               -> decltype(ProtobufInternalGetEnumDescriptor(
+                                   w)) {})) {
+    os << static_cast<std::underlying_type_t<T>>(v);
+    if (auto* desc =
+            ProtobufInternalGetEnumDescriptor(T{})->FindValueByNumber(v)) {
+      os << "(" << desc->name() << ")";
+    }
+    return os;
+  } else if constexpr (!std::is_enum_v<T> &&
+                       meta_internal::Requires<const T>(
+                           [&](auto&& w) -> decltype(absl::StrCat(w)) {})) {
+    return os << absl::StrCat(v);
+
+  } else if constexpr (meta_internal::Requires<const T>(
+                           [&](auto&& w)
+                               -> decltype(std::declval<std::ostream&>()
+                                           << log_internal::LogContainer(w)) {
+                           })) {
+    // For containers, use `[ elem0, ..., elemN ]` with a max of 15.
+    return os << log_internal::LogContainer(v, ContainerLogPolicy());
+
+  } else if constexpr (meta_internal::Requires<const T>(
+                           [&](auto&& w)
+                               -> decltype(std::declval<std::ostream&>() << w) {
+                           })) {
+    // Streaming
+    return os << v;
+
+  } else if constexpr (meta_internal::Requires<const T>(
+                           [&](auto&& w)
+                               -> decltype(std::declval<std::ostream&>()
+                                           << w.DebugString()) {})) {
+    // DebugString
+    return os << v.DebugString();
+
+  } else if constexpr (std::is_enum_v<T>) {
+    // In case the underlying type is some kind of char, we have to recurse.
+    return GenericPrintImpl(os, static_cast<std::underlying_type_t<T>>(v));
+
+  } else {
+    // Default: we don't have anything to stream the value.
+    return os << "[unprintable value of size " << sizeof(T) << " @" << &v
+              << "]";
+  }
+}
+
+// GenericPrinter always has a valid operator<<. It defers to the disjuction
+// above.
+template <typename T>
+class GenericPrinter {
+ public:
+  explicit GenericPrinter(const T& value) : value_(value) {}
+
+ private:
+  friend std::ostream& operator<<(std::ostream& os, const GenericPrinter& gp) {
+    return internal_generic_printer::GenericPrintImpl(os, gp.value_);
+  }
+  const T& value_;
+};
+
+struct GenericPrintStreamAdapter {
+  template <class StreamT>
+  struct Impl {
+    // Stream operator: this object will adapt to the underlying stream type,
+    // but only if the Impl is an rvalue. For example, this works:
+    //   std::cout << GenericPrint() << foo;
+    // but not:
+    //   auto adapter = (std::cout << GenericPrint());
+    //   adapter << foo;
+    template <typename T>
+    Impl&& operator<<(const T& value) && {
+      os << internal_generic_printer::GenericPrinter<T>(value);
+      return std::move(*this);
+    }
+
+    // Inhibit using a stack variable for the adapter:
+    template <typename T>
+    Impl& operator<<(const T& value) & = delete;
+
+    // Detects a Flush() method, for LogMessage compatibility.
+    template <typename T>
+    class HasFlushMethod {
+     private:
+      template <typename C>
+      static std::true_type Test(decltype(&C::Flush));
+      template <typename C>
+      static std::false_type Test(...);
+
+     public:
+      static constexpr bool value = decltype(Test<T>(nullptr))::value;
+    };
+
+    // LogMessage compatibility requires a Flush() method.
+    void Flush() {
+      if constexpr (HasFlushMethod<StreamT>::value) {
+        os.Flush();
+      }
+    }
+
+    StreamT& os;
+  };
+
+  // If Impl is evaluated on the RHS of an 'operator&&', and 'lhs && Impl.os'
+  // implicitly converts to void, then it's fine for Impl to do so, too. This
+  // will create precisely as many objects as 'lhs && Impl.os', so we should
+  // both observe any side effects, and avoid observing multiple side
+  // effects. (See absl::log_internal::Voidify for an example of why this might
+  // be useful.)
+  template <typename LHS, typename RHS>
+  friend auto operator&&(LHS&& lhs, Impl<RHS>&& rhs)
+      -> decltype(lhs && rhs.os) {
+    return lhs && rhs.os;
+  }
+
+  template <class StreamT>
+  friend Impl<StreamT> operator<<(StreamT& os, GenericPrintStreamAdapter&&) {
+    return Impl<StreamT>{os};
+  }
+};
+
+struct GenericPrintAdapterFactory {
+  internal_generic_printer::GenericPrintStreamAdapter operator()() const {
+    return internal_generic_printer::GenericPrintStreamAdapter{};
+  }
+  template <typename T>
+  internal_generic_printer::GenericPrinter<T> operator()(const T& value) const {
+    return internal_generic_printer::GenericPrinter<T>{value};
+  }
+};
+
+}  // namespace internal_generic_printer
+ABSL_NAMESPACE_END
+}  // namespace absl
+
+#endif  // ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_INTERNAL_H_
diff --git a/absl/strings/internal/generic_printer_test.cc b/absl/strings/internal/generic_printer_test.cc
new file mode 100644
index 0000000..4093228
--- /dev/null
+++ b/absl/strings/internal/generic_printer_test.cc
@@ -0,0 +1,685 @@
+// Copyright 2025 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/internal/generic_printer.h"
+
+#include <array>
+#include <cstdint>
+#include <limits>
+#include <map>
+#include <memory>
+#include <optional>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/substitute.h"
+
+namespace generic_logging_test {
+struct NotStreamable {};
+}  // namespace generic_logging_test
+
+static std::ostream& operator<<(std::ostream& os,
+                                const generic_logging_test::NotStreamable&) {
+  return os << "This overload should NOT be found by GenericPrint.";
+}
+
+// Types to test selection logic for streamable and non-streamable types.
+namespace generic_logging_test {
+struct Streamable {
+  int x;
+  friend std::ostream& operator<<(std::ostream& os, const Streamable& l) {
+    return os << "Streamable{" << l.x << "}";
+  }
+};
+}  // namespace generic_logging_test
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace strings_internal {
+namespace {
+
+using ::testing::AllOf;
+using ::testing::AnyOf;
+using ::testing::ContainsRegex;
+using ::testing::EndsWith;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::MatchesRegex;
+
+struct AbslStringifiable {
+  template <typename S>
+  friend void AbslStringify(S& sink, const AbslStringifiable&) {
+    sink.Append("AbslStringifiable!");
+  }
+};
+
+auto IsUnprintable() {
+#ifdef GTEST_USES_SIMPLE_RE
+  return HasSubstr("unprintable value of size");
+#else
+  return ContainsRegex(
+      "\\[unprintable value of size [0-9]+ @(0x)?[0-9a-fA-F]+\\]");
+#endif
+}
+
+auto HasExactlyNInstancesOf(int n, absl::string_view me) {
+#ifdef GTEST_USES_SIMPLE_RE
+  (void)n;
+  return HasSubstr(me);
+#else
+  absl::string_view value_m_times = "(.*$0){$1}.*";
+
+  return AllOf(MatchesRegex(absl::Substitute(value_m_times, me, n)),
+               Not(MatchesRegex(absl::Substitute(value_m_times, me, n + 1))));
+#endif
+}
+
+template <typename T>
+std::string GenericPrintToString(const T& v) {
+  std::stringstream ss;
+  ss << GenericPrint(v);
+  {
+    std::stringstream ss2;
+    ss2 << GenericPrint() << v;
+    EXPECT_EQ(ss.str(), ss2.str());
+  }
+  return ss.str();
+}
+
+TEST(GenericPrinterTest, Bool) {
+  EXPECT_EQ("true", GenericPrintToString(true));
+  EXPECT_EQ("false", GenericPrintToString(false));
+}
+
+TEST(GenericPrinterTest, VectorOfBool) {
+  std::vector<bool> v{true, false, true};
+  const auto& cv = v;
+  EXPECT_EQ("[true, false, true]", GenericPrintToString(v));
+  EXPECT_EQ("true", GenericPrintToString(v[0]));
+  EXPECT_EQ("true", GenericPrintToString(cv[0]));
+}
+
+TEST(GenericPrinterTest, CharLiterals) {
+  EXPECT_EQ(R"(a"\b)", GenericPrintToString(R"(a"\b)"));
+}
+
+TEST(GenericPrinterTest, Builtin) {
+  EXPECT_EQ("123", GenericPrintToString(123));
+}
+
+TEST(GenericPrinterTest, AbslStringifiable) {
+  EXPECT_EQ("AbslStringifiable!", GenericPrintToString(AbslStringifiable{}));
+}
+
+TEST(GenericPrinterTest, Nullptr) {
+  EXPECT_EQ("nullptr", GenericPrintToString(nullptr));
+}
+
+TEST(GenericPrinterTest, Chars) {
+  EXPECT_EQ(R"('\x0a' (0x0a 10))", GenericPrintToString('\x0a'));
+  EXPECT_EQ(R"(' ' (0x20 32))", GenericPrintToString(' '));
+  EXPECT_EQ(R"('~' (0x7e 126))", GenericPrintToString('~'));
+  EXPECT_EQ(R"('\'' (0x27 39))", GenericPrintToString('\''));
+}
+
+TEST(GenericPrinterTest, SignedChars) {
+  EXPECT_EQ(R"('\x0a' (0x0a 10))",
+            GenericPrintToString(static_cast<signed char>('\x0a')));
+  EXPECT_EQ(R"(' ' (0x20 32))",
+            GenericPrintToString(static_cast<signed char>(' ')));
+  EXPECT_EQ(R"('~' (0x7e 126))",
+            GenericPrintToString(static_cast<signed char>('~')));
+  EXPECT_EQ(R"('\'' (0x27 39))",
+            GenericPrintToString(static_cast<signed char>('\'')));
+}
+
+TEST(GenericPrinterTest, UnsignedChars) {
+  EXPECT_EQ(R"('\x0a' (0x0a 10))",
+            GenericPrintToString(static_cast<unsigned char>('\x0a')));
+  EXPECT_EQ(R"(' ' (0x20 32))",
+            GenericPrintToString(static_cast<unsigned char>(' ')));
+  EXPECT_EQ(R"('~' (0x7e 126))",
+            GenericPrintToString(static_cast<unsigned char>('~')));
+  EXPECT_EQ(R"('\'' (0x27 39))",
+            GenericPrintToString(static_cast<unsigned char>('\'')));
+}
+
+TEST(GenericPrinterTest, Bytes) {
+  EXPECT_EQ("0x00", GenericPrintToString(static_cast<std::byte>(0)));
+  EXPECT_EQ("0x7f", GenericPrintToString(static_cast<std::byte>(0x7F)));
+  EXPECT_EQ("0xff", GenericPrintToString(static_cast<std::byte>(0xFF)));
+}
+
+TEST(GenericPrinterTest, Strings) {
+  const std::string expected_quotes = R"("a\"\\b")";
+  EXPECT_EQ(expected_quotes, GenericPrintToString(std::string(R"(a"\b)")));
+  const std::string expected_nonprintable = R"("\x00\xcd\n\xab")";
+  EXPECT_EQ(expected_nonprintable,
+            GenericPrintToString(absl::string_view("\0\315\n\xAB", 4)));
+}
+
+TEST(GenericPrinterTest, PreciseFloat) {
+  // Instead of testing exactly how the values are formatted, just check that
+  // they are distinct.
+
+  // Ensure concise output for exact values:
+  EXPECT_EQ("1f", GenericPrintToString(1.f));
+  EXPECT_EQ("1.1f", GenericPrintToString(1.1f));
+
+  // Plausible real-world values:
+  float f = 10.0000095f;
+  EXPECT_NE(GenericPrintToString(f), GenericPrintToString(10.0000105f));
+  // Smallest increment for a real-world value:
+  EXPECT_NE(GenericPrintToString(f),
+            GenericPrintToString(std::nextafter(f, 11)));
+  // The two smallest (finite) values possible:
+  EXPECT_NE(GenericPrintToString(std::numeric_limits<float>::lowest()),
+            GenericPrintToString(
+                std::nextafter(std::numeric_limits<float>::lowest(), 1)));
+  // Ensure the value has the correct type suffix:
+  EXPECT_THAT(GenericPrintToString(0.f), EndsWith("f"));
+}
+
+TEST(GenericPrinterTest, PreciseDouble) {
+  EXPECT_EQ("1", GenericPrintToString(1.));
+  EXPECT_EQ("1.1", GenericPrintToString(1.1));
+  double d = 10.000000000000002;
+  EXPECT_NE(GenericPrintToString(d), GenericPrintToString(10.000000000000004));
+  EXPECT_NE(GenericPrintToString(d),
+            GenericPrintToString(std::nextafter(d, 11)));
+  EXPECT_NE(GenericPrintToString(std::numeric_limits<double>::lowest()),
+            GenericPrintToString(
+                std::nextafter(std::numeric_limits<double>::lowest(), 1)));
+  EXPECT_THAT(GenericPrintToString(0.), EndsWith("0"));
+}
+
+TEST(GenericPrinterTest, PreciseLongDouble) {
+  EXPECT_EQ("1L", GenericPrintToString(1.L));
+  EXPECT_EQ("1.1L", GenericPrintToString(1.1L));
+  long double ld = 10.0000000000000000000000000000002;
+  EXPECT_NE(GenericPrintToString(ld),
+            GenericPrintToString(10.0000000000000000000000000000004));
+  EXPECT_NE(GenericPrintToString(ld),
+            GenericPrintToString(std::nextafter(ld, 11)));
+  EXPECT_NE(GenericPrintToString(std::numeric_limits<long double>::lowest()),
+            GenericPrintToString(
+                std::nextafter(std::numeric_limits<long double>::lowest(), 1)));
+  EXPECT_THAT(GenericPrintToString(0.L), EndsWith("L"));
+}
+
+TEST(GenericPrinterTest, StreamableLvalue) {
+  generic_logging_test::Streamable x{234};
+  EXPECT_EQ("Streamable{234}", GenericPrintToString(x));
+}
+
+TEST(GenericPrinterTest, StreamableXvalue) {
+  EXPECT_EQ("Streamable{345}",
+            GenericPrintToString(generic_logging_test::Streamable{345}));
+}
+
+TEST(GenericPrinterTest, NotStreamableWithoutGenericPrint) {
+  ::generic_logging_test::NotStreamable x;
+  std::stringstream ss;
+  ::operator<<(ss, x);
+  EXPECT_EQ(ss.str(), "This overload should NOT be found by GenericPrint.");
+}
+
+TEST(GenericPrinterTest, NotStreamableLvalue) {
+  generic_logging_test::NotStreamable x;
+  EXPECT_THAT(GenericPrintToString(x), IsUnprintable());
+}
+
+TEST(GenericPrinterTest, NotStreamableXvalue) {
+  EXPECT_THAT(GenericPrintToString(generic_logging_test::NotStreamable{}),
+              IsUnprintable());
+}
+
+TEST(GenericPrinterTest, DebugString) {
+  struct WithDebugString {
+    std::string val;
+    std::string DebugString() const {
+      return absl::StrCat("WithDebugString{", val, "}");
+    }
+  };
+  EXPECT_EQ("WithDebugString{foo}",
+            GenericPrintToString(WithDebugString{"foo"}));
+}
+
+TEST(GenericPrinterTest, Vector) {
+  std::vector<int> v = {4, 5, 6};
+  EXPECT_THAT(GenericPrintToString(v), MatchesRegex(".*4,? 5,? 6.*"));
+}
+
+TEST(GenericPrinterTest, StreamableVector) {
+  std::vector<generic_logging_test::Streamable> v = {{7}, {8}, {9}};
+  EXPECT_THAT(GenericPrintToString(v),
+              MatchesRegex(".*Streamable.7.,? Streamable.8.,? Streamable.9.*"));
+}
+
+TEST(GenericPrinterTest, Map) {
+  absl::flat_hash_map<
+      std::string, absl::flat_hash_map<std::string, std::pair<double, double>>>
+      v = {{"A", {{"B", {.5, .25}}}}};
+
+  EXPECT_THAT(GenericPrintToString(v), R"([<"A", [<"B", <0.5, 0.25>>]>])");
+
+  std::map<std::string, std::map<std::string, std::pair<double, double>>> v2 = {
+      {"A", {{"B", {.5, .25}}}}};
+
+  EXPECT_THAT(GenericPrintToString(v2), R"([<"A", [<"B", <0.5, 0.25>>]>])");
+}
+
+TEST(GenericPrinterTest, StreamAdapter) {
+  std::stringstream ss;
+  static_assert(
+      std::is_same<
+          typename std::remove_reference<decltype(ss << GenericPrint())>::type,
+          internal_generic_printer::GenericPrintStreamAdapter::Impl<
+              std::stringstream>>::value,
+      "expected ostream << GenericPrint() to yield adapter impl");
+
+  ss << GenericPrint() << "again, " << "back-up, " << "cue, "
+     << "double-u, " << "eye, "
+     << "four: " << generic_logging_test::NotStreamable{};
+  EXPECT_THAT(
+      ss.str(),
+      MatchesRegex(
+          "again, back-up, cue, double-u, eye, four: .unprintable value.*"));
+}
+
+TEST(GenericPrinterTest, NotStreamableVector) {
+  std::vector<generic_logging_test::NotStreamable> v = {{}, {}, {}};
+#ifdef GTEST_USES_SIMPLE_RE
+  EXPECT_THAT(GenericPrintToString(v), HasSubstr("unprintable"));
+#else
+  EXPECT_THAT(GenericPrintToString(v), MatchesRegex(".*(unprintable.*){3}.*"));
+#endif
+}
+
+struct CustomContainer : public std::array<int, 4> {
+  template <typename Sink>
+  friend void AbslStringify(Sink& sink, const CustomContainer& c) {
+    absl::Format(&sink, "%d %d", c[0], c[1]);
+  }
+};
+
+// Checks that AbslStringify (go/totw/215) is respected for container-like
+// types.
+TEST(GenericPrinterTest, ContainerLikeCustomLogging) {
+  CustomContainer c = {1, 2, 3, 4};
+  EXPECT_EQ(GenericPrintToString(c), "1 2");
+}
+
+// Test helper: this function demonstrates customizable printing logic:
+// 'GenericPrinter<T>' can be nominated as a default template argument.
+template <typename T, typename Printer = GenericPrinter<T>>
+std::string SpecializablePrint(const T& v) {
+  std::stringstream ss;
+  ss << Printer{v};
+  return ss.str();
+}
+
+TEST(GenericPrinterTest, DefaultPrinter) {
+  EXPECT_EQ("123", SpecializablePrint(123));
+}
+
+// Example of custom printing logic. This doesn't actually test anything in
+// GenericPrinter, but it's a working example of customizing printing logic (as
+// opposed to the comments in generic_printer.h).
+struct CustomPrinter {
+  explicit CustomPrinter(int) {}
+  friend std::ostream& operator<<(std::ostream& os, CustomPrinter&&) {
+    return os << "custom printer";
+  }
+};
+
+TEST(GenericPrinterTest, CustomPrinter) {
+  EXPECT_EQ("custom printer", (SpecializablePrint<int, CustomPrinter>(123)));
+}
+
+TEST(GenricPrinterTest, Nullopt) {
+  EXPECT_EQ("nullopt", GenericPrintToString(std::nullopt));
+}
+
+TEST(GenericPrinterTest, Optional) {
+  EXPECT_EQ("nullopt", GenericPrintToString(std::optional<int>()));
+  EXPECT_EQ("nullopt", GenericPrintToString(std::optional<int>(std::nullopt)));
+  EXPECT_EQ("<3>", GenericPrintToString(std::make_optional(3)));
+  EXPECT_EQ("<Streamable{3}>", GenericPrintToString(std::make_optional(
+                                   generic_logging_test::Streamable{3})));
+}
+
+TEST(GenericPrinterTest, Tuple) {
+  EXPECT_EQ("<1, two, 3>", GenericPrintToString(std::make_tuple(1, "two", 3)));
+}
+
+TEST(GenericPrinterTest, EmptyTuple) {
+  EXPECT_EQ("<>", GenericPrintToString(std::make_tuple()));
+}
+
+TEST(GenericPrinterTest, TupleWithStreamableMember) {
+  EXPECT_EQ("<1, two, Streamable{3}>",
+            GenericPrintToString(std::make_tuple(
+                1, "two", generic_logging_test::Streamable{3})));
+}
+
+TEST(GenericPrinterTest, Variant) {
+  EXPECT_EQ(R"(('(index = 0)' "cow"))",
+            GenericPrintToString(std::variant<std::string, float>("cow")));
+
+  EXPECT_EQ("('(index = 1)' 1.1f)",
+            GenericPrintToString(std::variant<std::string, float>(1.1F)));
+}
+
+TEST(GenericPrinterTest, VariantMonostate) {
+  EXPECT_THAT(GenericPrintToString(std::variant<std::monostate, std::string>()),
+              IsUnprintable());
+}
+
+TEST(GenericPrinterTest, VariantNonStreamable) {
+  EXPECT_EQ(R"(('(index = 0)' "cow"))",
+            GenericPrintToString(
+                std::variant<std::string, generic_logging_test::NotStreamable>(
+                    "cow")));
+
+  EXPECT_THAT(
+      GenericPrintToString(
+          std::variant<std::string, generic_logging_test::NotStreamable>(
+              generic_logging_test::NotStreamable{})),
+      IsUnprintable());
+}
+
+TEST(GenericPrinterTest, VariantNestedVariant) {
+  EXPECT_EQ(
+      "('(index = 1)' ('(index = 1)' 1.1f))",
+      GenericPrintToString(std::variant<std::string, std::variant<int, float>>(
+          std::variant<int, float>(1.1F))));
+}
+
+TEST(GenericPrinterTest, VariantInPlace) {
+  EXPECT_EQ("('(index = 0)' 17)", GenericPrintToString(std::variant<int, int>(
+                                      std::in_place_index<0>, 17)));
+
+  EXPECT_EQ("('(index = 1)' 17)", GenericPrintToString(std::variant<int, int>(
+                                      std::in_place_index<1>, 17)));
+}
+
+TEST(GenericPrinterTest, StatusOrLikeOkPrintsValue) {
+  EXPECT_EQ(R"(<OK: "cow">)",
+            GenericPrintToString(absl::StatusOr<std::string>("cow")));
+
+  EXPECT_EQ(R"(<OK: 1.1f>)", GenericPrintToString(absl::StatusOr<float>(1.1F)));
+}
+
+TEST(GenericPrinterTest, StatusOrLikeNonOkPrintsStatus) {
+  EXPECT_THAT(
+      GenericPrintToString(absl::StatusOr<float>(
+          absl::InvalidArgumentError("my error message"))),
+      AllOf(HasSubstr("my error message"), HasSubstr("INVALID_ARGUMENT")));
+
+  EXPECT_THAT(GenericPrintToString(
+                  absl::StatusOr<int>(absl::AbortedError("other message"))),
+              AllOf(HasSubstr("other message"), HasSubstr("ABORTED")));
+}
+
+TEST(GenericPrinterTest, StatusOrLikeNonStreamableValueUnprintable) {
+  EXPECT_THAT(
+      GenericPrintToString(absl::StatusOr<generic_logging_test::NotStreamable>(
+          generic_logging_test::NotStreamable{})),
+      IsUnprintable());
+}
+
+TEST(GenericPrinterTest, StatusOrLikeNonStreamableErrorStillPrintable) {
+  EXPECT_THAT(
+      GenericPrintToString(absl::StatusOr<generic_logging_test::NotStreamable>(
+          absl::AbortedError("other message"))),
+      AllOf(HasSubstr("other message"), HasSubstr("ABORTED")));
+}
+
+TEST(GenericPrinterTest, IsSupportedPointer) {
+  using internal_generic_printer::is_supported_ptr;
+
+  EXPECT_TRUE(is_supported_ptr<std::unique_ptr<std::string>>);
+  EXPECT_TRUE(is_supported_ptr<std::unique_ptr<int[]>>);
+  EXPECT_TRUE((is_supported_ptr<std::unique_ptr<void, void (*)(void*)>>));
+
+  EXPECT_FALSE(is_supported_ptr<int*>);
+  EXPECT_FALSE(is_supported_ptr<std::shared_ptr<int>>);
+  EXPECT_FALSE(is_supported_ptr<std::weak_ptr<int>>);
+}
+
+TEST(GenericPrinterTest, SmartPointerPrintsNullptrForAllNullptrs) {
+  std::unique_ptr<std::string> up;
+
+  EXPECT_EQ("<nullptr>", GenericPrintToString(up));
+}
+
+TEST(GenericPrinterTest, SmartPointerPrintsValueIfNonNull) {
+  EXPECT_THAT(GenericPrintToString(std::make_unique<int>(5)),
+              HasSubstr("pointing to 5"));
+}
+
+TEST(GenericPrinterTest, SmartPointerPrintsAddressOfPointee) {
+  auto i = std::make_unique<int>(5);
+  auto c = std::make_unique<char>('z');
+  char memory[] = "abcdefg";
+  auto cp = std::make_unique<char*>(memory);
+
+  EXPECT_THAT(GenericPrintToString(i),
+              AnyOf(Eq(absl::StrFormat("<%016X pointing to 5>",
+                                       reinterpret_cast<intptr_t>(&*i))),
+                    Eq(absl::StrFormat("<%#x pointing to 5>",
+                                       reinterpret_cast<intptr_t>(&*i)))));
+
+  EXPECT_THAT(
+      GenericPrintToString(c),
+      AnyOf(HasSubstr(absl::StrFormat("<%016X pointing to 'z'",
+                                      reinterpret_cast<intptr_t>(&*c))),
+            HasSubstr(absl::StrFormat("<%#x pointing to 'z'",
+                                      reinterpret_cast<intptr_t>(&*c)))));
+
+  EXPECT_THAT(GenericPrintToString(cp),
+              AnyOf(Eq(absl::StrFormat("<%016X pointing to abcdefg>",
+                                       reinterpret_cast<intptr_t>(&*cp))),
+                    Eq(absl::StrFormat("<%#x pointing to abcdefg>",
+                                       reinterpret_cast<intptr_t>(&*cp)))));
+}
+
+TEST(GenericPrinterTest, SmartPointerToArrayOnlyPrintsAddressAndHelpText) {
+  auto empty = std::make_unique<int[]>(0);
+  auto nonempty = std::make_unique<int[]>(5);
+  nonempty[0] = 12345;
+  nonempty[4] = 54321;
+  // NOTE: ArenaSafeUniquePtr is not meant to support array-type template
+  // parameters, so we skip testing that here.
+  // http://g/c-users/J-AEFrFHssY/UMMFzCkdBAAJ, b/265984185.
+
+  EXPECT_THAT(
+      GenericPrintToString(nonempty),
+      AllOf(AnyOf(HasSubstr(absl::StrFormat(
+                      "%016X", reinterpret_cast<intptr_t>(nonempty.get()))),
+                  HasSubstr(absl::StrFormat(
+                      "%#x", reinterpret_cast<intptr_t>(nonempty.get())))),
+            HasSubstr("array"), Not(HasSubstr("to 54321")),
+            Not(HasSubstr("to 12345"))));
+
+  EXPECT_THAT(
+      GenericPrintToString(empty),
+      AllOf(AnyOf(HasSubstr(absl::StrFormat(
+                      "%016X", reinterpret_cast<intptr_t>(empty.get()))),
+                  HasSubstr(absl::StrFormat(
+                      "%#x", reinterpret_cast<intptr_t>(empty.get())))),
+            HasSubstr("array")));
+}
+
+TEST(GenericPrinterTest, SmartPointerToNonObjectType) {
+  auto int_ptr_deleter = [](void* data) {
+    int* p = static_cast<int*>(data);
+    delete p;
+  };
+
+  std::unique_ptr<void, decltype(int_ptr_deleter)> void_ptr(new int(959),
+                                                            int_ptr_deleter);
+
+  EXPECT_THAT(GenericPrintToString(void_ptr),
+              HasSubstr("pointing to a non-object type"));
+}
+
+TEST(GenericPrinterTest, PrintsCustomDeleterSmartPointer) {
+  // Delete `p` (if not nullptr) only on the 4th time the deleter is used.
+  auto four_deleter = [](std::string* p) {
+    static int counter = 0;
+    if (p == nullptr) return;  // skip calls to moved-from destructors.
+    if (++counter >= 4) delete p;
+  };
+
+  // Have four `unique_ptr`s "manage" the same string-pointer, with only the
+  // final (4th) call to the deleter deleting the string pointer.
+  auto* unique_string = new std::string("unique string");
+  std::vector<std::unique_ptr<std::string, decltype(four_deleter)>> test_ptrs;
+  for (int i = 0; i < 4; ++i) {
+    test_ptrs.emplace_back(unique_string, four_deleter);
+  }
+
+  EXPECT_THAT(GenericPrintToString(test_ptrs),
+              HasExactlyNInstancesOf(4, "unique string"));
+}
+
+// Ensure that GenericPrint is robust to recursion when a type's operator<<
+// calls into GenericPrint internally.
+struct CustomRecursive {
+  std::unique_ptr<CustomRecursive> next;
+  int val = 0;
+
+  friend std::ostream& operator<<(std::ostream& os, const CustomRecursive& cr) {
+    return os << "custom print: next = " << GenericPrintToString(cr.next);
+  }
+};
+
+TEST(GenericPrinterTest, DISABLED_CustomPrintOverloadRecursionDetected) {
+  auto r1 = std::make_unique<CustomRecursive>();
+  r1->val = 1;
+  auto& r2 = r1->next = std::make_unique<CustomRecursive>();
+  r2->val = 2;
+  r2->next = std::move(r1);
+
+  EXPECT_THAT(GenericPrintToString(*r2),
+              AllOf(HasExactlyNInstancesOf(2, "custom print"),
+                    HasExactlyNInstancesOf(1, "<recursive>")));
+
+  r2->next = nullptr;  // break the cycle
+}
+// <end DISABLED_ test section>
+
+enum CStyleEnum { kValue0, kValue1 };
+TEST(GenericPrinterTest, Enum) {
+  EXPECT_EQ("1", GenericPrintToString(kValue1));
+}
+
+enum class CppStyleEnum { kValue0, kValue1, kValue2 };
+TEST(GenericPrinterTest, EnumClass) {
+  EXPECT_EQ("2", GenericPrintToString(CppStyleEnum::kValue2));
+}
+
+enum class CharBasedEnum : char { kValueA = 'A', kValue1 = '\x01' };
+TEST(GenericPrinterTest, CharBasedEnum) {
+  EXPECT_EQ("'A' (0x41 65)", GenericPrintToString(CharBasedEnum::kValueA));
+  EXPECT_EQ("'\\x01' (0x01 1)", GenericPrintToString(CharBasedEnum::kValue1));
+}
+
+enum class WideBasedEnum : uint64_t {
+  kValue = std::numeric_limits<uint64_t>::max()
+};
+TEST(GenericPrinterTest, WideBasedEnum) {
+  EXPECT_EQ(absl::StrCat(std::numeric_limits<uint64_t>::max()),
+            GenericPrintToString(WideBasedEnum::kValue));
+}
+
+enum CStyleEnumWithStringify { kValueA = 0, kValueB = 2 };
+template <typename Sink>
+void AbslStringify(Sink& sink, CStyleEnumWithStringify e) {
+  switch (e) {
+    case CStyleEnumWithStringify::kValueA:
+      sink.Append("A");
+      return;
+    case CStyleEnumWithStringify::kValueB:
+      sink.Append("B");
+      return;
+  }
+  sink.Append("??");
+}
+TEST(GenericPrinterTest, CStyleEnumWithStringify) {
+  EXPECT_EQ("A", GenericPrintToString(CStyleEnumWithStringify::kValueA));
+  EXPECT_EQ("??",
+            GenericPrintToString(static_cast<CStyleEnumWithStringify>(1)));
+}
+
+enum class CppStyleEnumWithStringify { kValueA, kValueB, kValueC };
+template <typename Sink>
+void AbslStringify(Sink& sink, CppStyleEnumWithStringify e) {
+  switch (e) {
+    case CppStyleEnumWithStringify::kValueA:
+      sink.Append("A");
+      return;
+    case CppStyleEnumWithStringify::kValueB:
+      sink.Append("B");
+      return;
+    case CppStyleEnumWithStringify::kValueC:
+      sink.Append("C");
+      return;
+  }
+  sink.Append("??");
+}
+TEST(GenericPrinterTest, CppStyleEnumWithStringify) {
+  EXPECT_EQ("A", GenericPrintToString(CppStyleEnumWithStringify::kValueA));
+  EXPECT_EQ("??",
+            GenericPrintToString(static_cast<CppStyleEnumWithStringify>(17)));
+}
+
+enum class CharBasedEnumWithStringify : char { kValueA = 'A', kValueB = 'B' };
+template <typename Sink>
+void AbslStringify(Sink& sink, CharBasedEnumWithStringify e) {
+  switch (e) {
+    case CharBasedEnumWithStringify::kValueA:
+      sink.Append("charA");
+      return;
+    case CharBasedEnumWithStringify::kValueB:
+      sink.Append("charB");
+      return;
+  }
+  sink.Append("??");
+}
+TEST(GenericPrinterTest, CharBasedEnumWithStringify) {
+  EXPECT_EQ("charA", GenericPrintToString(CharBasedEnumWithStringify::kValueA));
+  EXPECT_EQ("??",
+            GenericPrintToString(static_cast<CharBasedEnumWithStringify>('W')));
+}
+
+}  // namespace
+}  // namespace strings_internal
+ABSL_NAMESPACE_END
+}  // namespace absl