Skip to content
Open
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
6 changes: 4 additions & 2 deletions crates/vm/src/builtins/enumerate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ impl PyPayload for PyEnumerate {

#[derive(FromArgs)]
pub struct EnumerateArgs {
// Bound as an object so the other arguments are checked before `iter()` runs, as in CPython.
#[pyarg(any)]
iterable: PyIter,
iterable: PyObjectRef,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Defer start conversion until binding validates the call

After only iterable is made raw, a malformed call such as enumerate(BadIter(), RaisingIndex(), extra) reaches ArgIndex::try_from_object for start before FuncArgs::bind_for notices the extra positional argument. It therefore runs and may propagate RaisingIndex.__index__ instead of raising the argument-count TypeError; CPython rejects this call before invoking either __index__ or __iter__. Binding start as an object too, then converting it in py_new immediately before get_iter, preserves the intended valid-call order without this side effect.

Useful? React with 👍 / 👎.

#[pyarg(any, default = 0)]
start: ArgIndex,
}
Expand All @@ -66,8 +67,9 @@ impl Constructor for PyEnumerate {
fn py_new(
_cls: &Py<PyType>,
Self::Args { iterable, start }: Self::Args,
_vm: &VirtualMachine,
vm: &VirtualMachine,
) -> PyResult<Self> {
let iterable = iterable.get_iter(vm)?;
let start = start.into_int_ref();
let counter = match start.as_bigint().to_usize() {
Some(n) => Counter::Small(n),
Expand Down
10 changes: 10 additions & 0 deletions extra_tests/snippets/builtin_enumerate.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from testutils import TestFailingIter, assert_raises

assert list(enumerate(["a", "b", "c"])) == [(0, "a"), (1, "b"), (2, "c")]

assert type(enumerate([])) == enumerate
Expand Down Expand Up @@ -25,3 +27,11 @@ def __iter__(self):
it = enumerate(Counter())
assert next(it) == (0, 1)
assert next(it) == (1, 2)


# start and the argument count are checked before iter() is called
assert_raises(TypeError, enumerate, TestFailingIter(), {})
assert_raises(TypeError, enumerate, TestFailingIter(), 0, 0)
assert_raises(TypeError, enumerate, TestFailingIter(), foo=1)
assert_raises(TypeError, enumerate, iterable=TestFailingIter(), start={})
assert_raises(RuntimeError, enumerate, TestFailingIter(), 0)
Loading