Skip to content

Commit cff008d

Browse files
authored
Fix #2050: Fix __eq__ implementations (#2067)
1 parent 7b370b4 commit cff008d

4 files changed

Lines changed: 89 additions & 5 deletions

File tree

‎cuda_core/cuda/core/_event.pyx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,11 @@ cdef class IPCEventDescriptor:
330330
self._is_blocking_sync = is_blocking_sync
331331
return self
332332

333-
def __eq__(self, IPCEventDescriptor rhs):
333+
def __eq__(self, other) -> bool:
334+
if not isinstance(other, IPCEventDescriptor):
335+
return NotImplemented
334336
# No need to check self._is_blocking_sync.
335-
return self._reserved == rhs._reserved
337+
return self._reserved == (<IPCEventDescriptor>other)._reserved
336338

337339
def __reduce__(self):
338340
return IPCEventDescriptor._init, (self._reserved, self._is_blocking_sync)

‎cuda_core/cuda/core/_layout.pyx‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,15 @@ cdef class _StridedLayout:
176176
f"_StridedLayout(shape={self.shape}, strides={self.strides}, itemsize={self.itemsize}, _slice_offset={self.slice_offset})"
177177
)
178178

179-
def __eq__(self : _StridedLayout, other : _StridedLayout) -> bool:
180-
return self.itemsize == other.itemsize and self.slice_offset == other.slice_offset and _base_layout_equal(self.base, other.base)
179+
def __eq__(self, other):
180+
if not isinstance(other, _StridedLayout):
181+
return NotImplemented
182+
cdef _StridedLayout _other = <_StridedLayout>other
183+
return (
184+
self.itemsize == _other.itemsize
185+
and self.slice_offset == _other.slice_offset
186+
and _base_layout_equal(self.base, _other.base)
187+
)
181188

182189
@property
183190
def ndim(self : _StridedLayout):

‎cuda_core/tests/test_event.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,43 @@ def test_ipc_event_descriptor_direct_init():
243243
_event_module.IPCEventDescriptor()
244244

245245

246+
@pytest.mark.parametrize(
247+
"other",
248+
[None, "string", 42, 3.14, (b"\x00" * 64,), object()],
249+
ids=["None", "str", "int", "float", "tuple", "object"],
250+
)
251+
def test_ipc_event_descriptor_eq_other_type(other):
252+
"""IPCEventDescriptor.__eq__ returns NotImplemented for unrelated types.
253+
254+
Regression test for https://github.com/NVIDIA/cuda-python/issues/2050: comparing
255+
an IPCEventDescriptor to objects of unrelated types must not raise TypeError /
256+
AttributeError; it must follow Python's rich-comparison protocol and yield False.
257+
"""
258+
import cuda.core._event as _event_module
259+
260+
desc = _event_module.IPCEventDescriptor._init(b"\x00" * 64, True)
261+
assert desc != other
262+
assert not (desc == other) # noqa: SIM201
263+
assert other != desc
264+
assert desc.__eq__(other) is NotImplemented
265+
266+
267+
def test_ipc_event_descriptor_eq_same_value():
268+
"""Two IPCEventDescriptors with the same _reserved compare equal."""
269+
import cuda.core._event as _event_module
270+
271+
reserved = b"\x01" * 64
272+
a = _event_module.IPCEventDescriptor._init(reserved, True)
273+
b = _event_module.IPCEventDescriptor._init(reserved, True)
274+
c = _event_module.IPCEventDescriptor._init(b"\x02" * 64, True)
275+
assert a == a
276+
assert a == b
277+
assert a != c
278+
# _is_blocking_sync is intentionally ignored by __eq__.
279+
d = _event_module.IPCEventDescriptor._init(reserved, False)
280+
assert a == d
281+
282+
246283
# ============================================================================
247284
# Event Hash Tests
248285
# ============================================================================

‎cuda_core/tests/test_strided_layout.py‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

@@ -927,3 +927,41 @@ def test_to_dense(layout_spec, new_stride_order):
927927
assert dense.is_dense
928928
assert dense.required_size_in_bytes() == np_ref.size * layout.itemsize
929929
assert dense.offset_bounds == (0, np_ref.size - 1)
930+
931+
932+
@pytest.mark.parametrize(
933+
"other",
934+
[
935+
None,
936+
"string",
937+
42,
938+
3.14,
939+
(3, 4),
940+
[1, 2, 3],
941+
object(),
942+
],
943+
ids=["None", "str", "int", "float", "tuple", "list", "object"],
944+
)
945+
def test_eq_returns_not_implemented_for_other_types(other):
946+
"""_StridedLayout.__eq__ returns False (via NotImplemented) for unrelated types.
947+
948+
Regression test for https://github.com/NVIDIA/cuda-python/issues/2050: comparing
949+
a _StridedLayout to objects of unrelated types must not raise AttributeError /
950+
TypeError; it must follow Python's rich-comparison protocol and yield False.
951+
"""
952+
layout = _StridedLayout.dense((5, 3, 7), 4)
953+
assert layout != other
954+
assert not (layout == other) # noqa: SIM201
955+
assert other != layout
956+
# Direct check of the protocol contract.
957+
assert layout.__eq__(other) is NotImplemented
958+
959+
960+
def test_eq_reflexive_and_value_equality():
961+
"""_StridedLayout equality is reflexive and compares by value, not identity."""
962+
a = _StridedLayout.dense((5, 3, 7), 4)
963+
b = _StridedLayout.dense((5, 3, 7), 4)
964+
c = _StridedLayout.dense((5, 3, 8), 4)
965+
assert a == a
966+
assert a == b
967+
assert a != c

0 commit comments

Comments
 (0)