Breaking change: Return NotImplemented when comparing descriptor containers with unrecognized types in the C++ and upb Python implementations.

Equality comparisons (== and !=) between descriptor containers and objects of unrecognized types previously emitted a FutureWarning and treated the objects as unequal.

They now return NotImplemented, following standard Python comparison behaviour
so that the reflected comparison on the other operand is attempted.

https://protobuf.dev/news/2026-09-22/#return-notimplemented-for-unrecognized-types

PiperOrigin-RevId: 1002560722
diff --git a/python/descriptor_containers.c b/python/descriptor_containers.c
index 580e728..3393522 100644
--- a/python/descriptor_containers.c
+++ b/python/descriptor_containers.c
@@ -7,7 +7,6 @@
 
 #include "python/descriptor_containers.h"
 
-#include "google/protobuf/breaking_changes.h"
 #include "python/descriptor.h"
 #include "python/protobuf.h"
 #include "upb/reflection/def.h"
@@ -39,21 +38,6 @@
   kPyUpb_CompareNotImplemented,
 } PyUpb_CompareResult;
 
-static PyUpb_CompareResult PyUpb_CompareUnrecognized(void) {
-#if PROTOBUF_PY_FUTURE_CONTAINER_EQ_RETURNS_NOTIMPLEMENTED
-  return kPyUpb_CompareNotImplemented;
-#else
-  if (PyErr_WarnEx(
-          PyExc_FutureWarning,
-          "Comparing descriptor containers with unrecognized types will return "
-          "NotImplemented in 2027.",
-          3) < 0) {
-    return kPyUpb_CompareError;
-  }
-  return kPyUpb_CompareNotEqual;
-#endif
-}
-
 // -----------------------------------------------------------------------------
 // ByNameIterator
 // -----------------------------------------------------------------------------
@@ -247,7 +231,7 @@
   }
 
   if (!PyList_Check(other)) {
-    return PyUpb_CompareUnrecognized();
+    return kPyUpb_CompareNotImplemented;
   }
 
   // return list(self) == other
@@ -285,11 +269,7 @@
   PyUpb_CompareResult eq = PyUpb_GenericSequence_IsEqual(self, other);
   switch (eq) {
     case kPyUpb_CompareNotImplemented:
-#if PROTOBUF_PY_FUTURE_CONTAINER_EQ_RETURNS_NOTIMPLEMENTED
       Py_RETURN_NOTIMPLEMENTED;
-#else
-      UPB_UNREACHABLE();  // Unreachable when this breaking change is disabled.
-#endif
     case kPyUpb_CompareError:
       return NULL;
     case kPyUpb_CompareEqual:
@@ -566,7 +546,7 @@
   }
 
   if (!PyDict_Check(other)) {
-    return PyUpb_CompareUnrecognized();
+    return kPyUpb_CompareNotImplemented;
   }
 
   PyObject* self_dict = PyDict_New();
@@ -590,11 +570,7 @@
   PyUpb_CompareResult eq = PyUpb_ByNameMap_IsEqual(self, other);
   switch (eq) {
     case kPyUpb_CompareNotImplemented:
-#if PROTOBUF_PY_FUTURE_CONTAINER_EQ_RETURNS_NOTIMPLEMENTED
       Py_RETURN_NOTIMPLEMENTED;
-#else
-      UPB_UNREACHABLE();  // Unreachable when this breaking change is disabled.
-#endif
     case kPyUpb_CompareError:
       return NULL;
     case kPyUpb_CompareEqual:
@@ -814,7 +790,7 @@
   }
 
   if (!PyDict_Check(other)) {
-    return PyUpb_CompareUnrecognized();
+    return kPyUpb_CompareNotImplemented;
   }
 
   PyObject* self_dict = PyDict_New();
