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
18type PyArg<'py> = Borrowed<'py, 'py, PyAny>;
22
23mod 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#[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 #[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
112impl<'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
153mod function_argument_holder {
155 pub trait Sealed {}
156
157 impl Sealed for () {}
158 impl<T> Sealed for Option<T> {}
159}
160
161pub 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
219pub 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
234pub 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
250pub 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
262pub 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#[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#[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 #[cfg(not(debug_assertions))]
304 None => unsafe { std::hint::unreachable_unchecked() },
305 }
306}
307
308#[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 #[cfg(not(debug_assertions))]
319 None => unsafe { std::hint::unreachable_unchecked() },
320 }
321}
322
323#[inline]
333pub unsafe fn cast_function_argument<'py>(
334 py: Python<'py>,
335 raw_arg: *mut ffi::PyObject,
336) -> PyArg<'py> {
337 unsafe { Borrowed::from_ptr_unchecked(py, raw_arg) }
339}
340
341#[inline]
350pub unsafe fn cast_non_null_function_argument<'py>(
351 py: Python<'py>,
352 raw_arg: NonNull<ffi::PyObject>,
353) -> PyArg<'py> {
354 unsafe { Borrowed::from_non_null(py, raw_arg) }
356}
357
358#[inline]
363pub unsafe fn cast_optional_function_argument<'py>(
364 py: Python<'py>,
365 raw_arg: *mut ffi::PyObject,
366) -> Option<PyArg<'py>> {
367 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
376pub 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 #[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 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 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 let mut varkeywords = K::Varkeywords::default();
449
450 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 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 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 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 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 for (i, arg) in args
523 .iter_borrowed()
524 .take(num_positional_parameters)
525 .enumerate()
526 {
527 output[i] = Some(arg);
528 }
529
530 let varargs = V::handle_varargs_tuple(&args, self)?;
532
533 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 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 #[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 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 if let Some(i) = self.find_keyword_parameter_in_positional(kwarg_name) {
601 if i < self.positional_only_parameters {
602 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(|¶m_name| param_name == kwarg_name)
635 }
636
637 #[inline]
638 fn find_keyword_parameter_in_keyword_only(&self, kwarg_name: &str) -> Option<usize> {
639 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
783mod 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
793pub trait VarargsHandler<'py>: varargs_handler::Sealed {
795 type Varargs;
796 fn handle_varargs_fastcall(
798 py: Python<'py>,
799 varargs: &[Option<PyArg<'py>>],
800 function_description: &FunctionDescription,
801 ) -> PyResult<Self::Varargs>;
802 fn handle_varargs_tuple(
806 args: &Bound<'py, PyTuple>,
807 function_description: &FunctionDescription,
808 ) -> PyResult<Self::Varargs>;
809}
810
811pub 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
847pub 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
871mod 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
881pub 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
892pub 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
908pub 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}