Match CPython's "argument of type ... is not a container or iterable" wording (#7714)

CPython's PySequence_Contains (Objects/abstract.c::_PySequence_IterSearch)
catches the TypeError from PyObject_GetIter when neither __contains__ nor
__iter__ is available, and re-raises with membership-test wording.

RustPython's PySequence::contains propagated the get_iter error verbatim,
so '1 in obj' produced 'X' object is not iterable — a literal but less
specific message.

Wrap the get_iter call in map_err: when the failure is a TypeError,
re-raise with byte-identical CPython wording. Other exception types pass
through unchanged.

Unmasks test_contains.TestContains.test_common_tests.
This commit is contained in:
Changjoon authored and GitHub committed 2026-04-29 18:29:31 +09:00
1 parent ba2b619c0c
commit 04ffa3891c
2 files changed
+14 -3

No files matched your search

-1
View File
@@ -16,7 +16,6 @@ class seq(base_set):
return [self.el][n]
class TestContains(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message
def test_common_tests(self):
a = base_set(1)
b = myset(1)
+14 -2
View File
@@ -1,5 +1,5 @@
use crate::{
PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
AsObject, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::{PyList, PyListRef, PySlice, PyTuple, PyTupleRef},
convert::ToPyObject,
function::PyArithmeticValue,
@@ -398,7 +398,19 @@ impl PySequence<'_> {
return f(self, target, vm);
}
let iter = self.obj.to_owned().get_iter(vm)?;
// CPython parity: when neither __contains__ nor __iter__ is available,
// `PySequence_Contains` rewords the get_iter TypeError into the
// membership-test wording. Other exception types propagate unchanged.
let iter = self.obj.to_owned().get_iter(vm).map_err(|e| {
if e.fast_isinstance(vm.ctx.exceptions.type_error) {
vm.new_type_error(format!(
"argument of type '{}' is not a container or iterable",
self.obj.class().name()
))
} else {
e
}
})?;
let iter = iter.iter::<PyObjectRef>(vm)?;
for elem in iter {