monty 0.0.21

A sandboxed, snapshotable Python interpreter written in Rust.
Documentation
//! Implementation of the map() builtin function.

use std::{iter, mem};

use crate::{
    args::{ArgValues, FromArgs, KwargsValues},
    bytecode::VM,
    defer_drop, defer_drop_mut,
    exception_private::{ExcType, ExcTypeExt, RunResult},
    heap::{DropWithContext, HeapData},
    types::{List, iter::checked_preallocation_hint},
    value::Value,
};

/// Implementation of the map() builtin function.
///
/// Applies a function to every item of one or more iterables and returns a list of results.
/// With multiple iterables, stops when the shortest iterable is exhausted.
///
/// Note: In Python this returns an iterator, but we return a list for simplicity.
/// Note: The `strict=` parameter is not yet supported.
///
/// Examples:
/// ```python
/// map(abs, [-1, 0, 1, 2])           # [1, 0, 1, 2]
/// map(pow, [2, 3], [3, 2])          # [8, 9]
/// map(str, [1, 2, 3])               # ['1', '2', '3']
/// ```
pub fn builtin_map(vm: &mut VM<'_>, args: ArgValues) -> RunResult<Value> {
    // CPython's map() uses a bespoke arity message
    // (`map() must have at least two arguments.`) rather than the generic
    // "missing N required positional arguments" wording the macro would
    // otherwise produce. Pre-check before delegating to MapArgs so we
    // match byte-for-byte; cleanup is handled by the early-return drop.
    //
    // Only fire the arity check when no kwargs are present — otherwise
    // `map(abs, bogus=1)` would report the arity error when CPython reports
    // the unknown-kwarg error. Delegating to the macro produces the
    // correct `got an unexpected keyword argument` message instead.
    let kwargs_empty = match &args {
        ArgValues::Kwargs(kwargs) => kwargs.is_empty(),
        ArgValues::ArgsKargs { kwargs, .. } => kwargs.is_empty(),
        _ => true,
    };
    if args.count() < 2 && kwargs_empty {
        args.drop_with(vm.heap);
        return Err(ExcType::type_error_map_arity());
    }
    let MapArgs {
        function,
        first_iterable,
        extra_iterables,
    } = MapArgs::from_args(args, vm)?;
    defer_drop!(function, vm);
    defer_drop_mut!(extra_iterables, vm);

    let first_iter = first_iterable.into_py_iter(vm)?;
    defer_drop!(first_iter, vm);
    let mut first_iter = first_iter.read(vm);

    let extra_iterators: Vec<Value> = Vec::with_capacity(extra_iterables.len());
    defer_drop_mut!(extra_iterators, vm);

    for iterable in extra_iterables.drain(..) {
        extra_iterators.push(iterable.into_py_iter(vm)?);
    }

    // Validate and clamp the iterator's hint before reserving native memory.
    let hint = first_iter.iter_size_hint(vm);
    let capacity = checked_preallocation_hint(hint, mem::size_of::<Value>(), vm.heap.tracker())?;
    let mut out = Vec::with_capacity(capacity);

    // map function over iterables until the shortest iter is exhausted
    match extra_iterators.as_mut_slice() {
        // map(f, iter)
        [] => {
            while let Some(item) = first_iter.py_next(vm)? {
                let args = ArgValues::One(item);
                out.push(vm.evaluate_function("map()", function, args)?);
            }
        }
        // map(f, iter1, iter2)
        [single] => {
            while let Some(arg1) = first_iter.py_next(vm)? {
                let Some(arg2) = single.py_next(vm)? else {
                    arg1.drop_with(vm);
                    break;
                };
                let args = ArgValues::Two(arg1, arg2);
                out.push(vm.evaluate_function("map()", function, args)?);
            }
        }
        // map(f, iter1, iter2, *iterables)
        multiple => 'outer: loop {
            let mut items = Vec::with_capacity(1 + multiple.len());

            for result in iter::once(first_iter.py_next(vm)).chain(multiple.iter_mut().map(|iter| iter.py_next(vm))) {
                if let Some(item) = result? {
                    items.push(item);
                } else {
                    items.drop_with(vm);
                    break 'outer;
                }
            }

            let args = ArgValues::ArgsKargs {
                args: items,
                kwargs: KwargsValues::Empty,
            };

            out.push(vm.evaluate_function("map()", function, args)?);
        },
    }

    let heap_id = vm.heap.allocate(HeapData::List(List::new(out)));
    Ok(Value::Ref(heap_id))
}

/// Argument shape for `map(function, iterable, *iterables)`.
///
/// `function` and the first `iterable` are required; any further iterables
/// are collected by `extra_iterables`. `map` doesn't accept kwargs, so the
/// macro's default unknown-kwarg error path is exactly what we want.
#[derive(FromArgs)]
#[from_args(name = "map")]
struct MapArgs {
    #[from_args(pos_only)]
    function: Value,
    #[from_args(pos_only)]
    first_iterable: Value,
    #[from_args(varargs)]
    extra_iterables: Vec<Value>,
}