Skip to main content

rustpython_vm/function/
protocol.rs

1use super::IntoFuncArgs;
2use crate::{
3    AsObject, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine,
4    builtins::{PyDictRef, iter::PySequenceIterator},
5    convert::ToPyObject,
6    object::{Traverse, TraverseFn},
7    protocol::{PyIter, PyIterIter, PyMapping},
8    types::GenericMethod,
9};
10use core::{borrow::Borrow, marker::PhantomData};
11
12#[derive(Clone, Traverse)]
13pub struct ArgCallable {
14    obj: PyObjectRef,
15    #[pytraverse(skip)]
16    call: GenericMethod,
17}
18
19impl ArgCallable {
20    #[inline(always)]
21    pub fn invoke(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult {
22        let args = args.into_args(vm);
23        (self.call)(&self.obj, args, vm)
24    }
25}
26
27impl core::fmt::Debug for ArgCallable {
28    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
29        f.debug_struct("ArgCallable")
30            .field("obj", &self.obj)
31            .field("call", &format!("{:08x}", self.call as usize))
32            .finish()
33    }
34}
35
36impl Borrow<PyObject> for ArgCallable {
37    #[inline(always)]
38    fn borrow(&self) -> &PyObject {
39        &self.obj
40    }
41}
42
43impl AsRef<PyObject> for ArgCallable {
44    #[inline(always)]
45    fn as_ref(&self) -> &PyObject {
46        &self.obj
47    }
48}
49
50impl From<ArgCallable> for PyObjectRef {
51    #[inline(always)]
52    fn from(value: ArgCallable) -> Self {
53        value.obj
54    }
55}
56
57impl TryFromObject for ArgCallable {
58    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
59        let Some(callable) = obj.to_callable() else {
60            return Err(vm.new_type_error(format!(
61                "'{}' object is not callable",
62                obj.class().slot_name()
63            )));
64        };
65        let call = callable.call;
66        Ok(Self { obj, call })
67    }
68}
69
70/// An iterable Python object.
71///
72/// `ArgIterable` implements `FromArgs` so that a built-in function can accept
73/// an object that is required to conform to the Python iterator protocol.
74///
75/// ArgIterable can optionally perform type checking and conversions on iterated
76/// objects using a generic type parameter that implements `TryFromObject`.
77pub struct ArgIterable<T = PyObjectRef> {
78    iterable: PyObjectRef,
79    iter_fn: Option<crate::types::IterFunc>,
80    _item: PhantomData<T>,
81}
82
83unsafe impl<T: Traverse> Traverse for ArgIterable<T> {
84    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
85        self.iterable.traverse(tracer_fn)
86    }
87}
88
89impl<T> ArgIterable<T> {
90    #[must_use]
91    pub(crate) fn as_object(&self) -> &PyObject {
92        &self.iterable
93    }
94
95    /// This object's iterator.
96    ///
97    /// This operation may fail if an exception is raised while invoking the
98    /// `__iter__` method of the iterable object.
99    fn get_iter(&self, vm: &VirtualMachine) -> PyResult<PyIter> {
100        Ok(PyIter::new(match self.iter_fn {
101            Some(f) => f(self.iterable.clone(), vm)?,
102            None => PySequenceIterator::new(self.iterable.clone(), vm)?.into_pyobject(vm),
103        }))
104    }
105
106    /// Returns an iterator over this sequence of objects. See [`PyIter::iter`]
107    /// for why it does not ask how long the iterator is.
108    ///
109    /// This operation may fail if an exception is raised while invoking the
110    /// `__iter__` method of the iterable object.
111    pub fn iter<'a>(&self, vm: &'a VirtualMachine) -> PyResult<PyIterIter<'a, T>> {
112        Ok(self.get_iter(vm)?.into_iter(vm))
113    }
114
115    /// [`Self::iter`] for a caller that fills a sized container from the
116    /// iterator, the way `PySequence_Fast()` does.
117    pub fn iter_sized<'a>(&self, vm: &'a VirtualMachine) -> PyResult<PyIterIter<'a, T>> {
118        self.get_iter(vm)?.into_iter_sized(vm)
119    }
120}
121
122impl<T> TryFromObject for ArgIterable<T>
123where
124    T: TryFromObject,
125{
126    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
127        let cls = obj.class();
128        let iter_fn = cls.slots.iter.load();
129        if iter_fn.is_none() && !cls.has_attr(identifier!(vm, __getitem__)) {
130            return Err(vm.new_type_error(format!("'{}' object is not iterable", cls.slot_name())));
131        }
132        Ok(Self {
133            iterable: obj,
134            iter_fn,
135            _item: PhantomData,
136        })
137    }
138}
139
140#[derive(Debug, Clone, Traverse)]
141pub struct ArgMapping {
142    obj: PyObjectRef,
143}
144
145impl ArgMapping {
146    #[inline]
147    #[must_use]
148    pub const fn new(obj: PyObjectRef) -> Self {
149        Self { obj }
150    }
151
152    #[inline(always)]
153    #[must_use]
154    pub fn from_dict_exact(dict: PyDictRef) -> Self {
155        Self { obj: dict.into() }
156    }
157
158    #[inline(always)]
159    #[must_use]
160    pub fn obj(&self) -> &PyObject {
161        &self.obj
162    }
163
164    #[inline(always)]
165    #[must_use]
166    pub fn mapping(&self) -> PyMapping<'_> {
167        self.obj.mapping_unchecked()
168    }
169}
170
171impl Borrow<PyObject> for ArgMapping {
172    #[inline(always)]
173    fn borrow(&self) -> &PyObject {
174        &self.obj
175    }
176}
177
178impl AsRef<PyObject> for ArgMapping {
179    #[inline(always)]
180    fn as_ref(&self) -> &PyObject {
181        &self.obj
182    }
183}
184
185impl From<ArgMapping> for PyObjectRef {
186    #[inline(always)]
187    fn from(value: ArgMapping) -> Self {
188        value.obj
189    }
190}
191
192impl ToPyObject for ArgMapping {
193    #[inline(always)]
194    fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
195        self.obj
196    }
197}
198
199impl TryFromObject for ArgMapping {
200    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
201        let _mapping = obj.try_mapping(vm)?;
202        Ok(Self { obj })
203    }
204}
205
206// this is not strictly related to PySequence protocol.
207#[derive(Clone)]
208pub struct ArgSequence<T = PyObjectRef>(Vec<T>);
209
210unsafe impl<T: Traverse> Traverse for ArgSequence<T> {
211    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
212        self.0.traverse(tracer_fn);
213    }
214}
215
216impl<T> ArgSequence<T> {
217    #[inline(always)]
218    #[must_use]
219    pub fn into_vec(self) -> Vec<T> {
220        self.0
221    }
222    #[inline(always)]
223    #[must_use]
224    pub fn as_slice(&self) -> &[T] {
225        &self.0
226    }
227}
228
229impl<T> core::ops::Deref for ArgSequence<T> {
230    type Target = [T];
231    #[inline(always)]
232    fn deref(&self) -> &[T] {
233        self.as_slice()
234    }
235}
236
237impl<'a, T> IntoIterator for &'a ArgSequence<T> {
238    type Item = &'a T;
239    type IntoIter = core::slice::Iter<'a, T>;
240    fn into_iter(self) -> Self::IntoIter {
241        self.iter()
242    }
243}
244impl<T> IntoIterator for ArgSequence<T> {
245    type Item = T;
246    type IntoIter = alloc::vec::IntoIter<T>;
247    fn into_iter(self) -> Self::IntoIter {
248        self.0.into_iter()
249    }
250}
251
252impl<T: TryFromObject> TryFromObject for ArgSequence<T> {
253    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
254        obj.try_to_value(vm).map(Self)
255    }
256}