protos: Collapse ClearMessage template

We introduce Ptr removal so that our helper functions can be agnostically passed in a Ptr<T> or a T*.

We utilize SFINAE to determine if PtrOrRaw. If the target is const, or not in {T, T*, Ptr<T>}, the template will not match and be discarded (but still compile successfully).

PiperOrigin-RevId: 640198851
diff --git a/protos/protos.h b/protos/protos.h
index 87547f9..6037b44 100644
--- a/protos/protos.h
+++ b/protos/protos.h
@@ -242,6 +242,26 @@
                           const upb_MiniTableExtension* ext,
                           const upb_Message* extension);
 
+template <typename T>
+struct RemovePtr;
+
+template <typename T>
+struct RemovePtr<Ptr<T>> {
+  using type = T;
+};
+
+template <typename T>
+struct RemovePtr<T*> {
+  using type = T;
+};
+
+template <typename T>
+using RemovePtrT = typename RemovePtr<T>::type;
+
+template <typename T, typename U = RemovePtrT<T>,
+          typename = std::enable_if_t<!std::is_const_v<U>>>
+using PtrOrRaw = T;
+
 }  // namespace internal
 
 template <typename T>
@@ -280,14 +300,10 @@
 }
 
 template <typename T>
-void ClearMessage(Ptr<T> message) {
-  static_assert(!std::is_const_v<T>, "");
-  upb_Message_Clear(internal::GetInternalMsg(message), T::minitable());
-}
-
-template <typename T>
-void ClearMessage(T* message) {
-  ClearMessage(protos::Ptr(message));
+void ClearMessage(internal::PtrOrRaw<T> message) {
+  auto ptr = Ptr(message);
+  auto minitable = internal::GetMiniTable(ptr);
+  upb_Message_Clear(internal::GetInternalMsg(ptr), minitable);
 }
 
 class ExtensionRegistry {
diff --git a/protos_generator/tests/test_generated.cc b/protos_generator/tests/test_generated.cc
index 61bd66f..35d8562 100644
--- a/protos_generator/tests/test_generated.cc
+++ b/protos_generator/tests/test_generated.cc
@@ -1157,6 +1157,44 @@
   EXPECT_FALSE(::protos::HasExtension(&model, theme));
 }
 
+TEST(CppGeneratedCode, CanInvokeClearMessageWithPtr) {
+  // Fill model.
+  TestModel model;
+  model.set_int64(5);
+  auto new_child = model.add_child_models();
+  // Clear using Ptr<T>
+  auto ptr = ::protos::Ptr<TestModel>(&model);
+  ::protos::ClearMessage(ptr);
+  // Successful clear
+  EXPECT_FALSE(model.has_int64());
+}
+
+TEST(CppGeneratedCode, CanInvokeClearMessageWithRawPtr) {
+  // Fill model.
+  TestModel model;
+  model.set_int64(5);
+  auto new_child = model.add_child_models();
+  // Clear using T*
+  ::protos::ClearMessage(&model);
+  // Successful clear
+  EXPECT_FALSE(model.has_int64());
+}
+
+template <typename T>
+bool CanCallClearMessage() {
+  return Requires<T>([](auto x) -> decltype(::protos::ClearMessage(x)) {});
+}
+
+TEST(CppGeneratedCode, CannotInvokeClearMessageWithConstPtr) {
+  EXPECT_TRUE(CanCallClearMessage<::protos::Ptr<TestModel>>());
+  EXPECT_FALSE(CanCallClearMessage<::protos::Ptr<const TestModel>>());
+}
+
+TEST(CppGeneratedCode, CannotInvokeClearMessageWithConstRawPtr) {
+  EXPECT_TRUE(CanCallClearMessage<TestModel*>());
+  EXPECT_FALSE(CanCallClearMessage<const TestModel*>());
+}
+
 TEST(CppGeneratedCode, DeepCopy) {
   // Fill model.
   TestModel model;
@@ -1203,16 +1241,10 @@
   EXPECT_EQ(225, TestModel::kChildMapFieldNumber);
 }
 
-// TODO : Add BUILD rule to test failures below.
-#ifdef TEST_CLEAR_MESSAGE_FAILURE
-TEST(CppGeneratedCode, ClearConstMessageShouldFail) {
-  // Fill model.
+TEST(CppGeneratedCode, ClearConstMessageShouldFailForConstChild) {
   TestModel model;
-  model.set_int64(5);
-  model.set_str2("Hello");
-  // Only mutable_ can be cleared not Ptr<const T>.
-  ::protos::ClearMessage(model.child_model_1());
+  EXPECT_FALSE(CanCallClearMessage<decltype(model.child_model_1())>());
+  EXPECT_TRUE(CanCallClearMessage<decltype(model.mutable_child_model_1())>());
 }
-#endif
 
 }  // namespace