From 04ffa3891c3b45a9edc9bce397a817508592fec5 Mon Sep 17 00:00:00 2001 From: Changjoon Date: Wed, 29 Apr 2026 18:29:31 +0900 Subject: [PATCH] Match CPython's "argument of type ... is not a container or iterable" wording (#7714) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- Lib/test/test_contains.py | 1 - crates/vm/src/protocol/sequence.rs | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_contains.py b/Lib/test/test_contains.py index 7339f16ac..259c954aa 100644 --- a/Lib/test/test_contains.py +++ b/Lib/test/test_contains.py @@ -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) diff --git a/crates/vm/src/protocol/sequence.rs b/crates/vm/src/protocol/sequence.rs index cee46a290..b142e6300 100644 --- a/crates/vm/src/protocol/sequence.rs +++ b/crates/vm/src/protocol/sequence.rs @@ -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::(vm)?; for elem in iter {