1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! 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>,
}