diff --git a/.cspell.dict/rust-more.txt b/.cspell.dict/rust-more.txt index af20aef568d..6c3a9cb9b91 100644 --- a/.cspell.dict/rust-more.txt +++ b/.cspell.dict/rust-more.txt @@ -51,6 +51,8 @@ muldiv nanos nonoverlapping objclass +oelements +olen peekable pemfile powc @@ -91,3 +93,5 @@ widestring winapi winresource winsock +zelements +zlen diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index e320a2008a8..fd00b5e1479 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -228,7 +228,6 @@ class L(list): pass with self.assertRaises(TypeError): (3,) + L([1,2]) - @unittest.skip("TODO: RUSTPYTHON; hang") def test_equal_operator_modifying_operand(self): # test fix for seg fault reported in bpo-38588 part 2. class X: @@ -254,7 +253,7 @@ def __eq__(self, other): list4 = [1] self.assertFalse(list3 == list4) - @unittest.skip("TODO: RUSTPYTHON; hang") + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: TypeError not raised def test_lt_operator_modifying_operand(self): # See gh-120298 class evil: diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index c13dea57169..fda7ba3573b 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -13,7 +13,6 @@ use crate::{ class::PyClassImpl, convert::ToPyObject, function::{ArgSize, FuncArgs, OptionalArg, PyComparisonValue}, - iter::PyExactSizeIterator, protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods}, recursion::ReprGuard, sequence::{MutObjectSequenceOp, OptionalRangeArgs, SequenceExt, SequenceMutExt}, @@ -547,11 +546,77 @@ impl Comparable for PyList { return Ok(res.into()); } let other = class_or_notimplemented!(Self, other); - let a = &*zelf.borrow_vec(); - let b = &*other.borrow_vec(); - a.iter() - .richcompare(b.iter(), op, vm) - .map(PyComparisonValue::Implemented) + + let mut zlen = zelf.__len__(); + let mut olen = other.__len__(); + if matches!(op, PyComparisonOp::Eq | PyComparisonOp::Ne) && (zlen != olen) { + // Shortcut: if the lengths differ, the lists differ + return Ok(PyComparisonValue::Implemented(matches!( + op, + PyComparisonOp::Ne + ))); + } + + let mut zelements = zelf.borrow_vec().to_vec(); + let mut oelements = other.borrow_vec().to_vec(); + + // Search for the first index where items are different. + let mut i = 0; + while i < zlen && i < olen { + if zelements.len() != zlen { + // Comparison mutated the list; refetch it. + zelements = zelf.borrow_vec().to_vec(); + } + + if oelements.len() != olen { + // Comparison mutated the list; refetch it. + oelements = other.borrow_vec().to_vec(); + } + + let zitem = &zelements[i]; + let oitem = &oelements[i]; + + if !vm.bool_eq(zitem, oitem)? { + break; + } + + // Refetch list sizes as calling the comparison may mutate the list. + zlen = zelf.__len__(); + olen = other.__len__(); + + i += 1; + } + + zlen = zelf.__len__(); + olen = other.__len__(); + if i >= zlen || i >= olen { + // No more items to compare -- compare sizes + let res = match op { + PyComparisonOp::Eq => zlen == olen, + PyComparisonOp::Ne => zlen != olen, + PyComparisonOp::Lt => zlen < olen, + PyComparisonOp::Le => zlen <= olen, + PyComparisonOp::Gt => zlen > olen, + PyComparisonOp::Ge => zlen >= olen, + }; + + return Ok(PyComparisonValue::Implemented(res)); + }; + + // We have an item that differs -- shortcuts for EQ/NE. + match op { + PyComparisonOp::Eq => return Ok(PyComparisonValue::Implemented(false)), + PyComparisonOp::Ne => return Ok(PyComparisonValue::Implemented(true)), + _ => {} + } + + // Compare the final item again using the proper operator. + zelements = zelf.borrow_vec().to_vec(); + oelements = other.borrow_vec().to_vec(); + let zitem = &zelements[i]; + let oitem = &oelements[i]; + let res = vm.identical_or_equal(zitem, oitem)?; + Ok(PyComparisonValue::Implemented(res)) } }