pyo3/impl_/
extract_argument.rs

1use crate::{
2    conversion::FromPyObjectBound,
3    exceptions::PyTypeError,
4    ffi,
5    pyclass::boolean_struct::False,
6    types::{any::PyAnyMethods, dict::PyDictMethods, tuple::PyTupleMethods, PyDict, PyTuple},
7    Borrowed, Bound, PyAny, PyClass, PyClassGuard, PyClassGuardMut, PyErr, PyResult, PyTypeCheck,
8    Python,
9};
10
11/// Helper type used to keep implementation more concise.
12///
13/// (Function argument extraction borrows input arguments.)
14type PyArg<'py> = Borrowed<'py, 'py, PyAny>;
15
16/// A trait which is used to help PyO3 macros extract function arguments.
17///
18/// `#[pyclass]` structs need to extract as `PyRef<T>` and `PyRefMut<T>`
19/// wrappers rather than extracting `&T` and `&mut T` directly. The `Holder` type is used
20/// to hold these temporary wrappers - the way the macro is constructed, these wrappers
21/// will be dropped as soon as the pyfunction call ends.
22///
23/// There exists a trivial blanket implementation for `T: FromPyObject` with `Holder = ()`.
24pub trait PyFunctionArgument<'a, 'holder, 'py, const IS_OPTION: bool>: Sized {
25    type Holder: FunctionArgumentHolder;
26
27    /// Provides the type hint information for which Python types are allowed.
28    #[cfg(feature = "experimental-inspect")]
29    const INPUT_TYPE: &'static str;
30
31    fn extract(obj: &'a Bound<'py, PyAny>, holder: &'holder mut Self::Holder) -> PyResult<Self>;
32}
33
34impl<'a, 'holder, 'py, T> PyFunctionArgument<'a, 'holder, 'py, false> for T
35where
36    T: FromPyObjectBound<'a, 'py>,
37{
38    type Holder = ();
39
40    #[cfg(feature = "experimental-inspect")]
41    const INPUT_TYPE: &'static str = T::INPUT_TYPE;
42
43    #[inline]
44    fn extract(obj: &'a Bound<'py, PyAny>, _: &'holder mut ()) -> PyResult<Self> {
45        obj.extract()
46    }
47}
48
49impl<'a, 'holder, 'py, T: 'py> PyFunctionArgument<'a, 'holder, 'py, false> for &'a Bound<'py, T>
50where
51    T: PyTypeCheck,
52{
53    type Holder = ();
54
55    #[cfg(feature = "experimental-inspect")]
56    const INPUT_TYPE: &'static str = T::PYTHON_TYPE;
57
58    #[inline]
59    fn extract(obj: &'a Bound<'py, PyAny>, _: &'holder mut ()) -> PyResult<Self> {
60        obj.cast().map_err(Into::into)
61    }
62}
63
64impl<'a, 'holder, 'py, T> PyFunctionArgument<'a, 'holder, 'py, true> for Option<T>
65where
66    T: PyFunctionArgument<'a, 'holder, 'py, false>, // inner `Option`s will use `FromPyObject`
67{
68    type Holder = T::Holder;
69
70    #[cfg(feature = "experimental-inspect")]
71    const INPUT_TYPE: &'static str = "typing.Any | None";
72
73    #[inline]
74    fn extract(obj: &'a Bound<'py, PyAny>, holder: &'holder mut T::Holder) -> PyResult<Self> {
75        if obj.is_none() {
76            Ok(None)
77        } else {
78            Ok(Some(T::extract(obj, holder)?))
79        }
80    }
81}
82
83#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
84impl<'a, 'holder> PyFunctionArgument<'a, 'holder, '_, false> for &'holder str {
85    type Holder = Option<std::borrow::Cow<'a, str>>;
86
87    #[cfg(feature = "experimental-inspect")]
88    const INPUT_TYPE: &'static str = "str";
89
90    #[inline]
91    fn extract(
92        obj: &'a Bound<'_, PyAny>,
93        holder: &'holder mut Option<std::borrow::Cow<'a, str>>,
94    ) -> PyResult<Self> {
95        Ok(holder.insert(obj.extract()?))
96    }
97}
98
99/// Trait for types which can be a function argument holder - they should
100/// to be able to const-initialize to an empty value.
101pub trait FunctionArgumentHolder: Sized {
102    const INIT: Self;
103}
104
105impl FunctionArgumentHolder for () {
106    const INIT: Self = ();
107}
108
109impl<T> FunctionArgumentHolder for Option<T> {
110    const INIT: Self = None;
111}
112
113#[inline]
114pub fn extract_pyclass_ref<'a, 'holder, 'py, T: PyClass>(
115    obj: &'a Bound<'py, PyAny>,
116    holder: &'holder mut Option<PyClassGuard<'a, T>>,
117) -> PyResult<&'holder T> {
118    Ok(&*holder.insert(PyClassGuard::try_borrow(obj.downcast()?.as_unbound())?))
119}
120
121#[inline]
122pub fn extract_pyclass_ref_mut<'a, 'holder, 'py, T: PyClass<Frozen = False>>(
123    obj: &'a Bound<'py, PyAny>,
124    holder: &'holder mut Option<PyClassGuardMut<'a, T>>,
125) -> PyResult<&'holder mut T> {
126    Ok(&mut *holder.insert(PyClassGuardMut::try_borrow_mut(
127        obj.downcast()?.as_unbound(),
128    )?))
129}
130
131/// The standard implementation of how PyO3 extracts a `#[pyfunction]` or `#[pymethod]` function argument.
132#[doc(hidden)]
133pub fn extract_argument<'a, 'holder, 'py, T, const IS_OPTION: bool>(
134    obj: &'a Bound<'py, PyAny>,
135    holder: &'holder mut T::Holder,
136    arg_name: &str,
137) -> PyResult<T>
138where
139    T: PyFunctionArgument<'a, 'holder, 'py, IS_OPTION>,
140{
141    match PyFunctionArgument::extract(obj, holder) {
142        Ok(value) => Ok(value),
143        Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)),
144    }
145}
146
147/// Alternative to [`extract_argument`] used for `Option<T>` arguments. This is necessary because Option<&T>
148/// does not implement `PyFunctionArgument` for `T: PyClass`.
149#[doc(hidden)]
150pub fn extract_optional_argument<'a, 'holder, 'py, T, const IS_OPTION: bool>(
151    obj: Option<&'a Bound<'py, PyAny>>,
152    holder: &'holder mut T::Holder,
153    arg_name: &str,
154    default: fn() -> Option<T>,
155) -> PyResult<Option<T>>
156where
157    T: PyFunctionArgument<'a, 'holder, 'py, IS_OPTION>,
158{
159    match obj {
160        Some(obj) => {
161            if obj.is_none() {
162                // Explicit `None` will result in None being used as the function argument
163                Ok(None)
164            } else {
165                extract_argument(obj, holder, arg_name).map(Some)
166            }
167        }
168        _ => Ok(default()),
169    }
170}
171
172/// Alternative to [`extract_argument`] used when the argument has a default value provided by an annotation.
173#[doc(hidden)]
174pub fn extract_argument_with_default<'a, 'holder, 'py, T, const IS_OPTION: bool>(
175    obj: Option<&'a Bound<'py, PyAny>>,
176    holder: &'holder mut T::Holder,
177    arg_name: &str,
178    default: fn() -> T,
179) -> PyResult<T>
180where
181    T: PyFunctionArgument<'a, 'holder, 'py, IS_OPTION>,
182{
183    match obj {
184        Some(obj) => extract_argument(obj, holder, arg_name),
185        None => Ok(default()),
186    }
187}
188
189/// Alternative to [`extract_argument`] used when the argument has a `#[pyo3(from_py_with)]` annotation.
190#[doc(hidden)]
191pub fn from_py_with<'a, 'py, T>(
192    obj: &'a Bound<'py, PyAny>,
193    arg_name: &str,
194    extractor: fn(&'a Bound<'py, PyAny>) -> PyResult<T>,
195) -> PyResult<T> {
196    match extractor(obj) {
197        Ok(value) => Ok(value),
198        Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)),
199    }
200}
201
202/// Alternative to [`extract_argument`] used when the argument has a `#[pyo3(from_py_with)]` annotation and also a default value.
203#[doc(hidden)]
204pub fn from_py_with_with_default<'a, 'py, T>(
205    obj: Option<&'a Bound<'py, PyAny>>,
206    arg_name: &str,
207    extractor: fn(&'a Bound<'py, PyAny>) -> PyResult<T>,
208    default: fn() -> T,
209) -> PyResult<T> {
210    match obj {
211        Some(obj) => from_py_with(obj, arg_name, extractor),
212        None => Ok(default()),
213    }
214}
215
216/// Adds the argument name to the error message of an error which occurred during argument extraction.
217///
218/// Only modifies TypeError. (Cannot guarantee all exceptions have constructors from
219/// single string.)
220#[doc(hidden)]
221#[cold]
222pub fn argument_extraction_error(py: Python<'_>, arg_name: &str, error: PyErr) -> PyErr {
223    if error.get_type(py).is(py.get_type::<PyTypeError>()) {
224        let remapped_error =
225            PyTypeError::new_err(format!("argument '{}': {}", arg_name, error.value(py)));
226        remapped_error.set_cause(py, error.cause(py));
227        remapped_error
228    } else {
229        error
230    }
231}
232
233/// Unwraps the Option<&PyAny> produced by the FunctionDescription `extract_arguments_` methods.
234/// They check if required methods are all provided.
235///
236/// # Safety
237/// `argument` must not be `None`
238#[doc(hidden)]
239#[inline]
240pub unsafe fn unwrap_required_argument<'a, 'py>(
241    argument: Option<&'a Bound<'py, PyAny>>,
242) -> &'a Bound<'py, PyAny> {
243    match argument {
244        Some(value) => value,
245        #[cfg(debug_assertions)]
246        None => unreachable!("required method argument was not extracted"),
247        #[cfg(not(debug_assertions))]
248        None => std::hint::unreachable_unchecked(),
249    }
250}
251
252pub struct KeywordOnlyParameterDescription {
253    pub name: &'static str,
254    pub required: bool,
255}
256
257/// Function argument specification for a `#[pyfunction]` or `#[pymethod]`.
258pub struct FunctionDescription {
259    pub cls_name: Option<&'static str>,
260    pub func_name: &'static str,
261    pub positional_parameter_names: &'static [&'static str],
262    pub positional_only_parameters: usize,
263    pub required_positional_parameters: usize,
264    pub keyword_only_parameters: &'static [KeywordOnlyParameterDescription],
265}
266
267impl FunctionDescription {
268    fn full_name(&self) -> String {
269        if let Some(cls_name) = self.cls_name {
270            format!("{}.{}()", cls_name, self.func_name)
271        } else {
272            format!("{}()", self.func_name)
273        }
274    }
275
276    /// Equivalent of `extract_arguments_tuple_dict` which uses the Python C-API "fastcall" convention.
277    ///
278    /// # Safety
279    /// - `args` must be a pointer to a C-style array of valid `ffi::PyObject` pointers, or NULL.
280    /// - `kwnames` must be a pointer to a PyTuple, or NULL.
281    /// - `nargs + kwnames.len()` is the total length of the `args` array.
282    #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
283    pub unsafe fn extract_arguments_fastcall<'py, V, K>(
284        &self,
285        py: Python<'py>,
286        args: *const *mut ffi::PyObject,
287        nargs: ffi::Py_ssize_t,
288        kwnames: *mut ffi::PyObject,
289        output: &mut [Option<PyArg<'py>>],
290    ) -> PyResult<(V::Varargs, K::Varkeywords)>
291    where
292        V: VarargsHandler<'py>,
293        K: VarkeywordsHandler<'py>,
294    {
295        let num_positional_parameters = self.positional_parameter_names.len();
296
297        debug_assert!(nargs >= 0);
298        debug_assert!(self.positional_only_parameters <= num_positional_parameters);
299        debug_assert!(self.required_positional_parameters <= num_positional_parameters);
300        debug_assert_eq!(
301            output.len(),
302            num_positional_parameters + self.keyword_only_parameters.len()
303        );
304
305        // Handle positional arguments
306        // Safety:
307        //  - Option<PyArg> has the same memory layout as `*mut ffi::PyObject`
308        //  - we both have the GIL and can borrow these input references for the `'py` lifetime.
309        let args: *const Option<PyArg<'py>> = args.cast();
310        let positional_args_provided = nargs as usize;
311        let remaining_positional_args = if args.is_null() {
312            debug_assert_eq!(positional_args_provided, 0);
313            &[]
314        } else {
315            // Can consume at most the number of positional parameters in the function definition,
316            // the rest are varargs.
317            let positional_args_to_consume =
318                num_positional_parameters.min(positional_args_provided);
319            let (positional_parameters, remaining) = unsafe {
320                std::slice::from_raw_parts(args, positional_args_provided)
321                    .split_at(positional_args_to_consume)
322            };
323            output[..positional_args_to_consume].copy_from_slice(positional_parameters);
324            remaining
325        };
326        let varargs = V::handle_varargs_fastcall(py, remaining_positional_args, self)?;
327
328        // Handle keyword arguments
329        let mut varkeywords = K::Varkeywords::default();
330
331        // Safety: kwnames is known to be a pointer to a tuple, or null
332        //  - we both have the GIL and can borrow this input reference for the `'py` lifetime.
333        let kwnames: Option<Borrowed<'_, '_, PyTuple>> = unsafe {
334            Borrowed::from_ptr_or_opt(py, kwnames).map(|kwnames| kwnames.cast_unchecked())
335        };
336        if let Some(kwnames) = kwnames {
337            let kwargs = unsafe {
338                ::std::slice::from_raw_parts(
339                    // Safety: PyArg has the same memory layout as `*mut ffi::PyObject`
340                    args.offset(nargs).cast::<PyArg<'py>>(),
341                    kwnames.len(),
342                )
343            };
344
345            self.handle_kwargs::<K, _>(
346                kwnames.iter_borrowed().zip(kwargs.iter().copied()),
347                &mut varkeywords,
348                num_positional_parameters,
349                output,
350            )?
351        }
352
353        // Once all inputs have been processed, check that all required arguments have been provided.
354
355        self.ensure_no_missing_required_positional_arguments(output, positional_args_provided)?;
356        self.ensure_no_missing_required_keyword_arguments(output)?;
357
358        Ok((varargs, varkeywords))
359    }
360
361    /// Extracts the `args` and `kwargs` provided into `output`, according to this function
362    /// definition.
363    ///
364    /// `output` must have the same length as this function has positional and keyword-only
365    /// parameters (as per the `positional_parameter_names` and `keyword_only_parameters`
366    /// respectively).
367    ///
368    /// Unexpected, duplicate or invalid arguments will cause this function to return `TypeError`.
369    ///
370    /// # Safety
371    /// - `args` must be a pointer to a PyTuple.
372    /// - `kwargs` must be a pointer to a PyDict, or NULL.
373    pub unsafe fn extract_arguments_tuple_dict<'py, V, K>(
374        &self,
375        py: Python<'py>,
376        args: *mut ffi::PyObject,
377        kwargs: *mut ffi::PyObject,
378        output: &mut [Option<PyArg<'py>>],
379    ) -> PyResult<(V::Varargs, K::Varkeywords)>
380    where
381        V: VarargsHandler<'py>,
382        K: VarkeywordsHandler<'py>,
383    {
384        // Safety:
385        //  - `args` is known to be a tuple
386        //  - `kwargs` is known to be a dict or null
387        //  - we both have the GIL and can borrow these input references for the `'py` lifetime.
388        let args: Borrowed<'py, 'py, PyTuple> =
389            unsafe { Borrowed::from_ptr(py, args).cast_unchecked::<PyTuple>() };
390        let kwargs: Option<Borrowed<'py, 'py, PyDict>> =
391            unsafe { Borrowed::from_ptr_or_opt(py, kwargs).map(|kwargs| kwargs.cast_unchecked()) };
392
393        let num_positional_parameters = self.positional_parameter_names.len();
394
395        debug_assert!(self.positional_only_parameters <= num_positional_parameters);
396        debug_assert!(self.required_positional_parameters <= num_positional_parameters);
397        debug_assert_eq!(
398            output.len(),
399            num_positional_parameters + self.keyword_only_parameters.len()
400        );
401
402        // Copy positional arguments into output
403        for (i, arg) in args
404            .iter_borrowed()
405            .take(num_positional_parameters)
406            .enumerate()
407        {
408            output[i] = Some(arg);
409        }
410
411        // If any arguments remain, push them to varargs (if possible) or error
412        let varargs = V::handle_varargs_tuple(&args, self)?;
413
414        // Handle keyword arguments
415        let mut varkeywords = K::Varkeywords::default();
416        if let Some(kwargs) = kwargs {
417            self.handle_kwargs::<K, _>(
418                unsafe { kwargs.iter_borrowed() },
419                &mut varkeywords,
420                num_positional_parameters,
421                output,
422            )?
423        }
424
425        // Once all inputs have been processed, check that all required arguments have been provided.
426
427        self.ensure_no_missing_required_positional_arguments(output, args.len())?;
428        self.ensure_no_missing_required_keyword_arguments(output)?;
429
430        Ok((varargs, varkeywords))
431    }
432
433    #[inline]
434    fn handle_kwargs<'py, K, I>(
435        &self,
436        kwargs: I,
437        varkeywords: &mut K::Varkeywords,
438        num_positional_parameters: usize,
439        output: &mut [Option<PyArg<'py>>],
440    ) -> PyResult<()>
441    where
442        K: VarkeywordsHandler<'py>,
443        I: IntoIterator<Item = (PyArg<'py>, PyArg<'py>)>,
444    {
445        debug_assert_eq!(
446            num_positional_parameters,
447            self.positional_parameter_names.len()
448        );
449        debug_assert_eq!(
450            output.len(),
451            num_positional_parameters + self.keyword_only_parameters.len()
452        );
453        let mut positional_only_keyword_arguments = Vec::new();
454        for (kwarg_name_py, value) in kwargs {
455            // Safety: All keyword arguments should be UTF-8 strings, but if it's not, `.to_str()`
456            // will return an error anyway.
457            #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
458            let kwarg_name =
459                unsafe { kwarg_name_py.cast_unchecked::<crate::types::PyString>() }.to_str();
460
461            #[cfg(all(not(Py_3_10), Py_LIMITED_API))]
462            let kwarg_name = kwarg_name_py.extract::<crate::pybacked::PyBackedStr>();
463
464            if let Ok(kwarg_name_owned) = kwarg_name {
465                #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
466                let kwarg_name = kwarg_name_owned;
467                #[cfg(all(not(Py_3_10), Py_LIMITED_API))]
468                let kwarg_name: &str = &kwarg_name_owned;
469
470                // Try to place parameter in keyword only parameters
471                if let Some(i) = self.find_keyword_parameter_in_keyword_only(kwarg_name) {
472                    if output[i + num_positional_parameters]
473                        .replace(value)
474                        .is_some()
475                    {
476                        return Err(self.multiple_values_for_argument(kwarg_name));
477                    }
478                    continue;
479                }
480
481                // Repeat for positional parameters
482                if let Some(i) = self.find_keyword_parameter_in_positional(kwarg_name) {
483                    if i < self.positional_only_parameters {
484                        // If accepting **kwargs, then it's allowed for the name of the
485                        // kwarg to conflict with a postional-only argument - the value
486                        // will go into **kwargs anyway.
487                        if K::handle_varkeyword(varkeywords, kwarg_name_py, value, self).is_err() {
488                            positional_only_keyword_arguments.push(kwarg_name_owned);
489                        }
490                    } else if output[i].replace(value).is_some() {
491                        return Err(self.multiple_values_for_argument(kwarg_name));
492                    }
493                    continue;
494                }
495            };
496
497            K::handle_varkeyword(varkeywords, kwarg_name_py, value, self)?
498        }
499
500        if !positional_only_keyword_arguments.is_empty() {
501            #[cfg(all(not(Py_3_10), Py_LIMITED_API))]
502            let positional_only_keyword_arguments: Vec<_> = positional_only_keyword_arguments
503                .iter()
504                .map(std::ops::Deref::deref)
505                .collect();
506            return Err(self.positional_only_keyword_arguments(&positional_only_keyword_arguments));
507        }
508
509        Ok(())
510    }
511
512    #[inline]
513    fn find_keyword_parameter_in_positional(&self, kwarg_name: &str) -> Option<usize> {
514        self.positional_parameter_names
515            .iter()
516            .position(|&param_name| param_name == kwarg_name)
517    }
518
519    #[inline]
520    fn find_keyword_parameter_in_keyword_only(&self, kwarg_name: &str) -> Option<usize> {
521        // Compare the keyword name against each parameter in turn. This is exactly the same method
522        // which CPython uses to map keyword names. Although it's O(num_parameters), the number of
523        // parameters is expected to be small so it's not worth constructing a mapping.
524        self.keyword_only_parameters
525            .iter()
526            .position(|param_desc| param_desc.name == kwarg_name)
527    }
528
529    #[inline]
530    fn ensure_no_missing_required_positional_arguments(
531        &self,
532        output: &[Option<PyArg<'_>>],
533        positional_args_provided: usize,
534    ) -> PyResult<()> {
535        if positional_args_provided < self.required_positional_parameters {
536            for out in &output[positional_args_provided..self.required_positional_parameters] {
537                if out.is_none() {
538                    return Err(self.missing_required_positional_arguments(output));
539                }
540            }
541        }
542        Ok(())
543    }
544
545    #[inline]
546    fn ensure_no_missing_required_keyword_arguments(
547        &self,
548        output: &[Option<PyArg<'_>>],
549    ) -> PyResult<()> {
550        let keyword_output = &output[self.positional_parameter_names.len()..];
551        for (param, out) in self.keyword_only_parameters.iter().zip(keyword_output) {
552            if param.required && out.is_none() {
553                return Err(self.missing_required_keyword_arguments(keyword_output));
554            }
555        }
556        Ok(())
557    }
558
559    #[cold]
560    fn too_many_positional_arguments(&self, args_provided: usize) -> PyErr {
561        let was = if args_provided == 1 { "was" } else { "were" };
562        let msg = if self.required_positional_parameters != self.positional_parameter_names.len() {
563            format!(
564                "{} takes from {} to {} positional arguments but {} {} given",
565                self.full_name(),
566                self.required_positional_parameters,
567                self.positional_parameter_names.len(),
568                args_provided,
569                was
570            )
571        } else {
572            format!(
573                "{} takes {} positional arguments but {} {} given",
574                self.full_name(),
575                self.positional_parameter_names.len(),
576                args_provided,
577                was
578            )
579        };
580        PyTypeError::new_err(msg)
581    }
582
583    #[cold]
584    fn multiple_values_for_argument(&self, argument: &str) -> PyErr {
585        PyTypeError::new_err(format!(
586            "{} got multiple values for argument '{}'",
587            self.full_name(),
588            argument
589        ))
590    }
591
592    #[cold]
593    fn unexpected_keyword_argument(&self, argument: PyArg<'_>) -> PyErr {
594        PyTypeError::new_err(format!(
595            "{} got an unexpected keyword argument '{}'",
596            self.full_name(),
597            argument.as_any()
598        ))
599    }
600
601    #[cold]
602    fn positional_only_keyword_arguments(&self, parameter_names: &[&str]) -> PyErr {
603        let mut msg = format!(
604            "{} got some positional-only arguments passed as keyword arguments: ",
605            self.full_name()
606        );
607        push_parameter_list(&mut msg, parameter_names);
608        PyTypeError::new_err(msg)
609    }
610
611    #[cold]
612    fn missing_required_arguments(&self, argument_type: &str, parameter_names: &[&str]) -> PyErr {
613        let arguments = if parameter_names.len() == 1 {
614            "argument"
615        } else {
616            "arguments"
617        };
618        let mut msg = format!(
619            "{} missing {} required {} {}: ",
620            self.full_name(),
621            parameter_names.len(),
622            argument_type,
623            arguments,
624        );
625        push_parameter_list(&mut msg, parameter_names);
626        PyTypeError::new_err(msg)
627    }
628
629    #[cold]
630    fn missing_required_keyword_arguments(&self, keyword_outputs: &[Option<PyArg<'_>>]) -> PyErr {
631        debug_assert_eq!(self.keyword_only_parameters.len(), keyword_outputs.len());
632
633        let missing_keyword_only_arguments: Vec<_> = self
634            .keyword_only_parameters
635            .iter()
636            .zip(keyword_outputs)
637            .filter_map(|(keyword_desc, out)| {
638                if keyword_desc.required && out.is_none() {
639                    Some(keyword_desc.name)
640                } else {
641                    None
642                }
643            })
644            .collect();
645
646        debug_assert!(!missing_keyword_only_arguments.is_empty());
647        self.missing_required_arguments("keyword", &missing_keyword_only_arguments)
648    }
649
650    #[cold]
651    fn missing_required_positional_arguments(&self, output: &[Option<PyArg<'_>>]) -> PyErr {
652        let missing_positional_arguments: Vec<_> = self
653            .positional_parameter_names
654            .iter()
655            .take(self.required_positional_parameters)
656            .zip(output)
657            .filter_map(|(param, out)| if out.is_none() { Some(*param) } else { None })
658            .collect();
659
660        debug_assert!(!missing_positional_arguments.is_empty());
661        self.missing_required_arguments("positional", &missing_positional_arguments)
662    }
663}
664
665/// A trait used to control whether to accept varargs in FunctionDescription::extract_argument_(method) functions.
666pub trait VarargsHandler<'py> {
667    type Varargs;
668    /// Called by `FunctionDescription::extract_arguments_fastcall` with any additional arguments.
669    fn handle_varargs_fastcall(
670        py: Python<'py>,
671        varargs: &[Option<PyArg<'py>>],
672        function_description: &FunctionDescription,
673    ) -> PyResult<Self::Varargs>;
674    /// Called by `FunctionDescription::extract_arguments_tuple_dict` with the original tuple.
675    ///
676    /// Additional arguments are those in the tuple slice starting from `function_description.positional_parameter_names.len()`.
677    fn handle_varargs_tuple(
678        args: &Bound<'py, PyTuple>,
679        function_description: &FunctionDescription,
680    ) -> PyResult<Self::Varargs>;
681}
682
683/// Marker struct which indicates varargs are not allowed.
684pub struct NoVarargs;
685
686impl<'py> VarargsHandler<'py> for NoVarargs {
687    type Varargs = ();
688
689    #[inline]
690    fn handle_varargs_fastcall(
691        _py: Python<'py>,
692        varargs: &[Option<PyArg<'py>>],
693        function_description: &FunctionDescription,
694    ) -> PyResult<Self::Varargs> {
695        let extra_arguments = varargs.len();
696        if extra_arguments > 0 {
697            return Err(function_description.too_many_positional_arguments(
698                function_description.positional_parameter_names.len() + extra_arguments,
699            ));
700        }
701        Ok(())
702    }
703
704    #[inline]
705    fn handle_varargs_tuple(
706        args: &Bound<'py, PyTuple>,
707        function_description: &FunctionDescription,
708    ) -> PyResult<Self::Varargs> {
709        let positional_parameter_count = function_description.positional_parameter_names.len();
710        let provided_args_count = args.len();
711        if provided_args_count <= positional_parameter_count {
712            Ok(())
713        } else {
714            Err(function_description.too_many_positional_arguments(provided_args_count))
715        }
716    }
717}
718
719/// Marker struct which indicates varargs should be collected into a `PyTuple`.
720pub struct TupleVarargs;
721
722impl<'py> VarargsHandler<'py> for TupleVarargs {
723    type Varargs = Bound<'py, PyTuple>;
724    #[inline]
725    fn handle_varargs_fastcall(
726        py: Python<'py>,
727        varargs: &[Option<PyArg<'py>>],
728        _function_description: &FunctionDescription,
729    ) -> PyResult<Self::Varargs> {
730        PyTuple::new(py, varargs)
731    }
732
733    #[inline]
734    fn handle_varargs_tuple(
735        args: &Bound<'py, PyTuple>,
736        function_description: &FunctionDescription,
737    ) -> PyResult<Self::Varargs> {
738        let positional_parameters = function_description.positional_parameter_names.len();
739        Ok(args.get_slice(positional_parameters, args.len()))
740    }
741}
742
743/// A trait used to control whether to accept varkeywords in FunctionDescription::extract_argument_(method) functions.
744pub trait VarkeywordsHandler<'py> {
745    type Varkeywords: Default;
746    fn handle_varkeyword(
747        varkeywords: &mut Self::Varkeywords,
748        name: PyArg<'py>,
749        value: PyArg<'py>,
750        function_description: &FunctionDescription,
751    ) -> PyResult<()>;
752}
753
754/// Marker struct which indicates unknown keywords are not permitted.
755pub struct NoVarkeywords;
756
757impl<'py> VarkeywordsHandler<'py> for NoVarkeywords {
758    type Varkeywords = ();
759    #[inline]
760    fn handle_varkeyword(
761        _varkeywords: &mut Self::Varkeywords,
762        name: PyArg<'py>,
763        _value: PyArg<'py>,
764        function_description: &FunctionDescription,
765    ) -> PyResult<()> {
766        Err(function_description.unexpected_keyword_argument(name))
767    }
768}
769
770/// Marker struct which indicates unknown keywords should be collected into a `PyDict`.
771pub struct DictVarkeywords;
772
773impl<'py> VarkeywordsHandler<'py> for DictVarkeywords {
774    type Varkeywords = Option<Bound<'py, PyDict>>;
775    #[inline]
776    fn handle_varkeyword(
777        varkeywords: &mut Self::Varkeywords,
778        name: PyArg<'py>,
779        value: PyArg<'py>,
780        _function_description: &FunctionDescription,
781    ) -> PyResult<()> {
782        varkeywords
783            .get_or_insert_with(|| PyDict::new(name.py()))
784            .set_item(name, value)
785    }
786}
787
788fn push_parameter_list(msg: &mut String, parameter_names: &[&str]) {
789    let len = parameter_names.len();
790    for (i, parameter) in parameter_names.iter().enumerate() {
791        if i != 0 {
792            if len > 2 {
793                msg.push(',');
794            }
795
796            if i == len - 1 {
797                msg.push_str(" and ")
798            } else {
799                msg.push(' ')
800            }
801        }
802
803        msg.push('\'');
804        msg.push_str(parameter);
805        msg.push('\'');
806    }
807}
808
809#[cfg(test)]
810mod tests {
811    use crate::types::{IntoPyDict, PyTuple};
812    use crate::Python;
813
814    use super::{push_parameter_list, FunctionDescription, NoVarargs, NoVarkeywords};
815
816    #[test]
817    fn unexpected_keyword_argument() {
818        let function_description = FunctionDescription {
819            cls_name: None,
820            func_name: "example",
821            positional_parameter_names: &[],
822            positional_only_parameters: 0,
823            required_positional_parameters: 0,
824            keyword_only_parameters: &[],
825        };
826
827        Python::attach(|py| {
828            let args = PyTuple::empty(py);
829            let kwargs = [("foo", 0u8)].into_py_dict(py).unwrap();
830            let err = unsafe {
831                function_description
832                    .extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
833                        py,
834                        args.as_ptr(),
835                        kwargs.as_ptr(),
836                        &mut [],
837                    )
838                    .unwrap_err()
839            };
840            assert_eq!(
841                err.to_string(),
842                "TypeError: example() got an unexpected keyword argument 'foo'"
843            );
844        })
845    }
846
847    #[test]
848    fn keyword_not_string() {
849        let function_description = FunctionDescription {
850            cls_name: None,
851            func_name: "example",
852            positional_parameter_names: &[],
853            positional_only_parameters: 0,
854            required_positional_parameters: 0,
855            keyword_only_parameters: &[],
856        };
857
858        Python::attach(|py| {
859            let args = PyTuple::empty(py);
860            let kwargs = [(1u8, 1u8)].into_py_dict(py).unwrap();
861            let err = unsafe {
862                function_description
863                    .extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
864                        py,
865                        args.as_ptr(),
866                        kwargs.as_ptr(),
867                        &mut [],
868                    )
869                    .unwrap_err()
870            };
871            assert_eq!(
872                err.to_string(),
873                "TypeError: example() got an unexpected keyword argument '1'"
874            );
875        })
876    }
877
878    #[test]
879    fn missing_required_arguments() {
880        let function_description = FunctionDescription {
881            cls_name: None,
882            func_name: "example",
883            positional_parameter_names: &["foo", "bar"],
884            positional_only_parameters: 0,
885            required_positional_parameters: 2,
886            keyword_only_parameters: &[],
887        };
888
889        Python::attach(|py| {
890            let args = PyTuple::empty(py);
891            let mut output = [None, None];
892            let err = unsafe {
893                function_description.extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
894                    py,
895                    args.as_ptr(),
896                    std::ptr::null_mut(),
897                    &mut output,
898                )
899            }
900            .unwrap_err();
901            assert_eq!(
902                err.to_string(),
903                "TypeError: example() missing 2 required positional arguments: 'foo' and 'bar'"
904            );
905        })
906    }
907
908    #[test]
909    fn push_parameter_list_empty() {
910        let mut s = String::new();
911        push_parameter_list(&mut s, &[]);
912        assert_eq!(&s, "");
913    }
914
915    #[test]
916    fn push_parameter_list_one() {
917        let mut s = String::new();
918        push_parameter_list(&mut s, &["a"]);
919        assert_eq!(&s, "'a'");
920    }
921
922    #[test]
923    fn push_parameter_list_two() {
924        let mut s = String::new();
925        push_parameter_list(&mut s, &["a", "b"]);
926        assert_eq!(&s, "'a' and 'b'");
927    }
928
929    #[test]
930    fn push_parameter_list_three() {
931        let mut s = String::new();
932        push_parameter_list(&mut s, &["a", "b", "c"]);
933        assert_eq!(&s, "'a', 'b', and 'c'");
934    }
935
936    #[test]
937    fn push_parameter_list_four() {
938        let mut s = String::new();
939        push_parameter_list(&mut s, &["a", "b", "c", "d"]);
940        assert_eq!(&s, "'a', 'b', 'c', and 'd'");
941    }
942}