Skip to main content

pyo3/impl_/
extract_argument.rs

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