diff --git a/vm/src/function.rs b/vm/src/function.rs index 266f7feaf..9ff8d0514 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -1,560 +1,24 @@ +mod argument; mod arithmetic; mod buffer; mod number; mod protocol; use crate::{ - builtins::{PyBaseExceptionRef, PyTupleRef, PyTypeRef}, - convert::{ToPyObject, ToPyResult}, - pyobject::PyThreadingConstraint, - AsObject, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, VirtualMachine, + builtins::PyTupleRef, convert::ToPyResult, pyobject::PyThreadingConstraint, PyObject, + PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, VirtualMachine, }; -use indexmap::IndexMap; -use itertools::Itertools; -use std::{marker::PhantomData, ops::RangeInclusive}; +use std::marker::PhantomData; +pub use argument::{ + ArgumentError, FromArgOptional, FromArgs, FuncArgs, IntoFuncArgs, KwArgs, OptionalArg, + OptionalOption, PosArgs, +}; pub use arithmetic::{PyArithmeticValue, PyComparisonValue}; pub use buffer::{ArgAsciiBuffer, ArgBytesLike, ArgMemoryBuffer, ArgStrOrBytesLike}; pub use number::{ArgIntoBool, ArgIntoComplex, ArgIntoFloat}; pub use protocol::{ArgCallable, ArgIterable, ArgMapping, ArgSequence}; -pub trait IntoFuncArgs: Sized { - fn into_args(self, vm: &VirtualMachine) -> FuncArgs; - fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs { - let mut args = self.into_args(vm); - args.prepend_arg(obj); - args - } -} - -impl IntoFuncArgs for T -where - T: Into, -{ - fn into_args(self, _vm: &VirtualMachine) -> FuncArgs { - self.into() - } -} - -// A tuple of values that each implement `ToPyObject` represents a sequence of -// arguments that can be bound and passed to a built-in function. -macro_rules! into_func_args_from_tuple { - ($(($n:tt, $T:ident)),*) => { - impl<$($T,)*> IntoFuncArgs for ($($T,)*) - where - $($T: ToPyObject,)* - { - #[inline] - fn into_args(self, vm: &VirtualMachine) -> FuncArgs { - let ($($n,)*) = self; - PosArgs::new(vec![$($n.to_pyobject(vm),)*]).into() - } - - #[inline] - fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs { - let ($($n,)*) = self; - PosArgs::new(vec![obj, $($n.to_pyobject(vm),)*]).into() - } - } - }; -} - -into_func_args_from_tuple!((v1, T1)); -into_func_args_from_tuple!((v1, T1), (v2, T2)); -into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3)); -into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4)); -into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5)); - -/// The `FuncArgs` struct is one of the most used structs then creating -/// a rust function that can be called from python. It holds both positional -/// arguments, as well as keyword arguments passed to the function. -#[derive(Debug, Default, Clone)] -pub struct FuncArgs { - pub args: Vec, - // sorted map, according to https://www.python.org/dev/peps/pep-0468/ - pub kwargs: IndexMap, -} - -/// Conversion from vector of python objects to function arguments. -impl From for FuncArgs -where - A: Into, -{ - fn from(args: A) -> Self { - FuncArgs { - args: args.into().into_vec(), - kwargs: IndexMap::new(), - } - } -} - -impl From for FuncArgs { - fn from(kwargs: KwArgs) -> Self { - FuncArgs { - args: Vec::new(), - kwargs: kwargs.0, - } - } -} - -impl FromArgs for FuncArgs { - fn from_args(_vm: &VirtualMachine, args: &mut FuncArgs) -> Result { - Ok(std::mem::take(args)) - } -} - -impl FuncArgs { - pub fn new(args: A, kwargs: K) -> Self - where - A: Into, - K: Into, - { - let PosArgs(args) = args.into(); - let KwArgs(kwargs) = kwargs.into(); - Self { args, kwargs } - } - - pub fn with_kwargs_names(mut args: A, kwarg_names: KW) -> Self - where - A: ExactSizeIterator, - KW: ExactSizeIterator, - { - // last `kwarg_names.len()` elements of args in order of appearance in the call signature - let total_argc = args.len(); - let kwargc = kwarg_names.len(); - let posargc = total_argc - kwargc; - - let posargs = args.by_ref().take(posargc).collect(); - - let kwargs = kwarg_names.zip_eq(args).collect::>(); - - FuncArgs { - args: posargs, - kwargs, - } - } - - pub fn prepend_arg(&mut self, item: PyObjectRef) { - self.args.reserve_exact(1); - self.args.insert(0, item) - } - - pub fn shift(&mut self) -> PyObjectRef { - self.args.remove(0) - } - - pub fn get_kwarg(&self, key: &str, default: PyObjectRef) -> PyObjectRef { - self.kwargs - .get(key) - .cloned() - .unwrap_or_else(|| default.clone()) - } - - pub fn get_optional_kwarg(&self, key: &str) -> Option { - self.kwargs.get(key).cloned() - } - - pub fn get_optional_kwarg_with_type( - &self, - key: &str, - ty: PyTypeRef, - vm: &VirtualMachine, - ) -> PyResult> { - match self.get_optional_kwarg(key) { - Some(kwarg) => { - if kwarg.fast_isinstance(&ty) { - Ok(Some(kwarg)) - } else { - let expected_ty_name = &ty.name(); - let kwarg_class = kwarg.class(); - let actual_ty_name = &kwarg_class.name(); - Err(vm.new_type_error(format!( - "argument of type {} is required for named parameter `{}` (got: {})", - expected_ty_name, key, actual_ty_name - ))) - } - } - None => Ok(None), - } - } - - pub fn take_positional(&mut self) -> Option { - if self.args.is_empty() { - None - } else { - Some(self.args.remove(0)) - } - } - - pub fn take_positional_keyword(&mut self, name: &str) -> Option { - self.take_positional().or_else(|| self.take_keyword(name)) - } - - pub fn take_keyword(&mut self, name: &str) -> Option { - self.kwargs.swap_remove(name) - } - - pub fn remaining_keywords(&mut self) -> impl Iterator + '_ { - self.kwargs.drain(..) - } - - /// Binds these arguments to their respective values. - /// - /// If there is an insufficient number of arguments, there are leftover - /// arguments after performing the binding, or if an argument is not of - /// the expected type, a TypeError is raised. - /// - /// If the given `FromArgs` includes any conversions, exceptions raised - /// during the conversion will halt the binding and return the error. - pub fn bind(mut self, vm: &VirtualMachine) -> PyResult { - let given_args = self.args.len(); - let bound = T::from_args(vm, &mut self) - .map_err(|e| e.into_exception(T::arity(), given_args, vm))?; - - if !self.args.is_empty() { - Err(vm.new_type_error(format!( - "Expected at most {} arguments ({} given)", - T::arity().end(), - given_args, - ))) - } else if let Some(err) = self.check_kwargs_empty(vm) { - Err(err) - } else { - Ok(bound) - } - } - - pub fn check_kwargs_empty(&self, vm: &VirtualMachine) -> Option { - self.kwargs - .keys() - .next() - .map(|k| vm.new_type_error(format!("Unexpected keyword argument {}", k))) - } -} - -/// An error encountered while binding arguments to the parameters of a Python -/// function call. -pub enum ArgumentError { - /// The call provided fewer positional arguments than the function requires. - TooFewArgs, - /// The call provided more positional arguments than the function accepts. - TooManyArgs, - /// The function doesn't accept a keyword argument with the given name. - InvalidKeywordArgument(String), - /// The function require a keyword argument with the given name, but one wasn't provided - RequiredKeywordArgument(String), - /// An exception was raised while binding arguments to the function - /// parameters. - Exception(PyBaseExceptionRef), -} - -impl From for ArgumentError { - fn from(ex: PyBaseExceptionRef) -> Self { - ArgumentError::Exception(ex) - } -} - -impl ArgumentError { - fn into_exception( - self, - arity: RangeInclusive, - num_given: usize, - vm: &VirtualMachine, - ) -> PyBaseExceptionRef { - match self { - ArgumentError::TooFewArgs => vm.new_type_error(format!( - "Expected at least {} arguments ({} given)", - arity.start(), - num_given - )), - ArgumentError::TooManyArgs => vm.new_type_error(format!( - "Expected at most {} arguments ({} given)", - arity.end(), - num_given - )), - ArgumentError::InvalidKeywordArgument(name) => { - vm.new_type_error(format!("{} is an invalid keyword argument", name)) - } - ArgumentError::RequiredKeywordArgument(name) => { - vm.new_type_error(format!("Required keyqord only argument {}", name)) - } - ArgumentError::Exception(ex) => ex, - } - } -} - -/// Implemented by any type that can be accepted as a parameter to a built-in -/// function. -/// -pub trait FromArgs: Sized { - /// The range of positional arguments permitted by the function signature. - /// - /// Returns an empty range if not applicable. - fn arity() -> RangeInclusive { - 0..=0 - } - - /// Extracts this item from the next argument(s). - fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result; -} - -pub trait FromArgOptional { - type Inner: TryFromObject; - fn from_inner(x: Self::Inner) -> Self; -} -impl FromArgOptional for OptionalArg { - type Inner = T; - fn from_inner(x: T) -> Self { - Self::Present(x) - } -} -impl FromArgOptional for T { - type Inner = Self; - fn from_inner(x: Self) -> Self { - x - } -} - -/// A map of keyword arguments to their values. -/// -/// A built-in function with a `KwArgs` parameter is analogous to a Python -/// function with `**kwargs`. All remaining keyword arguments are extracted -/// (and hence the function will permit an arbitrary number of them). -/// -/// `KwArgs` optionally accepts a generic type parameter to allow type checks -/// or conversions of each argument. -/// -/// Note: -/// -/// KwArgs is only for functions that accept arbitrary keyword arguments. For -/// functions that accept only *specific* named arguments, a rust struct with -/// an appropriate FromArgs implementation must be created. -#[derive(Clone)] -pub struct KwArgs(IndexMap); - -impl KwArgs { - pub fn new(map: IndexMap) -> Self { - KwArgs(map) - } - - pub fn pop_kwarg(&mut self, name: &str) -> Option { - self.0.remove(name) - } -} -impl FromIterator<(String, T)> for KwArgs { - fn from_iter>(iter: I) -> Self { - KwArgs(iter.into_iter().collect()) - } -} -impl Default for KwArgs { - fn default() -> Self { - KwArgs(IndexMap::new()) - } -} - -impl FromArgs for KwArgs -where - T: TryFromObject, -{ - fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result { - let mut kwargs = IndexMap::new(); - for (name, value) in args.remaining_keywords() { - kwargs.insert(name, value.try_into_value(vm)?); - } - Ok(KwArgs(kwargs)) - } -} - -impl IntoIterator for KwArgs { - type Item = (String, T); - type IntoIter = indexmap::map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -/// A list of positional argument values. -/// -/// A built-in function with a `PosArgs` parameter is analogous to a Python -/// function with `*args`. All remaining positional arguments are extracted -/// (and hence the function will permit an arbitrary number of them). -/// -/// `PosArgs` optionally accepts a generic type parameter to allow type checks -/// or conversions of each argument. -#[derive(Clone)] -pub struct PosArgs(Vec); - -impl PosArgs { - pub fn new(args: Vec) -> Self { - Self(args) - } - - pub fn into_vec(self) -> Vec { - self.0 - } - - pub fn iter(&self) -> std::slice::Iter { - self.0.iter() - } -} - -impl From> for PosArgs { - fn from(v: Vec) -> Self { - Self(v) - } -} - -impl From<()> for PosArgs { - fn from(_args: ()) -> Self { - Self(Vec::new()) - } -} - -impl AsRef<[T]> for PosArgs { - fn as_ref(&self) -> &[T] { - &self.0 - } -} - -impl PosArgs> { - pub fn into_tuple(self, vm: &VirtualMachine) -> PyTupleRef { - vm.ctx - .new_tuple(self.0.into_iter().map(Into::into).collect()) - } -} - -impl FromArgs for PosArgs -where - T: TryFromObject, -{ - fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result { - let mut varargs = Vec::new(); - while let Some(value) = args.take_positional() { - varargs.push(value.try_into_value(vm)?); - } - Ok(PosArgs(varargs)) - } -} - -impl IntoIterator for PosArgs { - type Item = T; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl FromArgs for T -where - T: TryFromObject, -{ - fn arity() -> RangeInclusive { - 1..=1 - } - - fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result { - let value = args.take_positional().ok_or(ArgumentError::TooFewArgs)?; - Ok(value.try_into_value(vm)?) - } -} - -/// An argument that may or may not be provided by the caller. -/// -/// This style of argument is not possible in pure Python. -#[derive(Debug, result_like::OptionLike, is_macro::Is)] -pub enum OptionalArg { - Present(T), - Missing, -} - -impl OptionalArg { - pub fn unwrap_or_none(self, vm: &VirtualMachine) -> PyObjectRef { - self.unwrap_or_else(|| vm.ctx.none()) - } -} - -pub type OptionalOption = OptionalArg>; - -impl OptionalOption { - #[inline] - pub fn flatten(self) -> Option { - match self { - OptionalArg::Present(Some(value)) => Some(value), - _ => None, - } - } -} - -impl FromArgs for OptionalArg -where - T: TryFromObject, -{ - fn arity() -> RangeInclusive { - 0..=1 - } - - fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result { - let r = if let Some(value) = args.take_positional() { - OptionalArg::Present(value.try_into_value(vm)?) - } else { - OptionalArg::Missing - }; - Ok(r) - } -} - -// For functions that accept no arguments. Implemented explicitly instead of via -// macro below to avoid unused warnings. -impl FromArgs for () { - fn from_args(_vm: &VirtualMachine, _args: &mut FuncArgs) -> Result { - Ok(()) - } -} - -// A tuple of types that each implement `FromArgs` represents a sequence of -// arguments that can be bound and passed to a built-in function. -// -// Technically, a tuple can contain tuples, which can contain tuples, and so on, -// so this actually represents a tree of values to be bound from arguments, but -// in practice this is only used for the top-level parameters. -macro_rules! tuple_from_py_func_args { - ($($T:ident),+) => { - impl<$($T),+> FromArgs for ($($T,)+) - where - $($T: FromArgs),+ - { - fn arity() -> RangeInclusive { - let mut min = 0; - let mut max = 0; - $( - let (start, end) = $T::arity().into_inner(); - min += start; - max += end; - )+ - min..=max - } - - fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result { - Ok(($($T::from_args(vm, args)?,)+)) - } - } - }; -} - -// Implement `FromArgs` for up to 7-tuples, allowing built-in functions to bind -// up to 7 top-level parameters (note that `PosArgs`, `KwArgs`, nested tuples, etc. -// count as 1, so this should actually be more than enough). -tuple_from_py_func_args!(A); -tuple_from_py_func_args!(A, B); -tuple_from_py_func_args!(A, B, C); -tuple_from_py_func_args!(A, B, C, D); -tuple_from_py_func_args!(A, B, C, D, E); -tuple_from_py_func_args!(A, B, C, D, E, F); -tuple_from_py_func_args!(A, B, C, D, E, F, G); -tuple_from_py_func_args!(A, B, C, D, E, F, G, H); - /// A built-in Python function. pub type PyNativeFunc = Box PyResult)>; diff --git a/vm/src/function/argument.rs b/vm/src/function/argument.rs new file mode 100644 index 000000000..b1ebe1155 --- /dev/null +++ b/vm/src/function/argument.rs @@ -0,0 +1,545 @@ +use crate::{ + builtins::{PyBaseExceptionRef, PyTupleRef, PyTypeRef}, + convert::ToPyObject, + AsObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, VirtualMachine, +}; +use indexmap::IndexMap; +use itertools::Itertools; +use std::ops::RangeInclusive; + +pub trait IntoFuncArgs: Sized { + fn into_args(self, vm: &VirtualMachine) -> FuncArgs; + fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs { + let mut args = self.into_args(vm); + args.prepend_arg(obj); + args + } +} + +impl IntoFuncArgs for T +where + T: Into, +{ + fn into_args(self, _vm: &VirtualMachine) -> FuncArgs { + self.into() + } +} + +// A tuple of values that each implement `ToPyObject` represents a sequence of +// arguments that can be bound and passed to a built-in function. +macro_rules! into_func_args_from_tuple { + ($(($n:tt, $T:ident)),*) => { + impl<$($T,)*> IntoFuncArgs for ($($T,)*) + where + $($T: ToPyObject,)* + { + #[inline] + fn into_args(self, vm: &VirtualMachine) -> FuncArgs { + let ($($n,)*) = self; + PosArgs::new(vec![$($n.to_pyobject(vm),)*]).into() + } + + #[inline] + fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs { + let ($($n,)*) = self; + PosArgs::new(vec![obj, $($n.to_pyobject(vm),)*]).into() + } + } + }; +} + +into_func_args_from_tuple!((v1, T1)); +into_func_args_from_tuple!((v1, T1), (v2, T2)); +into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3)); +into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4)); +into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5)); + +/// The `FuncArgs` struct is one of the most used structs then creating +/// a rust function that can be called from python. It holds both positional +/// arguments, as well as keyword arguments passed to the function. +#[derive(Debug, Default, Clone)] +pub struct FuncArgs { + pub args: Vec, + // sorted map, according to https://www.python.org/dev/peps/pep-0468/ + pub kwargs: IndexMap, +} + +/// Conversion from vector of python objects to function arguments. +impl From for FuncArgs +where + A: Into, +{ + fn from(args: A) -> Self { + FuncArgs { + args: args.into().into_vec(), + kwargs: IndexMap::new(), + } + } +} + +impl From for FuncArgs { + fn from(kwargs: KwArgs) -> Self { + FuncArgs { + args: Vec::new(), + kwargs: kwargs.0, + } + } +} + +impl FromArgs for FuncArgs { + fn from_args(_vm: &VirtualMachine, args: &mut FuncArgs) -> Result { + Ok(std::mem::take(args)) + } +} + +impl FuncArgs { + pub fn new(args: A, kwargs: K) -> Self + where + A: Into, + K: Into, + { + let PosArgs(args) = args.into(); + let KwArgs(kwargs) = kwargs.into(); + Self { args, kwargs } + } + + pub fn with_kwargs_names(mut args: A, kwarg_names: KW) -> Self + where + A: ExactSizeIterator, + KW: ExactSizeIterator, + { + // last `kwarg_names.len()` elements of args in order of appearance in the call signature + let total_argc = args.len(); + let kwargc = kwarg_names.len(); + let posargc = total_argc - kwargc; + + let posargs = args.by_ref().take(posargc).collect(); + + let kwargs = kwarg_names.zip_eq(args).collect::>(); + + FuncArgs { + args: posargs, + kwargs, + } + } + + pub fn prepend_arg(&mut self, item: PyObjectRef) { + self.args.reserve_exact(1); + self.args.insert(0, item) + } + + pub fn shift(&mut self) -> PyObjectRef { + self.args.remove(0) + } + + pub fn get_kwarg(&self, key: &str, default: PyObjectRef) -> PyObjectRef { + self.kwargs + .get(key) + .cloned() + .unwrap_or_else(|| default.clone()) + } + + pub fn get_optional_kwarg(&self, key: &str) -> Option { + self.kwargs.get(key).cloned() + } + + pub fn get_optional_kwarg_with_type( + &self, + key: &str, + ty: PyTypeRef, + vm: &VirtualMachine, + ) -> PyResult> { + match self.get_optional_kwarg(key) { + Some(kwarg) => { + if kwarg.fast_isinstance(&ty) { + Ok(Some(kwarg)) + } else { + let expected_ty_name = &ty.name(); + let kwarg_class = kwarg.class(); + let actual_ty_name = &kwarg_class.name(); + Err(vm.new_type_error(format!( + "argument of type {} is required for named parameter `{}` (got: {})", + expected_ty_name, key, actual_ty_name + ))) + } + } + None => Ok(None), + } + } + + pub fn take_positional(&mut self) -> Option { + if self.args.is_empty() { + None + } else { + Some(self.args.remove(0)) + } + } + + pub fn take_positional_keyword(&mut self, name: &str) -> Option { + self.take_positional().or_else(|| self.take_keyword(name)) + } + + pub fn take_keyword(&mut self, name: &str) -> Option { + self.kwargs.swap_remove(name) + } + + pub fn remaining_keywords(&mut self) -> impl Iterator + '_ { + self.kwargs.drain(..) + } + + /// Binds these arguments to their respective values. + /// + /// If there is an insufficient number of arguments, there are leftover + /// arguments after performing the binding, or if an argument is not of + /// the expected type, a TypeError is raised. + /// + /// If the given `FromArgs` includes any conversions, exceptions raised + /// during the conversion will halt the binding and return the error. + pub fn bind(mut self, vm: &VirtualMachine) -> PyResult { + let given_args = self.args.len(); + let bound = T::from_args(vm, &mut self) + .map_err(|e| e.into_exception(T::arity(), given_args, vm))?; + + if !self.args.is_empty() { + Err(vm.new_type_error(format!( + "Expected at most {} arguments ({} given)", + T::arity().end(), + given_args, + ))) + } else if let Some(err) = self.check_kwargs_empty(vm) { + Err(err) + } else { + Ok(bound) + } + } + + pub fn check_kwargs_empty(&self, vm: &VirtualMachine) -> Option { + self.kwargs + .keys() + .next() + .map(|k| vm.new_type_error(format!("Unexpected keyword argument {}", k))) + } +} + +/// An error encountered while binding arguments to the parameters of a Python +/// function call. +pub enum ArgumentError { + /// The call provided fewer positional arguments than the function requires. + TooFewArgs, + /// The call provided more positional arguments than the function accepts. + TooManyArgs, + /// The function doesn't accept a keyword argument with the given name. + InvalidKeywordArgument(String), + /// The function require a keyword argument with the given name, but one wasn't provided + RequiredKeywordArgument(String), + /// An exception was raised while binding arguments to the function + /// parameters. + Exception(PyBaseExceptionRef), +} + +impl From for ArgumentError { + fn from(ex: PyBaseExceptionRef) -> Self { + ArgumentError::Exception(ex) + } +} + +impl ArgumentError { + fn into_exception( + self, + arity: RangeInclusive, + num_given: usize, + vm: &VirtualMachine, + ) -> PyBaseExceptionRef { + match self { + ArgumentError::TooFewArgs => vm.new_type_error(format!( + "Expected at least {} arguments ({} given)", + arity.start(), + num_given + )), + ArgumentError::TooManyArgs => vm.new_type_error(format!( + "Expected at most {} arguments ({} given)", + arity.end(), + num_given + )), + ArgumentError::InvalidKeywordArgument(name) => { + vm.new_type_error(format!("{} is an invalid keyword argument", name)) + } + ArgumentError::RequiredKeywordArgument(name) => { + vm.new_type_error(format!("Required keyqord only argument {}", name)) + } + ArgumentError::Exception(ex) => ex, + } + } +} + +/// Implemented by any type that can be accepted as a parameter to a built-in +/// function. +/// +pub trait FromArgs: Sized { + /// The range of positional arguments permitted by the function signature. + /// + /// Returns an empty range if not applicable. + fn arity() -> RangeInclusive { + 0..=0 + } + + /// Extracts this item from the next argument(s). + fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result; +} + +pub trait FromArgOptional { + type Inner: TryFromObject; + fn from_inner(x: Self::Inner) -> Self; +} +impl FromArgOptional for OptionalArg { + type Inner = T; + fn from_inner(x: T) -> Self { + Self::Present(x) + } +} +impl FromArgOptional for T { + type Inner = Self; + fn from_inner(x: Self) -> Self { + x + } +} + +/// A map of keyword arguments to their values. +/// +/// A built-in function with a `KwArgs` parameter is analogous to a Python +/// function with `**kwargs`. All remaining keyword arguments are extracted +/// (and hence the function will permit an arbitrary number of them). +/// +/// `KwArgs` optionally accepts a generic type parameter to allow type checks +/// or conversions of each argument. +/// +/// Note: +/// +/// KwArgs is only for functions that accept arbitrary keyword arguments. For +/// functions that accept only *specific* named arguments, a rust struct with +/// an appropriate FromArgs implementation must be created. +#[derive(Clone)] +pub struct KwArgs(IndexMap); + +impl KwArgs { + pub fn new(map: IndexMap) -> Self { + KwArgs(map) + } + + pub fn pop_kwarg(&mut self, name: &str) -> Option { + self.0.remove(name) + } +} +impl FromIterator<(String, T)> for KwArgs { + fn from_iter>(iter: I) -> Self { + KwArgs(iter.into_iter().collect()) + } +} +impl Default for KwArgs { + fn default() -> Self { + KwArgs(IndexMap::new()) + } +} + +impl FromArgs for KwArgs +where + T: TryFromObject, +{ + fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result { + let mut kwargs = IndexMap::new(); + for (name, value) in args.remaining_keywords() { + kwargs.insert(name, value.try_into_value(vm)?); + } + Ok(KwArgs(kwargs)) + } +} + +impl IntoIterator for KwArgs { + type Item = (String, T); + type IntoIter = indexmap::map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +/// A list of positional argument values. +/// +/// A built-in function with a `PosArgs` parameter is analogous to a Python +/// function with `*args`. All remaining positional arguments are extracted +/// (and hence the function will permit an arbitrary number of them). +/// +/// `PosArgs` optionally accepts a generic type parameter to allow type checks +/// or conversions of each argument. +#[derive(Clone)] +pub struct PosArgs(Vec); + +impl PosArgs { + pub fn new(args: Vec) -> Self { + Self(args) + } + + pub fn into_vec(self) -> Vec { + self.0 + } + + pub fn iter(&self) -> std::slice::Iter { + self.0.iter() + } +} + +impl From> for PosArgs { + fn from(v: Vec) -> Self { + Self(v) + } +} + +impl From<()> for PosArgs { + fn from(_args: ()) -> Self { + Self(Vec::new()) + } +} + +impl AsRef<[T]> for PosArgs { + fn as_ref(&self) -> &[T] { + &self.0 + } +} + +impl PosArgs> { + pub fn into_tuple(self, vm: &VirtualMachine) -> PyTupleRef { + vm.ctx + .new_tuple(self.0.into_iter().map(Into::into).collect()) + } +} + +impl FromArgs for PosArgs +where + T: TryFromObject, +{ + fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result { + let mut varargs = Vec::new(); + while let Some(value) = args.take_positional() { + varargs.push(value.try_into_value(vm)?); + } + Ok(PosArgs(varargs)) + } +} + +impl IntoIterator for PosArgs { + type Item = T; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl FromArgs for T +where + T: TryFromObject, +{ + fn arity() -> RangeInclusive { + 1..=1 + } + + fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result { + let value = args.take_positional().ok_or(ArgumentError::TooFewArgs)?; + Ok(value.try_into_value(vm)?) + } +} + +/// An argument that may or may not be provided by the caller. +/// +/// This style of argument is not possible in pure Python. +#[derive(Debug, result_like::OptionLike, is_macro::Is)] +pub enum OptionalArg { + Present(T), + Missing, +} + +impl OptionalArg { + pub fn unwrap_or_none(self, vm: &VirtualMachine) -> PyObjectRef { + self.unwrap_or_else(|| vm.ctx.none()) + } +} + +pub type OptionalOption = OptionalArg>; + +impl OptionalOption { + #[inline] + pub fn flatten(self) -> Option { + match self { + OptionalArg::Present(Some(value)) => Some(value), + _ => None, + } + } +} + +impl FromArgs for OptionalArg +where + T: TryFromObject, +{ + fn arity() -> RangeInclusive { + 0..=1 + } + + fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result { + let r = if let Some(value) = args.take_positional() { + OptionalArg::Present(value.try_into_value(vm)?) + } else { + OptionalArg::Missing + }; + Ok(r) + } +} + +// For functions that accept no arguments. Implemented explicitly instead of via +// macro below to avoid unused warnings. +impl FromArgs for () { + fn from_args(_vm: &VirtualMachine, _args: &mut FuncArgs) -> Result { + Ok(()) + } +} + +// A tuple of types that each implement `FromArgs` represents a sequence of +// arguments that can be bound and passed to a built-in function. +// +// Technically, a tuple can contain tuples, which can contain tuples, and so on, +// so this actually represents a tree of values to be bound from arguments, but +// in practice this is only used for the top-level parameters. +macro_rules! tuple_from_py_func_args { + ($($T:ident),+) => { + impl<$($T),+> FromArgs for ($($T,)+) + where + $($T: FromArgs),+ + { + fn arity() -> RangeInclusive { + let mut min = 0; + let mut max = 0; + $( + let (start, end) = $T::arity().into_inner(); + min += start; + max += end; + )+ + min..=max + } + + fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result { + Ok(($($T::from_args(vm, args)?,)+)) + } + } + }; +} + +// Implement `FromArgs` for up to 7-tuples, allowing built-in functions to bind +// up to 7 top-level parameters (note that `PosArgs`, `KwArgs`, nested tuples, etc. +// count as 1, so this should actually be more than enough). +tuple_from_py_func_args!(A); +tuple_from_py_func_args!(A, B); +tuple_from_py_func_args!(A, B, C); +tuple_from_py_func_args!(A, B, C, D); +tuple_from_py_func_args!(A, B, C, D, E); +tuple_from_py_func_args!(A, B, C, D, E, F); +tuple_from_py_func_args!(A, B, C, D, E, F, G); +tuple_from_py_func_args!(A, B, C, D, E, F, G, H);