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: