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