diff --git a/cuda_core/cuda/core/_event.pyx b/cuda_core/cuda/core/_event.pyx index 3f5fb7ace26..ce7c7c2d3c7 100644 --- a/cuda_core/cuda/core/_event.pyx +++ b/cuda_core/cuda/core/_event.pyx @@ -330,9 +330,11 @@ cdef class IPCEventDescriptor: self._is_blocking_sync = is_blocking_sync return self - def __eq__(self, IPCEventDescriptor rhs): + def __eq__(self, other) -> bool: + if not isinstance(other, IPCEventDescriptor): + return NotImplemented # No need to check self._is_blocking_sync. - return self._reserved == rhs._reserved + return self._reserved == (other)._reserved def __reduce__(self): return IPCEventDescriptor._init, (self._reserved, self._is_blocking_sync) diff --git a/cuda_core/cuda/core/_layout.pyx b/cuda_core/cuda/core/_layout.pyx index 3e2580d11d1..787575cbb5e 100644 --- a/cuda_core/cuda/core/_layout.pyx +++ b/cuda_core/cuda/core/_layout.pyx @@ -176,8 +176,15 @@ cdef class _StridedLayout: f"_StridedLayout(shape={self.shape}, strides={self.strides}, itemsize={self.itemsize}, _slice_offset={self.slice_offset})" ) - def __eq__(self : _StridedLayout, other : _StridedLayout) -> bool: - return self.itemsize == other.itemsize and self.slice_offset == other.slice_offset and _base_layout_equal(self.base, other.base) + def __eq__(self, other): + if not isinstance(other, _StridedLayout): + return NotImplemented + cdef _StridedLayout _other = <_StridedLayout>other + return ( + self.itemsize == _other.itemsize + and self.slice_offset == _other.slice_offset + and _base_layout_equal(self.base, _other.base) + ) @property def ndim(self : _StridedLayout): diff --git a/cuda_core/tests/test_event.py b/cuda_core/tests/test_event.py index 4870c5081e7..c85efa19253 100644 --- a/cuda_core/tests/test_event.py +++ b/cuda_core/tests/test_event.py @@ -243,6 +243,43 @@ def test_ipc_event_descriptor_direct_init(): _event_module.IPCEventDescriptor() +@pytest.mark.parametrize( + "other", + [None, "string", 42, 3.14, (b"\x00" * 64,), object()], + ids=["None", "str", "int", "float", "tuple", "object"], +) +def test_ipc_event_descriptor_eq_other_type(other): + """IPCEventDescriptor.__eq__ returns NotImplemented for unrelated types. + + Regression test for https://github.com/NVIDIA/cuda-python/issues/2050: comparing + an IPCEventDescriptor to objects of unrelated types must not raise TypeError / + AttributeError; it must follow Python's rich-comparison protocol and yield False. + """ + import cuda.core._event as _event_module + + desc = _event_module.IPCEventDescriptor._init(b"\x00" * 64, True) + assert desc != other + assert not (desc == other) # noqa: SIM201 + assert other != desc + assert desc.__eq__(other) is NotImplemented + + +def test_ipc_event_descriptor_eq_same_value(): + """Two IPCEventDescriptors with the same _reserved compare equal.""" + import cuda.core._event as _event_module + + reserved = b"\x01" * 64 + a = _event_module.IPCEventDescriptor._init(reserved, True) + b = _event_module.IPCEventDescriptor._init(reserved, True) + c = _event_module.IPCEventDescriptor._init(b"\x02" * 64, True) + assert a == a + assert a == b + assert a != c + # _is_blocking_sync is intentionally ignored by __eq__. + d = _event_module.IPCEventDescriptor._init(reserved, False) + assert a == d + + # ============================================================================ # Event Hash Tests # ============================================================================ diff --git a/cuda_core/tests/test_strided_layout.py b/cuda_core/tests/test_strided_layout.py index 67c25d07f84..734837a886a 100644 --- a/cuda_core/tests/test_strided_layout.py +++ b/cuda_core/tests/test_strided_layout.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 @@ -927,3 +927,41 @@ def test_to_dense(layout_spec, new_stride_order): assert dense.is_dense assert dense.required_size_in_bytes() == np_ref.size * layout.itemsize assert dense.offset_bounds == (0, np_ref.size - 1) + + +@pytest.mark.parametrize( + "other", + [ + None, + "string", + 42, + 3.14, + (3, 4), + [1, 2, 3], + object(), + ], + ids=["None", "str", "int", "float", "tuple", "list", "object"], +) +def test_eq_returns_not_implemented_for_other_types(other): + """_StridedLayout.__eq__ returns False (via NotImplemented) for unrelated types. + + Regression test for https://github.com/NVIDIA/cuda-python/issues/2050: comparing + a _StridedLayout to objects of unrelated types must not raise AttributeError / + TypeError; it must follow Python's rich-comparison protocol and yield False. + """ + layout = _StridedLayout.dense((5, 3, 7), 4) + assert layout != other + assert not (layout == other) # noqa: SIM201 + assert other != layout + # Direct check of the protocol contract. + assert layout.__eq__(other) is NotImplemented + + +def test_eq_reflexive_and_value_equality(): + """_StridedLayout equality is reflexive and compares by value, not identity.""" + a = _StridedLayout.dense((5, 3, 7), 4) + b = _StridedLayout.dense((5, 3, 7), 4) + c = _StridedLayout.dense((5, 3, 8), 4) + assert a == a + assert a == b + assert a != c