use std::collections::HashMap; use std::marker::PhantomData; use std::mem; use std::ops::RangeInclusive; use indexmap::IndexMap; use result_like::impl_option_like; use crate::exceptions::PyBaseExceptionRef; use crate::obj::objtuple::PyTupleRef; use crate::obj::objtype::{isinstance, PyTypeRef}; use crate::pyobject::{ BorrowValue, IntoPyResult, PyObjectRef, PyRef, PyResult, PyThreadingConstraint, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; use self::OptionalArg::*; /// The `PyFuncArgs` 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 PyFuncArgs { 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 PyFuncArgs { fn from(args: Vec) -> Self { PyFuncArgs { args, kwargs: IndexMap::new(), } } } impl From for PyFuncArgs { fn from(arg: PyObjectRef) -> Self { PyFuncArgs { args: vec![arg], kwargs: IndexMap::new(), } } } impl From<(Args, KwArgs)> for PyFuncArgs { fn from(arg: (Args, KwArgs)) -> Self { let Args(args) = arg.0; let KwArgs(kwargs) = arg.1; PyFuncArgs { args, kwargs: kwargs.into_iter().collect(), } } } impl From<(&Args, &KwArgs)> for PyFuncArgs { fn from(arg: (&Args, &KwArgs)) -> Self { let Args(args) = arg.0; let KwArgs(kwargs) = arg.1; PyFuncArgs { args: args.clone(), kwargs: kwargs.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), } } } impl From for PyFuncArgs { fn from(kwargs: KwArgs) -> Self { PyFuncArgs { args: Vec::new(), kwargs: kwargs.into_iter().collect(), } } } impl FromArgs for PyFuncArgs { fn from_args(_vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result { Ok(mem::take(args)) } } impl PyFuncArgs { pub fn new(mut args: Vec, kwarg_names: Vec) -> PyFuncArgs { // last `kwarg_names.len()` elements of args in order of appearance in the call signature let kwarg_values = args.drain((args.len() - kwarg_names.len())..); let mut kwargs = IndexMap::new(); for (name, value) in kwarg_names.iter().zip(kwarg_values) { kwargs.insert(name.clone(), value); } PyFuncArgs { args, kwargs } } pub fn insert(&self, item: PyObjectRef) -> PyFuncArgs { let mut args = PyFuncArgs { args: self.args.clone(), kwargs: self.kwargs.clone(), }; args.args.insert(0, item); args } 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 isinstance(&kwarg, &ty) { Ok(Some(kwarg)) } else { let expected_ty_name = &ty.name; 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<'a>( &'a mut self, ) -> impl Iterator + 'a { 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| match e { ArgumentError::TooFewArgs => vm.new_type_error(format!( "Expected at least {} arguments ({} given)", T::arity().start(), given_args, )), ArgumentError::TooManyArgs => vm.new_type_error(format!( "Expected at most {} arguments ({} given)", T::arity().end(), given_args, )), 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, })?; 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(k) = self.kwargs.keys().next() { Err(vm.new_type_error(format!("Unexpected keyword argument {}", k))) } else { Ok(bound) } } } /// 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) } } /// 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 PyFuncArgs) -> 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 analagous 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. pub struct KwArgs(HashMap); impl KwArgs { pub fn new(map: HashMap) -> Self { KwArgs(map) } pub fn pop_kwarg(&mut self, name: &str) -> Option { self.0.remove(name) } } impl From> for KwArgs { fn from(map: HashMap) -> Self { KwArgs(map) } } impl Default for KwArgs { fn default() -> Self { KwArgs(HashMap::new()) } } impl FromArgs for KwArgs where T: TryFromObject, { fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result { let mut kwargs = HashMap::new(); for (name, value) in args.remaining_keywords() { kwargs.insert(name, T::try_from_object(vm, value)?); } Ok(KwArgs(kwargs)) } } impl IntoIterator for KwArgs { type Item = (String, T); type IntoIter = std::collections::hash_map::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } /// A list of positional argument values. /// /// A built-in function with a `Args` 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). /// /// `Args` optionally accepts a generic type parameter to allow type checks /// or conversions of each argument. #[derive(Clone)] pub struct Args(Vec); impl Args { pub fn new(args: Vec) -> Self { Args(args) } pub fn into_vec(self) -> Vec { self.0 } } impl From> for Args { fn from(v: Vec) -> Self { Args(v) } } impl AsRef<[T]> for Args { fn as_ref(&self) -> &[T] { &self.0 } } impl Args> { pub fn into_tuple(self, vm: &VirtualMachine) -> PyObjectRef { vm.ctx .new_tuple(self.0.into_iter().map(PyRef::into_object).collect()) } } impl FromArgs for Args where T: TryFromObject, { fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result { let mut varargs = Vec::new(); while let Some(value) = args.take_positional() { varargs.push(T::try_from_object(vm, value)?); } Ok(Args(varargs)) } } impl IntoIterator for Args { 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 PyFuncArgs) -> Result { if let Some(value) = args.take_positional() { Ok(T::try_from_object(vm, value)?) } else { Err(ArgumentError::TooFewArgs) } } } /// An argument that may or may not be provided by the caller. /// /// This style of argument is not possible in pure Python. #[derive(Debug, is_macro::Is)] pub enum OptionalArg { Present(T), Missing, } impl_option_like!(OptionalArg, Present, 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 { 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 PyFuncArgs) -> Result { if let Some(value) = args.take_positional() { Ok(Present(T::try_from_object(vm, value)?)) } else { Ok(Missing) } } } // 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 PyFuncArgs) -> 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 PyFuncArgs) -> Result { Ok(($($T::from_args(vm, args)?,)+)) } } }; } // Implement `FromArgs` for up to 5-tuples, allowing built-in functions to bind // up to 5 top-level parameters (note that `Args`, `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); /// A built-in Python function. pub type PyNativeFunc = Box PyResult)>; /// Implemented by types that are or can generate built-in functions. /// /// This trait is implemented by any function that matches the pattern: /// /// ```rust,ignore /// Fn([&self,] [T where T: FromArgs, ...] [, vm: &VirtualMachine]) /// ``` /// /// For example, anything from `Fn()` to `Fn(vm: &VirtualMachine) -> u32` to /// `Fn(PyIntRef, PyIntRef) -> String` to /// `Fn(&self, PyStrRef, FooOptions, vm: &VirtualMachine) -> PyResult` /// is `IntoPyNativeFunc`. If you do want a really general function signature, e.g. /// to forward the args to another function, you can define a function like /// `Fn(PyFuncArgs [, &VirtualMachine]) -> ...` /// /// Note that the `Kind` type parameter is meaningless and should be considered /// an implementation detail; if you need to use `IntoPyNativeFunc` as a trait bound /// just pass an unconstrained generic type, e.g. /// `fn foo(f: F) where F: IntoPyNativeFunc` pub trait IntoPyNativeFunc: Sized + PyThreadingConstraint + 'static { fn call(&self, vm: &VirtualMachine, args: PyFuncArgs) -> PyResult; /// `IntoPyNativeFunc::into_func()` generates a PyNativeFunc that performs the /// appropriate type and arity checking, any requested conversions, and then if /// successful calls the function with the extracted parameters. fn into_func(self) -> PyNativeFunc { Box::new(move |vm: &VirtualMachine, args| self.call(vm, args)) } } // TODO: once higher-rank trait bounds are stabilized, remove the `Kind` type // parameter and impl for F where F: for PyNativeFuncInternal impl IntoPyNativeFunc<(T, R, VM)> for F where F: PyNativeFuncInternal, { fn call(&self, vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { self.call_(vm, args) } } mod sealed { use super::*; pub trait PyNativeFuncInternal: Sized + PyThreadingConstraint + 'static { fn call_(&self, vm: &VirtualMachine, args: PyFuncArgs) -> PyResult; } } use sealed::PyNativeFuncInternal; #[doc(hidden)] pub struct OwnedParam(PhantomData); #[doc(hidden)] pub struct RefParam(PhantomData); // This is the "magic" that allows rust functions of varying signatures to // generate native python functions. // // Note that this could be done without a macro - it is simply to avoid repetition. macro_rules! into_py_native_func_tuple { ($(($n:tt, $T:ident)),*) => { impl PyNativeFuncInternal<($(OwnedParam<$T>,)*), R, VirtualMachine> for F where F: Fn($($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static, $($T: FromArgs,)* R: IntoPyResult, { fn call_(&self, vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let ($($n,)*) = args.bind::<($($T,)*)>(vm)?; (self)($($n,)* vm).into_pyresult(vm) } } impl PyNativeFuncInternal<(RefParam, $(OwnedParam<$T>,)*), R, VirtualMachine> for F where F: Fn(&S, $($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static, S: PyValue, $($T: FromArgs,)* R: IntoPyResult, { fn call_(&self, vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let (zelf, $($n,)*) = args.bind::<(PyRef, $($T,)*)>(vm)?; (self)(&zelf, $($n,)* vm).into_pyresult(vm) } } impl PyNativeFuncInternal<($(OwnedParam<$T>,)*), R, ()> for F where F: Fn($($T,)*) -> R + PyThreadingConstraint + 'static, $($T: FromArgs,)* R: IntoPyResult, { fn call_(&self, vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let ($($n,)*) = args.bind::<($($T,)*)>(vm)?; (self)($($n,)*).into_pyresult(vm) } } impl PyNativeFuncInternal<(RefParam, $(OwnedParam<$T>,)*), R, ()> for F where F: Fn(&S, $($T,)*) -> R + PyThreadingConstraint + 'static, S: PyValue, $($T: FromArgs,)* R: IntoPyResult, { fn call_(&self, vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let (zelf, $($n,)*) = args.bind::<(PyRef, $($T,)*)>(vm)?; (self)(&zelf, $($n,)*).into_pyresult(vm) } } }; } into_py_native_func_tuple!(); into_py_native_func_tuple!((v1, T1)); into_py_native_func_tuple!((v1, T1), (v2, T2)); into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3)); into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4)); into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5)); /// Tests that the predicate is True on a single value, or if the value is a tuple a tuple, then /// test that any of the values contained within the tuples satisfies the predicate. Type parameter /// T specifies the type that is expected, if the input value is not of that type or a tuple of /// values of that type, then a TypeError is raised. pub fn single_or_tuple_any( obj: PyObjectRef, predicate: &F, message: &M, vm: &VirtualMachine, ) -> PyResult where T: TryFromObject, F: Fn(&T) -> PyResult, M: Fn(&PyObjectRef) -> String, { match T::try_from_object(vm, obj.clone()) { Ok(single) => (predicate)(&single), Err(_) => { let tuple = PyTupleRef::try_from_object(vm, obj.clone()) .map_err(|_| vm.new_type_error((message)(&obj)))?; for obj in tuple.borrow_value().iter() { if single_or_tuple_any(obj.clone(), predicate, message, vm)? { return Ok(true); } } Ok(false) } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_intonativefunc_noalloc() { let check_zst = |f: PyNativeFunc| assert_eq!(std::mem::size_of_val(f.as_ref()), 0); fn py_func(_b: bool, _vm: &crate::VirtualMachine) -> i32 { 1 } check_zst(py_func.into_func()); let empty_closure = || "foo".to_owned(); check_zst(empty_closure.into_func()); } }