Skip to main content

rustpython_vm/function/
argument.rs

1use crate::{
2    AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine,
3    builtins::{PyBaseExceptionRef, PyTupleRef, PyTypeRef},
4    convert::ToPyObject,
5    object::{Traverse, TraverseFn},
6};
7use core::ops::RangeInclusive;
8use indexmap::IndexMap;
9use itertools::Itertools;
10
11pub trait IntoFuncArgs: Sized {
12    fn into_args(self, vm: &VirtualMachine) -> FuncArgs;
13    fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs {
14        let mut args = self.into_args(vm);
15        args.prepend_arg(obj);
16        args
17    }
18}
19
20impl<T> IntoFuncArgs for T
21where
22    T: Into<FuncArgs>,
23{
24    fn into_args(self, _vm: &VirtualMachine) -> FuncArgs {
25        self.into()
26    }
27}
28
29// A tuple of values that each implement `ToPyObject` represents a sequence of
30// arguments that can be bound and passed to a built-in function.
31macro_rules! into_func_args_from_tuple {
32    ($(($n:tt, $T:ident)),*) => {
33        impl<$($T,)*> IntoFuncArgs for ($($T,)*)
34        where
35            $($T: ToPyObject,)*
36        {
37            #[inline]
38            fn into_args(self, vm: &VirtualMachine) -> FuncArgs {
39                let ($($n,)*) = self;
40                PosArgs::new(vec![$($n.to_pyobject(vm),)*]).into()
41            }
42
43            #[inline]
44            fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs {
45                let ($($n,)*) = self;
46                PosArgs::new(vec![obj, $($n.to_pyobject(vm),)*]).into()
47            }
48        }
49    };
50}
51
52into_func_args_from_tuple!((v1, T1));
53into_func_args_from_tuple!((v1, T1), (v2, T2));
54into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3));
55into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4));
56into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5));
57into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5), (v6, T6));
58// We currently allows only 6 unnamed positional arguments.
59// Please use `#[derive(FromArgs)]` and a struct for more complex argument parsing.
60// The number of limitation came from:
61// https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
62
63/// The `FuncArgs` struct is one of the most used structs then creating
64/// a rust function that can be called from python. It holds both positional
65/// arguments, as well as keyword arguments passed to the function.
66#[derive(Debug, Default, Clone, Traverse)]
67pub struct FuncArgs {
68    pub args: Vec<PyObjectRef>,
69    // sorted map, according to https://www.python.org/dev/peps/pep-0468/
70    pub kwargs: IndexMap<String, PyObjectRef>,
71}
72
73unsafe impl Traverse for IndexMap<String, PyObjectRef> {
74    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
75        self.values().for_each(|v| v.traverse(tracer_fn));
76    }
77}
78
79/// Conversion from vector of python objects to function arguments.
80impl<A> From<A> for FuncArgs
81where
82    A: Into<PosArgs>,
83{
84    fn from(args: A) -> Self {
85        Self {
86            args: args.into().into_vec(),
87            kwargs: IndexMap::new(),
88        }
89    }
90}
91
92impl From<KwArgs> for FuncArgs {
93    fn from(kwargs: KwArgs) -> Self {
94        Self {
95            args: Vec::new(),
96            kwargs: kwargs.0,
97        }
98    }
99}
100
101impl FromArgs for FuncArgs {
102    fn from_args(_vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
103        Ok(core::mem::take(args))
104    }
105}
106
107impl FuncArgs {
108    pub fn new<A, K>(args: A, kwargs: K) -> Self
109    where
110        A: Into<PosArgs>,
111        K: Into<KwArgs>,
112    {
113        let PosArgs(args) = args.into();
114        let KwArgs(kwargs) = kwargs.into();
115        Self { args, kwargs }
116    }
117
118    pub fn with_kwargs_names<A, KW>(mut args: A, kwarg_names: KW) -> Self
119    where
120        A: ExactSizeIterator<Item = PyObjectRef>,
121        KW: ExactSizeIterator<Item = String>,
122    {
123        // last `kwarg_names.len()` elements of args in order of appearance in the call signature
124        let total_argc = args.len();
125        let kwarg_count = kwarg_names.len();
126        let pos_arg_count = total_argc - kwarg_count;
127
128        let pos_args = args.by_ref().take(pos_arg_count).collect();
129
130        let kwargs = kwarg_names.zip_eq(args).collect::<IndexMap<_, _>>();
131
132        Self {
133            args: pos_args,
134            kwargs,
135        }
136    }
137
138    /// Create FuncArgs from a vectorcall-style argument slice (PEP 590).
139    /// `args[..nargs]` are positional, and if `kwnames` is provided,
140    /// the last `kwnames.len()` entries in `args[nargs..]` are keyword values.
141    /// Convert borrowed vectorcall args to FuncArgs (clones all values).
142    pub fn from_vectorcall(
143        args: &[PyObjectRef],
144        nargs: usize,
145        kwnames: Option<&[PyObjectRef]>,
146    ) -> Self {
147        debug_assert!(nargs <= args.len());
148        debug_assert!(kwnames.is_none_or(|kw| nargs + kw.len() <= args.len()));
149        let pos_args = args[..nargs].to_vec();
150        let kwargs = if let Some(names) = kwnames {
151            names
152                .iter()
153                .zip(&args[nargs..nargs + names.len()])
154                .map(|(name, val)| {
155                    let key = name
156                        .downcast_ref::<crate::builtins::PyUtf8Str>()
157                        .expect("kwnames must be strings")
158                        .as_str()
159                        .to_owned();
160                    (key, val.clone())
161                })
162                .collect()
163        } else {
164            IndexMap::new()
165        };
166        Self {
167            args: pos_args,
168            kwargs,
169        }
170    }
171
172    /// Convert owned vectorcall args to FuncArgs (moves values, no clone).
173    pub fn from_vectorcall_owned(
174        mut args: Vec<PyObjectRef>,
175        nargs: usize,
176        kwnames: Option<&[PyObjectRef]>,
177    ) -> Self {
178        debug_assert!(nargs <= args.len());
179        debug_assert!(kwnames.is_none_or(|kw| nargs + kw.len() <= args.len()));
180        let kwargs = if let Some(names) = kwnames {
181            let kw_count = names.len();
182            names
183                .iter()
184                .zip(args.drain(nargs..nargs + kw_count))
185                .map(|(name, val)| {
186                    let key = name
187                        .downcast_ref::<crate::builtins::PyUtf8Str>()
188                        .expect("kwnames must be strings")
189                        .as_str()
190                        .to_owned();
191                    (key, val)
192                })
193                .collect()
194        } else {
195            IndexMap::new()
196        };
197        args.truncate(nargs);
198        Self { args, kwargs }
199    }
200
201    pub fn is_empty(&self) -> bool {
202        self.args.is_empty() && self.kwargs.is_empty()
203    }
204
205    pub fn prepend_arg(&mut self, item: PyObjectRef) {
206        self.args.reserve_exact(1);
207        self.args.insert(0, item)
208    }
209
210    pub fn shift(&mut self) -> PyObjectRef {
211        self.args.remove(0)
212    }
213
214    pub fn get_kwarg(&self, key: &str, default: PyObjectRef) -> PyObjectRef {
215        self.kwargs
216            .get(key)
217            .cloned()
218            .unwrap_or_else(|| default.clone())
219    }
220
221    pub fn get_optional_kwarg(&self, key: &str) -> Option<PyObjectRef> {
222        self.kwargs.get(key).cloned()
223    }
224
225    pub fn get_optional_kwarg_with_type(
226        &self,
227        key: &str,
228        ty: PyTypeRef,
229        vm: &VirtualMachine,
230    ) -> PyResult<Option<PyObjectRef>> {
231        match self.get_optional_kwarg(key) {
232            Some(kwarg) => {
233                if kwarg.fast_isinstance(&ty) {
234                    Ok(Some(kwarg))
235                } else {
236                    let expected_ty_name = &ty.name();
237                    let kwarg_class = kwarg.class();
238                    let actual_ty_name = &kwarg_class.name();
239                    Err(vm.new_type_error(format!(
240                        "argument of type {expected_ty_name} is required for named parameter `{key}` (got: {actual_ty_name})"
241                    )))
242                }
243            }
244            None => Ok(None),
245        }
246    }
247
248    pub fn take_positional(&mut self) -> Option<PyObjectRef> {
249        if self.args.is_empty() {
250            None
251        } else {
252            Some(self.args.remove(0))
253        }
254    }
255
256    pub fn take_positional_keyword(&mut self, name: &str) -> Option<PyObjectRef> {
257        self.take_positional().or_else(|| self.take_keyword(name))
258    }
259
260    pub fn take_keyword(&mut self, name: &str) -> Option<PyObjectRef> {
261        self.kwargs.swap_remove(name)
262    }
263
264    pub fn remaining_keywords(&mut self) -> impl Iterator<Item = (String, PyObjectRef)> + '_ {
265        self.kwargs.drain(..)
266    }
267
268    /// Binds these arguments to their respective values.
269    ///
270    /// If there is an insufficient number of arguments, there are leftover
271    /// arguments after performing the binding, or if an argument is not of
272    /// the expected type, a TypeError is raised.
273    ///
274    /// If the given `FromArgs` includes any conversions, exceptions raised
275    /// during the conversion will halt the binding and return the error.
276    pub fn bind<T: FromArgs>(mut self, vm: &VirtualMachine) -> PyResult<T> {
277        let given_args = self.args.len();
278        let bound = T::from_args(vm, &mut self)
279            .map_err(|e| e.into_exception(T::arity(), given_args, vm))?;
280
281        if !self.args.is_empty() {
282            Err(vm.new_type_error(format!(
283                "expected at most {} arguments, got {}",
284                T::arity().end(),
285                given_args,
286            )))
287        } else if let Some(err) = self.check_kwargs_empty(vm) {
288            Err(err)
289        } else {
290            Ok(bound)
291        }
292    }
293
294    pub fn check_kwargs_empty(&self, vm: &VirtualMachine) -> Option<PyBaseExceptionRef> {
295        self.kwargs
296            .keys()
297            .next()
298            .map(|k| vm.new_type_error(format!("Unexpected keyword argument {k}")))
299    }
300}
301
302/// An error encountered while binding arguments to the parameters of a Python
303/// function call.
304pub enum ArgumentError {
305    /// The call provided fewer positional arguments than the function requires.
306    TooFewArgs,
307    /// The call provided more positional arguments than the function accepts.
308    TooManyArgs,
309    /// The function doesn't accept a keyword argument with the given name.
310    InvalidKeywordArgument(String),
311    /// The function require a keyword argument with the given name, but one wasn't provided
312    RequiredKeywordArgument(String),
313    /// An exception was raised while binding arguments to the function
314    /// parameters.
315    Exception(PyBaseExceptionRef),
316}
317
318impl From<PyBaseExceptionRef> for ArgumentError {
319    fn from(ex: PyBaseExceptionRef) -> Self {
320        Self::Exception(ex)
321    }
322}
323
324impl ArgumentError {
325    fn into_exception(
326        self,
327        arity: RangeInclusive<usize>,
328        num_given: usize,
329        vm: &VirtualMachine,
330    ) -> PyBaseExceptionRef {
331        match self {
332            Self::TooFewArgs => vm.new_type_error(format!(
333                "expected at least {} arguments, got {}",
334                arity.start(),
335                num_given
336            )),
337            Self::TooManyArgs => vm.new_type_error(format!(
338                "expected at most {} arguments, got {}",
339                arity.end(),
340                num_given
341            )),
342            Self::InvalidKeywordArgument(name) => {
343                vm.new_type_error(format!("{name} is an invalid keyword argument"))
344            }
345            Self::RequiredKeywordArgument(name) => {
346                vm.new_type_error(format!("Required keyword only argument {name}"))
347            }
348            Self::Exception(ex) => ex,
349        }
350    }
351}
352
353/// Implemented by any type that can be accepted as a parameter to a built-in
354/// function.
355///
356pub trait FromArgs: Sized {
357    /// The range of positional arguments permitted by the function signature.
358    ///
359    /// Returns an empty range if not applicable.
360    fn arity() -> RangeInclusive<usize> {
361        0..=0
362    }
363
364    /// Extracts this item from the next argument(s).
365    fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError>;
366}
367
368pub trait FromArgOptional {
369    type Inner: TryFromObject;
370    fn from_inner(x: Self::Inner) -> Self;
371}
372
373impl<T: TryFromObject> FromArgOptional for OptionalArg<T> {
374    type Inner = T;
375    fn from_inner(x: T) -> Self {
376        Self::Present(x)
377    }
378}
379
380impl<T: TryFromObject> FromArgOptional for T {
381    type Inner = Self;
382    fn from_inner(x: Self) -> Self {
383        x
384    }
385}
386
387/// A map of keyword arguments to their values.
388///
389/// A built-in function with a `KwArgs` parameter is analogous to a Python
390/// function with `**kwargs`. All remaining keyword arguments are extracted
391/// (and hence the function will permit an arbitrary number of them).
392///
393/// `KwArgs` optionally accepts a generic type parameter to allow type checks
394/// or conversions of each argument.
395///
396/// Note:
397///
398/// KwArgs is only for functions that accept arbitrary keyword arguments. For
399/// functions that accept only *specific* named arguments, a rust struct with
400/// an appropriate FromArgs implementation must be created.
401#[derive(Clone)]
402pub struct KwArgs<T = PyObjectRef>(IndexMap<String, T>);
403
404unsafe impl<T> Traverse for KwArgs<T>
405where
406    T: Traverse,
407{
408    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
409        self.0.iter().map(|(_, v)| v.traverse(tracer_fn)).count();
410    }
411}
412
413impl<T> KwArgs<T> {
414    pub const fn new(map: IndexMap<String, T>) -> Self {
415        Self(map)
416    }
417
418    pub fn pop_kwarg(&mut self, name: &str) -> Option<T> {
419        self.0.swap_remove(name)
420    }
421
422    pub fn is_empty(self) -> bool {
423        self.0.is_empty()
424    }
425}
426
427impl<T> FromIterator<(String, T)> for KwArgs<T> {
428    fn from_iter<I: IntoIterator<Item = (String, T)>>(iter: I) -> Self {
429        Self(iter.into_iter().collect())
430    }
431}
432
433impl<T> Default for KwArgs<T> {
434    fn default() -> Self {
435        Self(IndexMap::new())
436    }
437}
438
439impl<T> FromArgs for KwArgs<T>
440where
441    T: TryFromObject,
442{
443    fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
444        let mut kwargs = IndexMap::new();
445        for (name, value) in args.remaining_keywords() {
446            kwargs.insert(name, value.try_into_value(vm)?);
447        }
448        Ok(Self(kwargs))
449    }
450}
451
452impl<T> IntoIterator for KwArgs<T> {
453    type Item = (String, T);
454    type IntoIter = indexmap::map::IntoIter<String, T>;
455
456    fn into_iter(self) -> Self::IntoIter {
457        self.0.into_iter()
458    }
459}
460
461/// A list of positional argument values.
462///
463/// A built-in function with a `PosArgs` parameter is analogous to a Python
464/// function with `*args`. All remaining positional arguments are extracted
465/// (and hence the function will permit an arbitrary number of them).
466///
467/// `PosArgs` optionally accepts a generic type parameter to allow type checks
468/// or conversions of each argument.
469#[derive(Clone)]
470pub struct PosArgs<T = PyObjectRef>(Vec<T>);
471
472unsafe impl<T> Traverse for PosArgs<T>
473where
474    T: Traverse,
475{
476    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
477        self.0.traverse(tracer_fn)
478    }
479}
480
481impl<T> PosArgs<T> {
482    pub const fn new(args: Vec<T>) -> Self {
483        Self(args)
484    }
485
486    pub fn into_vec(self) -> Vec<T> {
487        self.0
488    }
489
490    pub fn iter(&self) -> core::slice::Iter<'_, T> {
491        self.0.iter()
492    }
493}
494
495impl<T> From<Vec<T>> for PosArgs<T> {
496    fn from(v: Vec<T>) -> Self {
497        Self(v)
498    }
499}
500
501impl From<()> for PosArgs<PyObjectRef> {
502    fn from(_args: ()) -> Self {
503        Self(Vec::new())
504    }
505}
506
507impl<T> AsRef<[T]> for PosArgs<T> {
508    fn as_ref(&self) -> &[T] {
509        &self.0
510    }
511}
512
513impl<T: PyPayload> PosArgs<PyRef<T>> {
514    pub fn into_tuple(self, vm: &VirtualMachine) -> PyTupleRef {
515        vm.ctx
516            .new_tuple(self.0.into_iter().map(Into::into).collect())
517    }
518}
519
520impl<T> FromArgs for PosArgs<T>
521where
522    T: TryFromObject,
523{
524    fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
525        let mut varargs = Vec::new();
526        while let Some(value) = args.take_positional() {
527            varargs.push(value.try_into_value(vm)?);
528        }
529        Ok(Self(varargs))
530    }
531}
532
533impl<T> IntoIterator for PosArgs<T> {
534    type Item = T;
535    type IntoIter = alloc::vec::IntoIter<T>;
536
537    fn into_iter(self) -> Self::IntoIter {
538        self.0.into_iter()
539    }
540}
541
542impl<T> FromArgs for T
543where
544    T: TryFromObject,
545{
546    fn arity() -> RangeInclusive<usize> {
547        1..=1
548    }
549
550    fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
551        let value = args.take_positional().ok_or(ArgumentError::TooFewArgs)?;
552        Ok(value.try_into_value(vm)?)
553    }
554}
555
556/// An argument that may or may not be provided by the caller.
557///
558/// This style of argument is not possible in pure Python.
559#[derive(Debug, result_like::OptionLike, is_macro::Is)]
560pub enum OptionalArg<T = PyObjectRef> {
561    Present(T),
562    Missing,
563}
564
565unsafe impl<T> Traverse for OptionalArg<T>
566where
567    T: Traverse,
568{
569    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
570        match self {
571            Self::Present(o) => o.traverse(tracer_fn),
572            Self::Missing => (),
573        }
574    }
575}
576
577impl OptionalArg<PyObjectRef> {
578    pub fn unwrap_or_none(self, vm: &VirtualMachine) -> PyObjectRef {
579        self.unwrap_or_else(|| vm.ctx.none())
580    }
581}
582
583pub type OptionalOption<T = PyObjectRef> = OptionalArg<Option<T>>;
584
585impl<T> OptionalOption<T> {
586    #[inline]
587    pub fn flatten(self) -> Option<T> {
588        self.into_option().flatten()
589    }
590}
591
592impl<T> FromArgs for OptionalArg<T>
593where
594    T: TryFromObject,
595{
596    fn arity() -> RangeInclusive<usize> {
597        0..=1
598    }
599
600    fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
601        let r = if let Some(value) = args.take_positional() {
602            Self::Present(value.try_into_value(vm)?)
603        } else {
604            Self::Missing
605        };
606        Ok(r)
607    }
608}
609
610// For functions that accept no arguments. Implemented explicitly instead of via
611// macro below to avoid unused warnings.
612impl FromArgs for () {
613    fn from_args(_vm: &VirtualMachine, _args: &mut FuncArgs) -> Result<Self, ArgumentError> {
614        Ok(())
615    }
616}
617
618// A tuple of types that each implement `FromArgs` represents a sequence of
619// arguments that can be bound and passed to a built-in function.
620//
621// Technically, a tuple can contain tuples, which can contain tuples, and so on,
622// so this actually represents a tree of values to be bound from arguments, but
623// in practice this is only used for the top-level parameters.
624macro_rules! tuple_from_py_func_args {
625    ($($T:ident),+) => {
626        impl<$($T),+> FromArgs for ($($T,)+)
627        where
628            $($T: FromArgs),+
629        {
630            fn arity() -> RangeInclusive<usize> {
631                let mut min = 0;
632                let mut max = 0;
633                $(
634                    let (start, end) = $T::arity().into_inner();
635                    min += start;
636                    max += end;
637                )+
638                min..=max
639            }
640
641            fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
642                Ok(($($T::from_args(vm, args)?,)+))
643            }
644        }
645    };
646}
647
648// Implement `FromArgs` for up to 7-tuples, allowing built-in functions to bind
649// up to 7 top-level parameters (note that `PosArgs`, `KwArgs`, nested tuples, etc.
650// count as 1, so this should actually be more than enough).
651tuple_from_py_func_args!(A);
652tuple_from_py_func_args!(A, B);
653tuple_from_py_func_args!(A, B, C);
654tuple_from_py_func_args!(A, B, C, D);
655tuple_from_py_func_args!(A, B, C, D, E);
656tuple_from_py_func_args!(A, B, C, D, E, F);
657tuple_from_py_func_args!(A, B, C, D, E, F, G);
658tuple_from_py_func_args!(A, B, C, D, E, F, G, H);