1use crate::{
2 AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine,
3 builtins::{PyBaseExceptionRef, PyTupleRef, PyTypeRef},
4 convert::ToPyObject,
5 object::{Traverse, TraverseFn},
6};
7use core::ops::RangeInclusive;
8use indexmap::IndexMap;
9use itertools::Itertools;
10
11pub trait IntoFuncArgs: Sized {
12 fn into_args(self, vm: &VirtualMachine) -> FuncArgs;
13 fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs {
14 let mut args = self.into_args(vm);
15 args.prepend_arg(obj);
16 args
17 }
18}
19
20impl<T> IntoFuncArgs for T
21where
22 T: Into<FuncArgs>,
23{
24 fn into_args(self, _vm: &VirtualMachine) -> FuncArgs {
25 self.into()
26 }
27}
28
29macro_rules! into_func_args_from_tuple {
32 ($(($n:tt, $T:ident)),*) => {
33 impl<$($T,)*> IntoFuncArgs for ($($T,)*)
34 where
35 $($T: ToPyObject,)*
36 {
37 #[inline]
38 fn into_args(self, vm: &VirtualMachine) -> FuncArgs {
39 let ($($n,)*) = self;
40 PosArgs::new(vec![$($n.to_pyobject(vm),)*]).into()
41 }
42
43 #[inline]
44 fn into_method_args(self, obj: PyObjectRef, vm: &VirtualMachine) -> FuncArgs {
45 let ($($n,)*) = self;
46 PosArgs::new(vec![obj, $($n.to_pyobject(vm),)*]).into()
47 }
48 }
49 };
50}
51
52into_func_args_from_tuple!((v1, T1));
53into_func_args_from_tuple!((v1, T1), (v2, T2));
54into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3));
55into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4));
56into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5));
57into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5), (v6, T6));
58#[derive(Debug, Default, Clone, Traverse)]
67pub struct FuncArgs {
68 pub args: Vec<PyObjectRef>,
69 pub kwargs: IndexMap<String, PyObjectRef>,
71}
72
73unsafe impl Traverse for IndexMap<String, PyObjectRef> {
74 fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
75 self.values().for_each(|v| v.traverse(tracer_fn));
76 }
77}
78
79impl<A> From<A> for FuncArgs
81where
82 A: Into<PosArgs>,
83{
84 fn from(args: A) -> Self {
85 Self {
86 args: args.into().into_vec(),
87 kwargs: IndexMap::new(),
88 }
89 }
90}
91
92impl From<KwArgs> for FuncArgs {
93 fn from(kwargs: KwArgs) -> Self {
94 Self {
95 args: Vec::new(),
96 kwargs: kwargs.0,
97 }
98 }
99}
100
101impl FromArgs for FuncArgs {
102 fn from_args(_vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
103 Ok(core::mem::take(args))
104 }
105}
106
107impl FuncArgs {
108 pub fn new<A, K>(args: A, kwargs: K) -> Self
109 where
110 A: Into<PosArgs>,
111 K: Into<KwArgs>,
112 {
113 let PosArgs(args) = args.into();
114 let KwArgs(kwargs) = kwargs.into();
115 Self { args, kwargs }
116 }
117
118 pub fn with_kwargs_names<A, KW>(mut args: A, kwarg_names: KW) -> Self
119 where
120 A: ExactSizeIterator<Item = PyObjectRef>,
121 KW: ExactSizeIterator<Item = String>,
122 {
123 let total_argc = args.len();
125 let kwarg_count = kwarg_names.len();
126 let pos_arg_count = total_argc - kwarg_count;
127
128 let pos_args = args.by_ref().take(pos_arg_count).collect();
129
130 let kwargs = kwarg_names.zip_eq(args).collect::<IndexMap<_, _>>();
131
132 Self {
133 args: pos_args,
134 kwargs,
135 }
136 }
137
138 pub fn from_vectorcall(
143 args: &[PyObjectRef],
144 nargs: usize,
145 kwnames: Option<&[PyObjectRef]>,
146 ) -> Self {
147 debug_assert!(nargs <= args.len());
148 debug_assert!(kwnames.is_none_or(|kw| nargs + kw.len() <= args.len()));
149 let pos_args = args[..nargs].to_vec();
150 let kwargs = if let Some(names) = kwnames {
151 names
152 .iter()
153 .zip(&args[nargs..nargs + names.len()])
154 .map(|(name, val)| {
155 let key = name
156 .downcast_ref::<crate::builtins::PyUtf8Str>()
157 .expect("kwnames must be strings")
158 .as_str()
159 .to_owned();
160 (key, val.clone())
161 })
162 .collect()
163 } else {
164 IndexMap::new()
165 };
166 Self {
167 args: pos_args,
168 kwargs,
169 }
170 }
171
172 pub fn from_vectorcall_owned(
174 mut args: Vec<PyObjectRef>,
175 nargs: usize,
176 kwnames: Option<&[PyObjectRef]>,
177 ) -> Self {
178 debug_assert!(nargs <= args.len());
179 debug_assert!(kwnames.is_none_or(|kw| nargs + kw.len() <= args.len()));
180 let kwargs = if let Some(names) = kwnames {
181 let kw_count = names.len();
182 names
183 .iter()
184 .zip(args.drain(nargs..nargs + kw_count))
185 .map(|(name, val)| {
186 let key = name
187 .downcast_ref::<crate::builtins::PyUtf8Str>()
188 .expect("kwnames must be strings")
189 .as_str()
190 .to_owned();
191 (key, val)
192 })
193 .collect()
194 } else {
195 IndexMap::new()
196 };
197 args.truncate(nargs);
198 Self { args, kwargs }
199 }
200
201 pub fn is_empty(&self) -> bool {
202 self.args.is_empty() && self.kwargs.is_empty()
203 }
204
205 pub fn prepend_arg(&mut self, item: PyObjectRef) {
206 self.args.reserve_exact(1);
207 self.args.insert(0, item)
208 }
209
210 pub fn shift(&mut self) -> PyObjectRef {
211 self.args.remove(0)
212 }
213
214 pub fn get_kwarg(&self, key: &str, default: PyObjectRef) -> PyObjectRef {
215 self.kwargs
216 .get(key)
217 .cloned()
218 .unwrap_or_else(|| default.clone())
219 }
220
221 pub fn get_optional_kwarg(&self, key: &str) -> Option<PyObjectRef> {
222 self.kwargs.get(key).cloned()
223 }
224
225 pub fn get_optional_kwarg_with_type(
226 &self,
227 key: &str,
228 ty: PyTypeRef,
229 vm: &VirtualMachine,
230 ) -> PyResult<Option<PyObjectRef>> {
231 match self.get_optional_kwarg(key) {
232 Some(kwarg) => {
233 if kwarg.fast_isinstance(&ty) {
234 Ok(Some(kwarg))
235 } else {
236 let expected_ty_name = &ty.name();
237 let kwarg_class = kwarg.class();
238 let actual_ty_name = &kwarg_class.name();
239 Err(vm.new_type_error(format!(
240 "argument of type {expected_ty_name} is required for named parameter `{key}` (got: {actual_ty_name})"
241 )))
242 }
243 }
244 None => Ok(None),
245 }
246 }
247
248 pub fn take_positional(&mut self) -> Option<PyObjectRef> {
249 if self.args.is_empty() {
250 None
251 } else {
252 Some(self.args.remove(0))
253 }
254 }
255
256 pub fn take_positional_keyword(&mut self, name: &str) -> Option<PyObjectRef> {
257 self.take_positional().or_else(|| self.take_keyword(name))
258 }
259
260 pub fn take_keyword(&mut self, name: &str) -> Option<PyObjectRef> {
261 self.kwargs.swap_remove(name)
262 }
263
264 pub fn remaining_keywords(&mut self) -> impl Iterator<Item = (String, PyObjectRef)> + '_ {
265 self.kwargs.drain(..)
266 }
267
268 pub fn bind<T: FromArgs>(mut self, vm: &VirtualMachine) -> PyResult<T> {
277 let given_args = self.args.len();
278 let bound = T::from_args(vm, &mut self)
279 .map_err(|e| e.into_exception(T::arity(), given_args, vm))?;
280
281 if !self.args.is_empty() {
282 Err(vm.new_type_error(format!(
283 "expected at most {} arguments, got {}",
284 T::arity().end(),
285 given_args,
286 )))
287 } else if let Some(err) = self.check_kwargs_empty(vm) {
288 Err(err)
289 } else {
290 Ok(bound)
291 }
292 }
293
294 pub fn check_kwargs_empty(&self, vm: &VirtualMachine) -> Option<PyBaseExceptionRef> {
295 self.kwargs
296 .keys()
297 .next()
298 .map(|k| vm.new_type_error(format!("Unexpected keyword argument {k}")))
299 }
300}
301
302pub enum ArgumentError {
305 TooFewArgs,
307 TooManyArgs,
309 InvalidKeywordArgument(String),
311 RequiredKeywordArgument(String),
313 Exception(PyBaseExceptionRef),
316}
317
318impl From<PyBaseExceptionRef> for ArgumentError {
319 fn from(ex: PyBaseExceptionRef) -> Self {
320 Self::Exception(ex)
321 }
322}
323
324impl ArgumentError {
325 fn into_exception(
326 self,
327 arity: RangeInclusive<usize>,
328 num_given: usize,
329 vm: &VirtualMachine,
330 ) -> PyBaseExceptionRef {
331 match self {
332 Self::TooFewArgs => vm.new_type_error(format!(
333 "expected at least {} arguments, got {}",
334 arity.start(),
335 num_given
336 )),
337 Self::TooManyArgs => vm.new_type_error(format!(
338 "expected at most {} arguments, got {}",
339 arity.end(),
340 num_given
341 )),
342 Self::InvalidKeywordArgument(name) => {
343 vm.new_type_error(format!("{name} is an invalid keyword argument"))
344 }
345 Self::RequiredKeywordArgument(name) => {
346 vm.new_type_error(format!("Required keyword only argument {name}"))
347 }
348 Self::Exception(ex) => ex,
349 }
350 }
351}
352
353pub trait FromArgs: Sized {
357 fn arity() -> RangeInclusive<usize> {
361 0..=0
362 }
363
364 fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError>;
366}
367
368pub trait FromArgOptional {
369 type Inner: TryFromObject;
370 fn from_inner(x: Self::Inner) -> Self;
371}
372
373impl<T: TryFromObject> FromArgOptional for OptionalArg<T> {
374 type Inner = T;
375 fn from_inner(x: T) -> Self {
376 Self::Present(x)
377 }
378}
379
380impl<T: TryFromObject> FromArgOptional for T {
381 type Inner = Self;
382 fn from_inner(x: Self) -> Self {
383 x
384 }
385}
386
387#[derive(Clone)]
402pub struct KwArgs<T = PyObjectRef>(IndexMap<String, T>);
403
404unsafe impl<T> Traverse for KwArgs<T>
405where
406 T: Traverse,
407{
408 fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
409 self.0.iter().map(|(_, v)| v.traverse(tracer_fn)).count();
410 }
411}
412
413impl<T> KwArgs<T> {
414 pub const fn new(map: IndexMap<String, T>) -> Self {
415 Self(map)
416 }
417
418 pub fn pop_kwarg(&mut self, name: &str) -> Option<T> {
419 self.0.swap_remove(name)
420 }
421
422 pub fn is_empty(self) -> bool {
423 self.0.is_empty()
424 }
425}
426
427impl<T> FromIterator<(String, T)> for KwArgs<T> {
428 fn from_iter<I: IntoIterator<Item = (String, T)>>(iter: I) -> Self {
429 Self(iter.into_iter().collect())
430 }
431}
432
433impl<T> Default for KwArgs<T> {
434 fn default() -> Self {
435 Self(IndexMap::new())
436 }
437}
438
439impl<T> FromArgs for KwArgs<T>
440where
441 T: TryFromObject,
442{
443 fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
444 let mut kwargs = IndexMap::new();
445 for (name, value) in args.remaining_keywords() {
446 kwargs.insert(name, value.try_into_value(vm)?);
447 }
448 Ok(Self(kwargs))
449 }
450}
451
452impl<T> IntoIterator for KwArgs<T> {
453 type Item = (String, T);
454 type IntoIter = indexmap::map::IntoIter<String, T>;
455
456 fn into_iter(self) -> Self::IntoIter {
457 self.0.into_iter()
458 }
459}
460
461#[derive(Clone)]
470pub struct PosArgs<T = PyObjectRef>(Vec<T>);
471
472unsafe impl<T> Traverse for PosArgs<T>
473where
474 T: Traverse,
475{
476 fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
477 self.0.traverse(tracer_fn)
478 }
479}
480
481impl<T> PosArgs<T> {
482 pub const fn new(args: Vec<T>) -> Self {
483 Self(args)
484 }
485
486 pub fn into_vec(self) -> Vec<T> {
487 self.0
488 }
489
490 pub fn iter(&self) -> core::slice::Iter<'_, T> {
491 self.0.iter()
492 }
493}
494
495impl<T> From<Vec<T>> for PosArgs<T> {
496 fn from(v: Vec<T>) -> Self {
497 Self(v)
498 }
499}
500
501impl From<()> for PosArgs<PyObjectRef> {
502 fn from(_args: ()) -> Self {
503 Self(Vec::new())
504 }
505}
506
507impl<T> AsRef<[T]> for PosArgs<T> {
508 fn as_ref(&self) -> &[T] {
509 &self.0
510 }
511}
512
513impl<T: PyPayload> PosArgs<PyRef<T>> {
514 pub fn into_tuple(self, vm: &VirtualMachine) -> PyTupleRef {
515 vm.ctx
516 .new_tuple(self.0.into_iter().map(Into::into).collect())
517 }
518}
519
520impl<T> FromArgs for PosArgs<T>
521where
522 T: TryFromObject,
523{
524 fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
525 let mut varargs = Vec::new();
526 while let Some(value) = args.take_positional() {
527 varargs.push(value.try_into_value(vm)?);
528 }
529 Ok(Self(varargs))
530 }
531}
532
533impl<T> IntoIterator for PosArgs<T> {
534 type Item = T;
535 type IntoIter = alloc::vec::IntoIter<T>;
536
537 fn into_iter(self) -> Self::IntoIter {
538 self.0.into_iter()
539 }
540}
541
542impl<T> FromArgs for T
543where
544 T: TryFromObject,
545{
546 fn arity() -> RangeInclusive<usize> {
547 1..=1
548 }
549
550 fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
551 let value = args.take_positional().ok_or(ArgumentError::TooFewArgs)?;
552 Ok(value.try_into_value(vm)?)
553 }
554}
555
556#[derive(Debug, result_like::OptionLike, is_macro::Is)]
560pub enum OptionalArg<T = PyObjectRef> {
561 Present(T),
562 Missing,
563}
564
565unsafe impl<T> Traverse for OptionalArg<T>
566where
567 T: Traverse,
568{
569 fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
570 match self {
571 Self::Present(o) => o.traverse(tracer_fn),
572 Self::Missing => (),
573 }
574 }
575}
576
577impl OptionalArg<PyObjectRef> {
578 pub fn unwrap_or_none(self, vm: &VirtualMachine) -> PyObjectRef {
579 self.unwrap_or_else(|| vm.ctx.none())
580 }
581}
582
583pub type OptionalOption<T = PyObjectRef> = OptionalArg<Option<T>>;
584
585impl<T> OptionalOption<T> {
586 #[inline]
587 pub fn flatten(self) -> Option<T> {
588 self.into_option().flatten()
589 }
590}
591
592impl<T> FromArgs for OptionalArg<T>
593where
594 T: TryFromObject,
595{
596 fn arity() -> RangeInclusive<usize> {
597 0..=1
598 }
599
600 fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
601 let r = if let Some(value) = args.take_positional() {
602 Self::Present(value.try_into_value(vm)?)
603 } else {
604 Self::Missing
605 };
606 Ok(r)
607 }
608}
609
610impl FromArgs for () {
613 fn from_args(_vm: &VirtualMachine, _args: &mut FuncArgs) -> Result<Self, ArgumentError> {
614 Ok(())
615 }
616}
617
618macro_rules! tuple_from_py_func_args {
625 ($($T:ident),+) => {
626 impl<$($T),+> FromArgs for ($($T,)+)
627 where
628 $($T: FromArgs),+
629 {
630 fn arity() -> RangeInclusive<usize> {
631 let mut min = 0;
632 let mut max = 0;
633 $(
634 let (start, end) = $T::arity().into_inner();
635 min += start;
636 max += end;
637 )+
638 min..=max
639 }
640
641 fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
642 Ok(($($T::from_args(vm, args)?,)+))
643 }
644 }
645 };
646}
647
648tuple_from_py_func_args!(A);
652tuple_from_py_func_args!(A, B);
653tuple_from_py_func_args!(A, B, C);
654tuple_from_py_func_args!(A, B, C, D);
655tuple_from_py_func_args!(A, B, C, D, E);
656tuple_from_py_func_args!(A, B, C, D, E, F);
657tuple_from_py_func_args!(A, B, C, D, E, F, G);
658tuple_from_py_func_args!(A, B, C, D, E, F, G, H);