@@ -838,11 +814,7 @@
   PyUpb_CompareResult eq = PyUpb_ByNumberMap_IsEqual(self, other);
   switch (eq) {
     case kPyUpb_CompareNotImplemented:
-#if PROTOBUF_PY_FUTURE_CONTAINER_EQ_RETURNS_NOTIMPLEMENTED
       Py_RETURN_NOTIMPLEMENTED;
-#else
-      UPB_UNREACHABLE();  // Unreachable when this breaking change is disabled.
-#endif
     case kPyUpb_CompareError:
       return NULL;
     case kPyUpb_CompareEqual:
diff --git a/python/google/protobuf/breaking_changes.h b/python/google/protobuf/breaking_changes.h
index 901281f..2dbab47 100644
--- a/python/google/protobuf/breaking_changes.h
+++ b/python/google/protobuf/breaking_changes.h
@@ -14,19 +14,12 @@
 
 #ifdef PROTOBUF_PY_FUTURE_BREAKING_CHANGES
 
-// Fix PyProto C++ and upb implementations to return NotImplemented in
-// descriptor container equality checks for unrecognized types.
-// Owner: runze@
-#define PROTOBUF_PY_FUTURE_CONTAINER_EQ_RETURNS_NOTIMPLEMENTED 1
-
 // Make GetOptions() return immutable options.
 // Owner: runze@
 #define PROTOBUF_PY_FUTURE_FREEZE_OPTIONS 1
 
 #else
 
-#define PROTOBUF_PY_FUTURE_CONTAINER_EQ_RETURNS_NOTIMPLEMENTED 0
-
 #define PROTOBUF_PY_FUTURE_FREEZE_OPTIONS 0
 
 #endif
diff --git a/python/google/protobuf/internal/descriptor_test.py b/python/google/protobuf/internal/descriptor_test.py
index a84b50f..318408f 100755
--- a/python/google/protobuf/internal/descriptor_test.py
+++ b/python/google/protobuf/internal/descriptor_test.py
@@ -148,10 +148,6 @@
     file_descriptor = pool.AddSerializedFile(serialized)
     self.assertEqual('', file_descriptor.package)
 
-  @unittest.skipIf(
-      api_implementation.Type() in ('upb', 'cpp'),
-      "Currently broken in OSS, will be fixed in the next breaking release"
-  )
   def testBadEqOnFieldsByName(self):
     fields = unittest_pb2.TestAllTypes.DESCRIPTOR.fields_by_name
 
diff --git a/python/google/protobuf/pyext/descriptor_containers.cc b/python/google/protobuf/pyext/descriptor_containers.cc
index d1eb112..67cf059 100644
--- a/python/google/protobuf/pyext/descriptor_containers.cc
+++ b/python/google/protobuf/pyext/descriptor_containers.cc
@@ -39,7 +39,6 @@
 #include "absl/strings/string_view.h"
 #include "google/protobuf/descriptor.h"
 #include "google/protobuf/port_def.inc"
-#include "google/protobuf/breaking_changes.h"
 #include "google/protobuf/pyext/descriptor.h"
 #include "google/protobuf/pyext/descriptor_pool.h"
 #include "google/protobuf/pyext/scoped_pyobject_ptr.h"
@@ -307,21 +306,6 @@
   kNotImplemented,
 };
 
-static CompareResult CompareUnrecognized() {
-#if PROTOBUF_PY_FUTURE_CONTAINER_EQ_RETURNS_NOTIMPLEMENTED
-  return CompareResult::kNotImplemented;
-#else
-  if (PyErr_WarnEx(
-          PyExc_FutureWarning,
-          "Comparing descriptor containers with unrecognized types will return "
-          "NotImplemented in 2027.",
-          3) < 0) {
-    return CompareResult::kError;
-  }
-  return CompareResult::kNotEqual;
-#endif
-}
-
 // A sequence container can only be equal to another sequence container, or (for
 // backward compatibility) to a list containing the same items.
 static CompareResult DescriptorSequence_Equal(PyContainer* self,
@@ -362,7 +346,7 @@
     return CompareResult::kEqual;
   }
 
-  return CompareUnrecognized();
+  return CompareResult::kNotImplemented;
 }
 
 // A mapping container can only be equal to another mapping container, or (for
@@ -410,7 +394,7 @@
     return CompareResult::kEqual;
   }
 
-  return CompareUnrecognized();
+  return CompareResult::kNotImplemented;
 }
 
 static PyObject* RichCompare(PyContainer* self, PyObject* other, int opid) {
@@ -428,11 +412,7 @@
   }
   switch (result) {
     case CompareResult::kNotImplemented:
-#if PROTOBUF_PY_FUTURE_CONTAINER_EQ_RETURNS_NOTIMPLEMENTED
       Py_RETURN_NOTIMPLEMENTED;
-#else
-      return nullptr;  // Unreachable when this breaking change is disabled.
-#endif
     case CompareResult::kError:
       return nullptr;
     case CompareResult::kEqual: