Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10916,7 +10916,6 @@ class TypeIterationTests(BaseTestCase):
Annotated[T, ''],
)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_cannot_iterate(self):
expected_error_regex = "object is not iterable"
for test_type in self._UNITERABLE_TYPES:
Expand Down
26 changes: 25 additions & 1 deletion crates/vm/src/types/slot.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::common::lock::{
PyMappedRwLockReadGuard, PyMappedRwLockWriteGuard, PyRwLockReadGuard, PyRwLockWriteGuard,
};
use crate::vm::PyMethod;
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
builtins::{PyInt, PyStr, PyStrInterned, PyStrRef, PyType, PyTypeRef},
Expand Down Expand Up @@ -534,7 +535,30 @@ pub(crate) fn richcompare_wrapper(
}

fn iter_wrapper(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
vm.call_special_method(&zelf, identifier!(vm, __iter__), ())
fn method_is_none(method: &PyMethod, vm: &VirtualMachine) -> bool {
match method {
PyMethod::Attribute(meth) => vm.is_none(meth),
_ => false,
}
}

//
// look-up '__iter__' method
//
let method_ident = identifier!(vm, __iter__);
let method = vm
.get_special_method(&zelf, method_ident)?
.ok_or_else(|| vm.new_attribute_error(method_ident.as_str().to_owned()))?;
Comment on lines +548 to +551
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify what error CPython raises when __iter__ is dynamically deleted after being set
# Also check how get_special_method works and what it returns for None vs missing

echo "=== Check get_special_method implementation ==="
ast-grep --pattern 'fn get_special_method($$$) {
  $$$
}'

echo ""
echo "=== Look for get_special_method definition ==="
rg -n 'fn get_special_method' crates/vm/src/ -A 20 | head -50

Repository: RustPython/RustPython

Length of output: 1467


🏁 Script executed:

#!/bin/bash
echo "=== Check iter_wrapper and surrounding context in slot.rs ==="
rg -n 'iter_wrapper\|__iter__' crates/vm/src/types/slot.rs -B 5 -A 10

Repository: RustPython/RustPython

Length of output: 126


🏁 Script executed:

#!/bin/bash
echo "=== Check how other slot wrappers handle missing methods ==="
rg -n 'new_attribute_error|new_type_error' crates/vm/src/types/slot.rs -B 3 -A 1 | head -100

Repository: RustPython/RustPython

Length of output: 4724


🏁 Script executed:

#!/bin/bash
echo "=== Check if there are references to 'not iterable' error messages ==="
rg -n 'not iterable' crates/vm/src/ -B 2 -A 2

Repository: RustPython/RustPython

Length of output: 1234


🏁 Script executed:

#!/bin/bash
echo "=== Check get_special_method behavior in the codebase ==="
rg -n 'get_special_method.*ok_or' crates/vm/src/ -B 3 -A 3 | head -60

Repository: RustPython/RustPython

Length of output: 122


Change error type from AttributeError to TypeError when __iter__ is missing.

The code at line 551 currently raises AttributeError when get_special_method returns None (i.e., when __iter__ is not found). This is inconsistent with CPython, which raises TypeError: '<ClassName>' object is not iterable. This error type also aligns with how other slot wrappers in the codebase handle missing methods—len_wrapper, repr_wrapper, str_wrapper, and others all use new_type_error() for similar cases.

The method_is_none() check at line 557 handles the case where __iter__ exists but returns None, which should also raise the same TypeError (already correct).

Proposed fix
     let method_ident = identifier!(vm, __iter__);
     let method = vm
         .get_special_method(&zelf, method_ident)?
-        .ok_or_else(|| vm.new_attribute_error(method_ident.as_str().to_owned()))?;
+        .ok_or_else(|| vm.new_type_error(format!("'{}' object is not iterable", zelf.class().name())))?;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let method_ident = identifier!(vm, __iter__);
let method = vm
.get_special_method(&zelf, method_ident)?
.ok_or_else(|| vm.new_attribute_error(method_ident.as_str().to_owned()))?;
let method_ident = identifier!(vm, __iter__);
let method = vm
.get_special_method(&zelf, method_ident)?
.ok_or_else(|| vm.new_type_error(format!("'{}' object is not iterable", zelf.class().name())))?;
🤖 Prompt for AI Agents
In `@crates/vm/src/types/slot.rs` around lines 548 - 551, Replace the
AttributeError raised when __iter__ is missing with a TypeError: in the block
that calls vm.get_special_method(&zelf, identifier!(vm, __iter__)), change the
ok_or_else(...) from vm.new_attribute_error(...) to vm.new_type_error(...) and
construct the message as "'<ClassName>' object is not iterable" using the
object's class name (e.g., format!("'{}' object is not iterable",
zelf.class().name())). Keep the existing method_is_none() TypeError behavior
unchanged.


//
// setting '__iter__' method to 'None' value is used
// to mark non-iterable objects, raise TypeError
//
if method_is_none(&method, vm) {
return Err(vm.new_type_error(format!("'{}' object is not iterable", zelf.class().name())));
}

method.invoke((), vm)
}

fn bool_wrapper(num: PyNumber<'_>, vm: &VirtualMachine) -> PyResult<bool> {
Expand Down
Loading