Allow CHECK_<OP> variants to be used with unprintable types.

PiperOrigin-RevId: 786406173
Change-Id: Ifc362a702f82a6b3ad33210980dda6f40f14fca4
diff --git a/absl/log/check_test_impl.inc b/absl/log/check_test_impl.inc
index 37226a3..5a7caf4 100644
--- a/absl/log/check_test_impl.inc
+++ b/absl/log/check_test_impl.inc
@@ -265,6 +265,26 @@
   ABSL_TEST_CHECK_NE(nullptr, p_not_null);
 }
 
+struct ExampleTypeThatHasNoStreamOperator {
+  bool x;
+
+  bool operator==(const ExampleTypeThatHasNoStreamOperator& other) const {
+    return x == other.x;
+  }
+  bool operator==(const bool& other) const { return x == other; }
+};
+
+TEST(CHECKDeathTest, TestBinaryChecksWithUnprintable) {
+  ExampleTypeThatHasNoStreamOperator a{true};
+  ExampleTypeThatHasNoStreamOperator b{false};
+  ABSL_TEST_CHECK_EQ(a, a);
+  EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b),
+               "Check failed: a == b \\(UNPRINTABLE vs. UNPRINTABLE\\)");
+  ABSL_TEST_CHECK_EQ(a, true);
+  EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, false),
+               "Check failed: a == false \\(UNPRINTABLE vs. 0\\)");
+}
+
 #if GTEST_HAS_DEATH_TEST
 
 // Test logging of various char-typed values by failing CHECK*().
diff --git a/absl/log/internal/check_op.cc b/absl/log/internal/check_op.cc
index 23db63b..5db98dd 100644
--- a/absl/log/internal/check_op.cc
+++ b/absl/log/internal/check_op.cc
@@ -101,6 +101,8 @@
   }
 }
 
+void MakeCheckOpUnprintableString(std::ostream& os) { os << "UNPRINTABLE"; }
+
 // Helper functions for string comparisons.
 #define DEFINE_CHECK_STROP_IMPL(name, func, expected)                          \
   const char* absl_nullable Check##func##expected##Impl(                       \
diff --git a/absl/log/internal/check_op.h b/absl/log/internal/check_op.h
index d7b55f6..4554475 100644
--- a/absl/log/internal/check_op.h
+++ b/absl/log/internal/check_op.h
@@ -224,6 +224,19 @@
 void MakeCheckOpValueString(std::ostream& os, unsigned char v);
 void MakeCheckOpValueString(std::ostream& os, const void* absl_nullable p);
 
+void MakeCheckOpUnprintableString(std::ostream& os);
+
+// A wrapper for types that have no operator<<.
+struct UnprintableWrapper {
+  template <typename T>
+  explicit UnprintableWrapper(const T&) {}
+
+  friend std::ostream& operator<<(std::ostream& os, const UnprintableWrapper&) {
+    MakeCheckOpUnprintableString(os);
+    return os;
+  }
+};
+
 namespace detect_specialization {
 
 // MakeCheckOpString is being specialized for every T and U pair that is being
@@ -353,6 +366,15 @@
                                              << std::declval<T>())>>
     : std::true_type {};
 
+// This overload triggers when T is neither possible to print nor an enum.
+template <typename T>
+std::enable_if_t<std::negation_v<std::disjunction<
+                     std::is_convertible<T, int>, std::is_enum<T>,
+                     std::is_pointer<T>, std::is_same<T, std::nullptr_t>,
+                     is_streamable<T>, HasAbslStringify<T>>>,
+                 UnprintableWrapper>
+Detect(...);
+
 // This overload triggers when T is a scoped enum that has not defined an output
 // stream operator (operator<<) or AbslStringify. It causes the enum value to be
 // converted to a type that can be streamed. For consistency with other enums, a