1#[cfg(feature = "experimental-inspect")]
2use crate::inspect::TypeHint;
3#[cfg(any(Py_3_10, not(Py_LIMITED_API), feature = "experimental-inspect"))]
4use crate::types::PyString;
5use crate::{
6 exceptions::PyTypeError,
7 ffi,
8 pyclass::boolean_struct::False,
9 types::{any::PyAnyMethods, dict::PyDictMethods, tuple::PyTupleMethods, PyDict, PyTuple},
10 Borrowed, Bound, CastError, FromPyObject, PyAny, PyClass, PyClassGuard, PyClassGuardMut, PyErr,
11 PyResult, PyTypeCheck, Python,
12};
13
14type PyArg<'py> = Borrowed<'py, 'py, PyAny>;
18
19mod function_argument {
23 use crate::{
24 impl_::extract_argument::PyFunctionArgument, pyclass::boolean_struct::False, FromPyObject,
25 PyClass, PyTypeCheck,
26 };
27
28 pub trait Sealed<const IMPLEMENTS_FROMPYOBJECT: bool> {}
29 impl<'a, 'py, T: FromPyObject<'a, 'py>> Sealed<true> for T {}
30 impl<'py, T: PyTypeCheck + 'py> Sealed<false> for &'_ crate::Bound<'py, T> {}
31 impl<'a, 'holder, 'py, T: PyFunctionArgument<'a, 'holder, 'py, false>> Sealed<false> for Option<T> {}
32 #[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
33 impl Sealed<false> for &'_ str {}
34 impl<T: PyClass> Sealed<false> for &'_ T {}
35 impl<T: PyClass<Frozen = False>> Sealed<false> for &'_ mut T {}
36}
37
38#[diagnostic::on_unimplemented(
52 message = "`{Self}` cannot be used as a Python function argument",
53 note = "implement `FromPyObject` to enable using `{Self}` as a function argument",
54 note = "`Python<'py>` is also a valid argument type to pass the Python token into `#[pyfunction]`s and `#[pymethods]`"
55)]
56pub trait PyFunctionArgument<'a, 'holder, 'py, const IMPLEMENTS_FROMPYOBJECT: bool>:
57 Sized + function_argument::Sealed<IMPLEMENTS_FROMPYOBJECT>
58{
59 type Holder: FunctionArgumentHolder;
60 type Error: Into<PyErr>;
61
62 #[cfg(feature = "experimental-inspect")]
64 const INPUT_TYPE: TypeHint;
65
66 fn extract(
67 obj: &'a Bound<'py, PyAny>,
68 holder: &'holder mut Self::Holder,
69 ) -> Result<Self, Self::Error>;
70}
71
72impl<'a, 'py, T> PyFunctionArgument<'a, '_, 'py, true> for T
73where
74 T: FromPyObject<'a, 'py>,
75{
76 type Holder = ();
77 type Error = T::Error;
78
79 #[cfg(feature = "experimental-inspect")]
80 const INPUT_TYPE: TypeHint = T::INPUT_TYPE;
81
82 #[inline]
83 fn extract(obj: &'a Bound<'py, PyAny>, _: &'_ mut ()) -> Result<Self, Self::Error> {
84 obj.extract()
85 }
86}
87
88impl<'a, 'py, T: 'py> PyFunctionArgument<'a, '_, 'py, false> for &'a Bound<'py, T>
89where
90 T: PyTypeCheck,
91{
92 type Holder = ();
93 type Error = CastError<'a, 'py>;
94
95 #[cfg(feature = "experimental-inspect")]
96 const INPUT_TYPE: TypeHint = T::TYPE_HINT;
97
98 #[inline]
99 fn extract(obj: &'a Bound<'py, PyAny>, _: &'_ mut ()) -> Result<Self, Self::Error> {
100 obj.cast()
101 }
102}
103
104impl<'a, 'holder, 'py, T> PyFunctionArgument<'a, 'holder, 'py, false> for Option<T>
106where
107 T: PyFunctionArgument<'a, 'holder, 'py, false>,
108{
109 type Holder = T::Holder;
110 type Error = T::Error;
111
112 #[cfg(feature = "experimental-inspect")]
113 const INPUT_TYPE: TypeHint = TypeHint::union(&[T::INPUT_TYPE, TypeHint::builtin("None")]);
114
115 #[inline]
116 fn extract(
117 obj: &'a Bound<'py, PyAny>,
118 holder: &'holder mut T::Holder,
119 ) -> Result<Self, Self::Error> {
120 if obj.is_none() {
121 Ok(None)
122 } else {
123 Ok(Some(T::extract(obj, holder)?))
124 }
125 }
126}
127
128#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
129impl<'a, 'holder, 'py> PyFunctionArgument<'a, 'holder, 'py, false> for &'holder str {
130 type Holder = Option<std::borrow::Cow<'a, str>>;
131 type Error = <std::borrow::Cow<'a, str> as FromPyObject<'a, 'py>>::Error;
132
133 #[cfg(feature = "experimental-inspect")]
134 const INPUT_TYPE: TypeHint = PyString::TYPE_HINT;
135
136 #[inline]
137 fn extract(
138 obj: &'a Bound<'py, PyAny>,
139 holder: &'holder mut Option<std::borrow::Cow<'a, str>>,
140 ) -> PyResult<Self> {
141 Ok(holder.insert(obj.extract()?))
142 }
143}
144
145pub trait FunctionArgumentHolder: Sized {
148 const INIT: Self;
149}
150
151impl FunctionArgumentHolder for () {
152 const INIT: Self = ();
153}
154
155impl<T> FunctionArgumentHolder for Option<T> {
156 const INIT: Self = None;
157}
158
159impl<'a, 'holder, T: PyClass> PyFunctionArgument<'a, 'holder, '_, false> for &'holder T {
160 type Holder = ::std::option::Option<PyClassGuard<'a, T>>;
161 type Error = PyErr;
162
163 #[cfg(feature = "experimental-inspect")]
164 const INPUT_TYPE: TypeHint = T::TYPE_HINT;
165
166 #[inline]
167 fn extract(obj: &'a Bound<'_, PyAny>, holder: &'holder mut Self::Holder) -> PyResult<Self> {
168 extract_pyclass_ref(obj, holder)
169 }
170}
171
172impl<'a, 'holder, T: PyClass<Frozen = False>> PyFunctionArgument<'a, 'holder, '_, false>
173 for &'holder mut T
174{
175 type Holder = ::std::option::Option<PyClassGuardMut<'a, T>>;
176 type Error = PyErr;
177
178 #[cfg(feature = "experimental-inspect")]
179 const INPUT_TYPE: TypeHint = T::TYPE_HINT;
180
181 #[inline]
182 fn extract(obj: &'a Bound<'_, PyAny>, holder: &'holder mut Self::Holder) -> PyResult<Self> {
183 extract_pyclass_ref_mut(obj, holder)
184 }
185}
186
187#[inline]
188pub fn extract_pyclass_ref<'a, 'holder, T: PyClass>(
189 obj: &'a Bound<'_, PyAny>,
190 holder: &'holder mut Option<PyClassGuard<'a, T>>,
191) -> PyResult<&'holder T> {
192 Ok(&*holder.insert(PyClassGuard::try_borrow(obj.cast()?.as_unbound())?))
193}
194
195#[inline]
196pub fn extract_pyclass_ref_mut<'a, 'holder, T: PyClass<Frozen = False>>(
197 obj: &'a Bound<'_, PyAny>,
198 holder: &'holder mut Option<PyClassGuardMut<'a, T>>,
199) -> PyResult<&'holder mut T> {
200 Ok(&mut *holder.insert(PyClassGuardMut::try_borrow_mut(obj.cast()?.as_unbound())?))
201}
202
203#[doc(hidden)]
205pub fn extract_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>(
206 obj: &'a Bound<'py, PyAny>,
207 holder: &'holder mut T::Holder,
208 arg_name: &str,
209) -> PyResult<T>
210where
211 T: PyFunctionArgument<'a, 'holder, 'py, IMPLEMENTS_FROMPYOBJECT>,
212{
213 match PyFunctionArgument::extract(obj, holder) {
214 Ok(value) => Ok(value),
215 Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e.into())),
216 }
217}
218
219#[doc(hidden)]
221pub fn extract_argument_with_default<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>(
222 obj: Option<&'a Bound<'py, PyAny>>,
223 holder: &'holder mut T::Holder,
224 arg_name: &str,
225 default: fn() -> T,
226) -> PyResult<T>
227where
228 T: PyFunctionArgument<'a, 'holder, 'py, IMPLEMENTS_FROMPYOBJECT>,
229{
230 match obj {
231 Some(obj) => extract_argument(obj, holder, arg_name),
232 None => Ok(default()),
233 }
234}
235
236#[doc(hidden)]
238pub fn from_py_with<'a, 'py, T>(
239 obj: &'a Bound<'py, PyAny>,
240 arg_name: &str,
241 extractor: fn(&'a Bound<'py, PyAny>) -> PyResult<T>,
242) -> PyResult<T> {
243 match extractor(obj) {
244 Ok(value) => Ok(value),
245 Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)),
246 }
247}
248
249#[doc(hidden)]
251pub fn from_py_with_with_default<'a, 'py, T>(
252 obj: Option<&'a Bound<'py, PyAny>>,
253 arg_name: &str,
254 extractor: fn(&'a Bound<'py, PyAny>) -> PyResult<T>,
255 default: fn() -> T,
256) -> PyResult<T> {
257 match obj {
258 Some(obj) => from_py_with(obj, arg_name, extractor),
259 None => Ok(default()),
260 }
261}
262
263#[doc(hidden)]
268#[cold]
269pub fn argument_extraction_error(py: Python<'_>, arg_name: &str, error: PyErr) -> PyErr {
270 if error.get_type(py).is(py.get_type::<PyTypeError>()) {
271 let remapped_error =
272 PyTypeError::new_err(format!("argument '{}': {}", arg_name, error.value(py)));
273 remapped_error.set_cause(py, error.cause(py));
274 remapped_error
275 } else {
276 error
277 }
278}
279
280#[doc(hidden)]
286#[inline]
287pub unsafe fn unwrap_required_argument<'a, 'py>(
288 argument: Option<&'a Bound<'py, PyAny>>,
289) -> &'a Bound<'py, PyAny> {
290 match argument {
291 Some(value) => value,
292 #[cfg(debug_assertions)]
293 None => unreachable!("required method argument was not extracted"),
294 #[cfg(not(debug_assertions))]
296 None => unsafe { std::hint::unreachable_unchecked() },
297 }
298}
299
300pub struct KeywordOnlyParameterDescription {
301 pub name: &'static str,
302 pub required: bool,
303}
304
305pub struct FunctionDescription {
307 pub cls_name: Option<&'static str>,
308 pub func_name: &'static str,
309 pub positional_parameter_names: &'static [&'static str],
310 pub positional_only_parameters: usize,
311 pub required_positional_parameters: usize,
312 pub keyword_only_parameters: &'static [KeywordOnlyParameterDescription],
313}
314
315impl FunctionDescription {
316 fn full_name(&self) -> String {
317 if let Some(cls_name) = self.cls_name {
318 format!("{}.{}()", cls_name, self.func_name)
319 } else {
320 format!("{}()", self.func_name)
321 }
322 }
323
324 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
331 pub unsafe fn extract_arguments_fastcall<'py, V, K>(
332 &self,
333 py: Python<'py>,
334 args: *const *mut ffi::PyObject,
335 nargs: ffi::Py_ssize_t,
336 kwnames: *mut ffi::PyObject,
337 output: &mut [Option<PyArg<'py>>],
338 ) -> PyResult<(V::Varargs, K::Varkeywords)>
339 where
340 V: VarargsHandler<'py>,
341 K: VarkeywordsHandler<'py>,
342 {
343 let num_positional_parameters = self.positional_parameter_names.len();
344
345 debug_assert!(nargs >= 0);
346 debug_assert!(self.positional_only_parameters <= num_positional_parameters);
347 debug_assert!(self.required_positional_parameters <= num_positional_parameters);
348 debug_assert_eq!(
349 output.len(),
350 num_positional_parameters + self.keyword_only_parameters.len()
351 );
352
353 let args: *const Option<PyArg<'py>> = args.cast();
358 let positional_args_provided = nargs as usize;
359 let remaining_positional_args = if args.is_null() {
360 debug_assert_eq!(positional_args_provided, 0);
361 &[]
362 } else {
363 let positional_args_to_consume =
366 num_positional_parameters.min(positional_args_provided);
367 let (positional_parameters, remaining) = unsafe {
368 std::slice::from_raw_parts(args, positional_args_provided)
369 .split_at(positional_args_to_consume)
370 };
371 output[..positional_args_to_consume].copy_from_slice(positional_parameters);
372 remaining
373 };
374 let varargs = V::handle_varargs_fastcall(py, remaining_positional_args, self)?;
375
376 let mut varkeywords = K::Varkeywords::default();
378
379 let kwnames: Option<Borrowed<'_, '_, PyTuple>> = unsafe {
382 Borrowed::from_ptr_or_opt(py, kwnames).map(|kwnames| kwnames.cast_unchecked())
383 };
384 if let Some(kwnames) = kwnames {
385 let kwargs = unsafe {
386 ::std::slice::from_raw_parts(
387 args.offset(nargs).cast::<PyArg<'py>>(),
389 kwnames.len(),
390 )
391 };
392
393 self.handle_kwargs::<K, _>(
394 kwnames.iter_borrowed().zip(kwargs.iter().copied()),
395 &mut varkeywords,
396 num_positional_parameters,
397 output,
398 )?
399 }
400
401 self.ensure_no_missing_required_positional_arguments(output, positional_args_provided)?;
404 self.ensure_no_missing_required_keyword_arguments(output)?;
405
406 Ok((varargs, varkeywords))
407 }
408
409 pub unsafe fn extract_arguments_tuple_dict<'py, V, K>(
422 &self,
423 py: Python<'py>,
424 args: *mut ffi::PyObject,
425 kwargs: *mut ffi::PyObject,
426 output: &mut [Option<PyArg<'py>>],
427 ) -> PyResult<(V::Varargs, K::Varkeywords)>
428 where
429 V: VarargsHandler<'py>,
430 K: VarkeywordsHandler<'py>,
431 {
432 let args: Borrowed<'py, 'py, PyTuple> =
437 unsafe { Borrowed::from_ptr(py, args).cast_unchecked::<PyTuple>() };
438 let kwargs: Option<Borrowed<'py, 'py, PyDict>> =
439 unsafe { Borrowed::from_ptr_or_opt(py, kwargs).map(|kwargs| kwargs.cast_unchecked()) };
440
441 let num_positional_parameters = self.positional_parameter_names.len();
442
443 debug_assert!(self.positional_only_parameters <= num_positional_parameters);
444 debug_assert!(self.required_positional_parameters <= num_positional_parameters);
445 debug_assert_eq!(
446 output.len(),
447 num_positional_parameters + self.keyword_only_parameters.len()
448 );
449
450 for (i, arg) in args
452 .iter_borrowed()
453 .take(num_positional_parameters)
454 .enumerate()
455 {
456 output[i] = Some(arg);
457 }
458
459 let varargs = V::handle_varargs_tuple(&args, self)?;
461
462 let mut varkeywords = K::Varkeywords::default();
464 if let Some(kwargs) = kwargs {
465 self.handle_kwargs::<K, _>(
466 unsafe { kwargs.iter_borrowed() },
467 &mut varkeywords,
468 num_positional_parameters,
469 output,
470 )?
471 }
472
473 self.ensure_no_missing_required_positional_arguments(output, args.len())?;
476 self.ensure_no_missing_required_keyword_arguments(output)?;
477
478 Ok((varargs, varkeywords))
479 }
480
481 #[inline]
482 fn handle_kwargs<'py, K, I>(
483 &self,
484 kwargs: I,
485 varkeywords: &mut K::Varkeywords,
486 num_positional_parameters: usize,
487 output: &mut [Option<PyArg<'py>>],
488 ) -> PyResult<()>
489 where
490 K: VarkeywordsHandler<'py>,
491 I: IntoIterator<Item = (PyArg<'py>, PyArg<'py>)>,
492 {
493 debug_assert_eq!(
494 num_positional_parameters,
495 self.positional_parameter_names.len()
496 );
497 debug_assert_eq!(
498 output.len(),
499 num_positional_parameters + self.keyword_only_parameters.len()
500 );
501 let mut positional_only_keyword_arguments = Vec::new();
502 for (kwarg_name_py, value) in kwargs {
503 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
506 let kwarg_name = unsafe { kwarg_name_py.cast_unchecked::<PyString>() }.to_str();
507
508 #[cfg(all(not(Py_3_10), Py_LIMITED_API))]
509 let kwarg_name = kwarg_name_py.extract::<crate::pybacked::PyBackedStr>();
510
511 if let Ok(kwarg_name_owned) = kwarg_name {
512 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
513 let kwarg_name = kwarg_name_owned;
514 #[cfg(all(not(Py_3_10), Py_LIMITED_API))]
515 let kwarg_name: &str = &kwarg_name_owned;
516
517 if let Some(i) = self.find_keyword_parameter_in_keyword_only(kwarg_name) {
519 if output[i + num_positional_parameters]
520 .replace(value)
521 .is_some()
522 {
523 return Err(self.multiple_values_for_argument(kwarg_name));
524 }
525 continue;
526 }
527
528 if let Some(i) = self.find_keyword_parameter_in_positional(kwarg_name) {
530 if i < self.positional_only_parameters {
531 if K::handle_varkeyword(varkeywords, kwarg_name_py, value, self).is_err() {
535 positional_only_keyword_arguments.push(kwarg_name_owned);
536 }
537 } else if output[i].replace(value).is_some() {
538 return Err(self.multiple_values_for_argument(kwarg_name));
539 }
540 continue;
541 }
542 };
543
544 K::handle_varkeyword(varkeywords, kwarg_name_py, value, self)?
545 }
546
547 if !positional_only_keyword_arguments.is_empty() {
548 #[cfg(all(not(Py_3_10), Py_LIMITED_API))]
549 let positional_only_keyword_arguments: Vec<_> = positional_only_keyword_arguments
550 .iter()
551 .map(std::ops::Deref::deref)
552 .collect();
553 return Err(self.positional_only_keyword_arguments(&positional_only_keyword_arguments));
554 }
555
556 Ok(())
557 }
558
559 #[inline]
560 fn find_keyword_parameter_in_positional(&self, kwarg_name: &str) -> Option<usize> {
561 self.positional_parameter_names
562 .iter()
563 .position(|¶m_name| param_name == kwarg_name)
564 }
565
566 #[inline]
567 fn find_keyword_parameter_in_keyword_only(&self, kwarg_name: &str) -> Option<usize> {
568 self.keyword_only_parameters
572 .iter()
573 .position(|param_desc| param_desc.name == kwarg_name)
574 }
575
576 #[inline]
577 fn ensure_no_missing_required_positional_arguments(
578 &self,
579 output: &[Option<PyArg<'_>>],
580 positional_args_provided: usize,
581 ) -> PyResult<()> {
582 if positional_args_provided < self.required_positional_parameters {
583 for out in &output[positional_args_provided..self.required_positional_parameters] {
584 if out.is_none() {
585 return Err(self.missing_required_positional_arguments(output));
586 }
587 }
588 }
589 Ok(())
590 }
591
592 #[inline]
593 fn ensure_no_missing_required_keyword_arguments(
594 &self,
595 output: &[Option<PyArg<'_>>],
596 ) -> PyResult<()> {
597 let keyword_output = &output[self.positional_parameter_names.len()..];
598 for (param, out) in self.keyword_only_parameters.iter().zip(keyword_output) {
599 if param.required && out.is_none() {
600 return Err(self.missing_required_keyword_arguments(keyword_output));
601 }
602 }
603 Ok(())
604 }
605
606 #[cold]
607 fn too_many_positional_arguments(&self, args_provided: usize) -> PyErr {
608 let was = if args_provided == 1 { "was" } else { "were" };
609 let msg = if self.required_positional_parameters != self.positional_parameter_names.len() {
610 format!(
611 "{} takes from {} to {} positional arguments but {} {} given",
612 self.full_name(),
613 self.required_positional_parameters,
614 self.positional_parameter_names.len(),
615 args_provided,
616 was
617 )
618 } else {
619 format!(
620 "{} takes {} positional arguments but {} {} given",
621 self.full_name(),
622 self.positional_parameter_names.len(),
623 args_provided,
624 was
625 )
626 };
627 PyTypeError::new_err(msg)
628 }
629
630 #[cold]
631 fn multiple_values_for_argument(&self, argument: &str) -> PyErr {
632 PyTypeError::new_err(format!(
633 "{} got multiple values for argument '{}'",
634 self.full_name(),
635 argument
636 ))
637 }
638
639 #[cold]
640 fn unexpected_keyword_argument(&self, argument: PyArg<'_>) -> PyErr {
641 PyTypeError::new_err(format!(
642 "{} got an unexpected keyword argument '{}'",
643 self.full_name(),
644 argument.as_any()
645 ))
646 }
647
648 #[cold]
649 fn positional_only_keyword_arguments(&self, parameter_names: &[&str]) -> PyErr {
650 let mut msg = format!(
651 "{} got some positional-only arguments passed as keyword arguments: ",
652 self.full_name()
653 );
654 push_parameter_list(&mut msg, parameter_names);
655 PyTypeError::new_err(msg)
656 }
657
658 #[cold]
659 fn missing_required_arguments(&self, argument_type: &str, parameter_names: &[&str]) -> PyErr {
660 let arguments = if parameter_names.len() == 1 {
661 "argument"
662 } else {
663 "arguments"
664 };
665 let mut msg = format!(
666 "{} missing {} required {} {}: ",
667 self.full_name(),
668 parameter_names.len(),
669 argument_type,
670 arguments,
671 );
672 push_parameter_list(&mut msg, parameter_names);
673 PyTypeError::new_err(msg)
674 }
675
676 #[cold]
677 fn missing_required_keyword_arguments(&self, keyword_outputs: &[Option<PyArg<'_>>]) -> PyErr {
678 debug_assert_eq!(self.keyword_only_parameters.len(), keyword_outputs.len());
679
680 let missing_keyword_only_arguments: Vec<_> = self
681 .keyword_only_parameters
682 .iter()
683 .zip(keyword_outputs)
684 .filter_map(|(keyword_desc, out)| {
685 if keyword_desc.required && out.is_none() {
686 Some(keyword_desc.name)
687 } else {
688 None
689 }
690 })
691 .collect();
692
693 debug_assert!(!missing_keyword_only_arguments.is_empty());
694 self.missing_required_arguments("keyword", &missing_keyword_only_arguments)
695 }
696
697 #[cold]
698 fn missing_required_positional_arguments(&self, output: &[Option<PyArg<'_>>]) -> PyErr {
699 let missing_positional_arguments: Vec<_> = self
700 .positional_parameter_names
701 .iter()
702 .take(self.required_positional_parameters)
703 .zip(output)
704 .filter_map(|(param, out)| if out.is_none() { Some(*param) } else { None })
705 .collect();
706
707 debug_assert!(!missing_positional_arguments.is_empty());
708 self.missing_required_arguments("positional", &missing_positional_arguments)
709 }
710}
711
712pub trait VarargsHandler<'py> {
714 type Varargs;
715 fn handle_varargs_fastcall(
717 py: Python<'py>,
718 varargs: &[Option<PyArg<'py>>],
719 function_description: &FunctionDescription,
720 ) -> PyResult<Self::Varargs>;
721 fn handle_varargs_tuple(
725 args: &Bound<'py, PyTuple>,
726 function_description: &FunctionDescription,
727 ) -> PyResult<Self::Varargs>;
728}
729
730pub struct NoVarargs;
732
733impl<'py> VarargsHandler<'py> for NoVarargs {
734 type Varargs = ();
735
736 #[inline]
737 fn handle_varargs_fastcall(
738 _py: Python<'py>,
739 varargs: &[Option<PyArg<'py>>],
740 function_description: &FunctionDescription,
741 ) -> PyResult<Self::Varargs> {
742 let extra_arguments = varargs.len();
743 if extra_arguments > 0 {
744 return Err(function_description.too_many_positional_arguments(
745 function_description.positional_parameter_names.len() + extra_arguments,
746 ));
747 }
748 Ok(())
749 }
750
751 #[inline]
752 fn handle_varargs_tuple(
753 args: &Bound<'py, PyTuple>,
754 function_description: &FunctionDescription,
755 ) -> PyResult<Self::Varargs> {
756 let positional_parameter_count = function_description.positional_parameter_names.len();
757 let provided_args_count = args.len();
758 if provided_args_count <= positional_parameter_count {
759 Ok(())
760 } else {
761 Err(function_description.too_many_positional_arguments(provided_args_count))
762 }
763 }
764}
765
766pub struct TupleVarargs;
768
769impl<'py> VarargsHandler<'py> for TupleVarargs {
770 type Varargs = Bound<'py, PyTuple>;
771 #[inline]
772 fn handle_varargs_fastcall(
773 py: Python<'py>,
774 varargs: &[Option<PyArg<'py>>],
775 _function_description: &FunctionDescription,
776 ) -> PyResult<Self::Varargs> {
777 PyTuple::new(py, varargs)
778 }
779
780 #[inline]
781 fn handle_varargs_tuple(
782 args: &Bound<'py, PyTuple>,
783 function_description: &FunctionDescription,
784 ) -> PyResult<Self::Varargs> {
785 let positional_parameters = function_description.positional_parameter_names.len();
786 Ok(args.get_slice(positional_parameters, args.len()))
787 }
788}
789
790pub trait VarkeywordsHandler<'py> {
792 type Varkeywords: Default;
793 fn handle_varkeyword(
794 varkeywords: &mut Self::Varkeywords,
795 name: PyArg<'py>,
796 value: PyArg<'py>,
797 function_description: &FunctionDescription,
798 ) -> PyResult<()>;
799}
800
801pub struct NoVarkeywords;
803
804impl<'py> VarkeywordsHandler<'py> for NoVarkeywords {
805 type Varkeywords = ();
806 #[inline]
807 fn handle_varkeyword(
808 _varkeywords: &mut Self::Varkeywords,
809 name: PyArg<'py>,
810 _value: PyArg<'py>,
811 function_description: &FunctionDescription,
812 ) -> PyResult<()> {
813 Err(function_description.unexpected_keyword_argument(name))
814 }
815}
816
817pub struct DictVarkeywords;
819
820impl<'py> VarkeywordsHandler<'py> for DictVarkeywords {
821 type Varkeywords = Option<Bound<'py, PyDict>>;
822 #[inline]
823 fn handle_varkeyword(
824 varkeywords: &mut Self::Varkeywords,
825 name: PyArg<'py>,
826 value: PyArg<'py>,
827 _function_description: &FunctionDescription,
828 ) -> PyResult<()> {
829 varkeywords
830 .get_or_insert_with(|| PyDict::new(name.py()))
831 .set_item(name, value)
832 }
833}
834
835fn push_parameter_list(msg: &mut String, parameter_names: &[&str]) {
836 let len = parameter_names.len();
837 for (i, parameter) in parameter_names.iter().enumerate() {
838 if i != 0 {
839 if len > 2 {
840 msg.push(',');
841 }
842
843 if i == len - 1 {
844 msg.push_str(" and ")
845 } else {
846 msg.push(' ')
847 }
848 }
849
850 msg.push('\'');
851 msg.push_str(parameter);
852 msg.push('\'');
853 }
854}
855
856#[cfg(test)]
857mod tests {
858 use crate::types::{IntoPyDict, PyTuple};
859 use crate::Python;
860
861 use super::{push_parameter_list, FunctionDescription, NoVarargs, NoVarkeywords};
862
863 #[test]
864 fn unexpected_keyword_argument() {
865 let function_description = FunctionDescription {
866 cls_name: None,
867 func_name: "example",
868 positional_parameter_names: &[],
869 positional_only_parameters: 0,
870 required_positional_parameters: 0,
871 keyword_only_parameters: &[],
872 };
873
874 Python::attach(|py| {
875 let args = PyTuple::empty(py);
876 let kwargs = [("foo", 0u8)].into_py_dict(py).unwrap();
877 let err = unsafe {
878 function_description
879 .extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
880 py,
881 args.as_ptr(),
882 kwargs.as_ptr(),
883 &mut [],
884 )
885 .unwrap_err()
886 };
887 assert_eq!(
888 err.to_string(),
889 "TypeError: example() got an unexpected keyword argument 'foo'"
890 );
891 })
892 }
893
894 #[test]
895 fn keyword_not_string() {
896 let function_description = FunctionDescription {
897 cls_name: None,
898 func_name: "example",
899 positional_parameter_names: &[],
900 positional_only_parameters: 0,
901 required_positional_parameters: 0,
902 keyword_only_parameters: &[],
903 };
904
905 Python::attach(|py| {
906 let args = PyTuple::empty(py);
907 let kwargs = [(1u8, 1u8)].into_py_dict(py).unwrap();
908 let err = unsafe {
909 function_description
910 .extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
911 py,
912 args.as_ptr(),
913 kwargs.as_ptr(),
914 &mut [],
915 )
916 .unwrap_err()
917 };
918 assert_eq!(
919 err.to_string(),
920 "TypeError: example() got an unexpected keyword argument '1'"
921 );
922 })
923 }
924
925 #[test]
926 fn missing_required_arguments() {
927 let function_description = FunctionDescription {
928 cls_name: None,
929 func_name: "example",
930 positional_parameter_names: &["foo", "bar"],
931 positional_only_parameters: 0,
932 required_positional_parameters: 2,
933 keyword_only_parameters: &[],
934 };
935
936 Python::attach(|py| {
937 let args = PyTuple::empty(py);
938 let mut output = [None, None];
939 let err = unsafe {
940 function_description.extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
941 py,
942 args.as_ptr(),
943 std::ptr::null_mut(),
944 &mut output,
945 )
946 }
947 .unwrap_err();
948 assert_eq!(
949 err.to_string(),
950 "TypeError: example() missing 2 required positional arguments: 'foo' and 'bar'"
951 );
952 })
953 }
954
955 #[test]
956 fn push_parameter_list_empty() {
957 let mut s = String::new();
958 push_parameter_list(&mut s, &[]);
959 assert_eq!(&s, "");
960 }
961
962 #[test]
963 fn push_parameter_list_one() {
964 let mut s = String::new();
965 push_parameter_list(&mut s, &["a"]);
966 assert_eq!(&s, "'a'");
967 }
968
969 #[test]
970 fn push_parameter_list_two() {
971 let mut s = String::new();
972 push_parameter_list(&mut s, &["a", "b"]);
973 assert_eq!(&s, "'a' and 'b'");
974 }
975
976 #[test]
977 fn push_parameter_list_three() {
978 let mut s = String::new();
979 push_parameter_list(&mut s, &["a", "b", "c"]);
980 assert_eq!(&s, "'a', 'b', and 'c'");
981 }
982
983 #[test]
984 fn push_parameter_list_four() {
985 let mut s = String::new();
986 push_parameter_list(&mut s, &["a", "b", "c", "d"]);
987 assert_eq!(&s, "'a', 'b', 'c', and 'd'");
988 }
989}