Skip to content

Commit bba2b7f

Browse files
committed
Implement cell comparison and repr
The cell type implemented neither Comparable nor Representable, so object's address-based defaults leaked through: cell(1) == cell(1) was False, ordering raised TypeError, and repr rendered <cell object at 0x...> rather than <cell at 0x...: int object at 0x...>. Compare cells by contents, with empty cells ordering before everything else, and render CPython's repr for both the filled and empty cases. Mark the type unhashable. Content-based equality combined with the inherited identity hash would break the hash/eq contract; CPython gets this implicitly, because defining tp_richcompare suppresses tp_hash inheritance. Reference: CPython Objects/cellobject.c, cell_richcompare and cell_repr. Assisted-by: Claude Code:claude-opus-5
1 parent 1c7759c commit bba2b7f

3 files changed

Lines changed: 33 additions & 4 deletions

File tree

Lib/test/test_funcattrs.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,6 @@ def f():
432432

433433

434434
class CellTest(unittest.TestCase):
435-
@unittest.expectedFailure # TODO: RUSTPYTHON
436435
def test_comparison(self):
437436
# These tests are here simply to exercise the comparison code;
438437
# their presence should not be interpreted as providing any

Lib/test/test_reprlib.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,6 @@ def test_nesting(self):
237237
eq(r([[[[[[{}]]]]]]), "[[[[[[{}]]]]]]")
238238
eq(r([[[[[[[{}]]]]]]]), "[[[[[[[...]]]]]]]")
239239

240-
@unittest.expectedFailure # TODO: RUSTPYTHON
241240
def test_cell(self):
242241
def get_cell():
243242
x = 42

crates/vm/src/builtins/function.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,7 +1507,7 @@ impl Representable for PyBoundMethod {
15071507
}
15081508
}
15091509

1510-
#[pyclass(module = false, name = "cell", traverse)]
1510+
#[pyclass(module = false, name = "cell", unhashable = true, traverse)]
15111511
#[derive(Debug, Default)]
15121512
pub(crate) struct PyCell {
15131513
contents: PyMutex<Option<PyObjectRef>>,
@@ -1530,7 +1530,7 @@ impl Constructor for PyCell {
15301530
}
15311531
}
15321532

1533-
#[pyclass(with(Constructor))]
1533+
#[pyclass(with(Constructor, Comparable, Representable))]
15341534
impl PyCell {
15351535
pub(crate) const fn new(contents: Option<PyObjectRef>) -> Self {
15361536
Self {
@@ -1561,6 +1561,37 @@ impl PyCell {
15611561
}
15621562
}
15631563

1564+
impl Comparable for PyCell {
1565+
fn cmp(
1566+
zelf: &Py<Self>,
1567+
other: &PyObject,
1568+
op: PyComparisonOp,
1569+
vm: &VirtualMachine,
1570+
) -> PyResult<PyComparisonValue> {
1571+
let other = class_or_notimplemented!(Self, other);
1572+
// compare cells by contents; empty cells come before anything else
1573+
match (zelf.get(), other.get()) {
1574+
(Some(a), Some(b)) => a.rich_compare(b, op, vm)?.is_true(vm).map(Into::into),
1575+
(a, b) => Ok(op.eval_ord(b.is_none().cmp(&a.is_none())).into()),
1576+
}
1577+
}
1578+
}
1579+
1580+
impl Representable for PyCell {
1581+
#[inline]
1582+
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
1583+
let id = zelf.get_id();
1584+
Ok(match zelf.get() {
1585+
Some(value) => format!(
1586+
"<cell at {id:#x}: {} object at {:#x}>",
1587+
value.class().slot_name(),
1588+
value.get_id()
1589+
),
1590+
None => format!("<cell at {id:#x}: empty>"),
1591+
})
1592+
}
1593+
}
1594+
15641595
/// Vectorcall implementation for PyFunction (PEP 590).
15651596
/// Takes owned args to avoid cloning when filling fastlocals.
15661597
pub(crate) fn vectorcall_function(

0 commit comments

Comments
 (0)