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(
61                vm.new_type_error(format!("'{}' object is not callable", obj.class().name()))
62            );
63        };
64        let call = callable.call;
65        Ok(Self { obj, call })
66    }
67}
68
69/// An iterable Python object.
70///
71/// `ArgIterable` implements `FromArgs` so that a built-in function can accept
72/// an object that is required to conform to the Python iterator protocol.
73///
74/// ArgIterable can optionally perform type checking and conversions on iterated
75/// objects using a generic type parameter that implements `TryFromObject`.
76pub struct ArgIterable<T = PyObjectRef> {
77    iterable: PyObjectRef,
78    iter_fn: Option<crate::types::IterFunc>,
79    _item: PhantomData<T>,
80}
81
82unsafe impl<T: Traverse> Traverse for ArgIterable<T> {
83    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
84        self.iterable.traverse(tracer_fn)
85    }
86}
87
88impl<T> ArgIterable<T> {
89    /// Returns an iterator over this sequence of objects.
90    ///
91    /// This operation may fail if an exception is raised while invoking the
92    /// `__iter__` method of the iterable object.
93    pub fn iter<'a>(&self, vm: &'a VirtualMachine) -> PyResult<PyIterIter<'a, T>> {
94        let iter = PyIter::new(match self.iter_fn {
95            Some(f) => f(self.iterable.clone(), vm)?,
96            None => PySequenceIterator::new(self.iterable.clone(), vm)?.into_pyobject(vm),
97        });
98        iter.into_iter(vm)
99    }
100}
101
102impl<T> TryFromObject for ArgIterable<T>
103where
104    T: TryFromObject,
105{
106    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
107        let cls = obj.class();
108        let iter_fn = cls.slots.iter.load();
109        if iter_fn.is_none() && !cls.has_attr(identifier!(vm, __getitem__)) {
110            return Err(vm.new_type_error(format!("'{}' object is not iterable", cls.name())));
111        }
112        Ok(Self {
113            iterable: obj,
114            iter_fn,
115            _item: PhantomData,
116        })
117    }
118}
119
120#[derive(Debug, Clone, Traverse)]
121pub struct ArgMapping {
122    obj: PyObjectRef,
123}
124
125impl ArgMapping {
126    #[inline]
127    pub const fn new(obj: PyObjectRef) -> Self {
128        Self { obj }
129    }
130
131    #[inline(always)]
132    pub fn from_dict_exact(dict: PyDictRef) -> Self {
133        Self { obj: dict.into() }
134    }
135
136    #[inline(always)]
137    pub fn obj(&self) -> &PyObject {
138        &self.obj
139    }
140
141    #[inline(always)]
142    pub fn mapping(&self) -> PyMapping<'_> {
143        self.obj.mapping_unchecked()
144    }
145}
146
147impl Borrow<PyObject> for ArgMapping {
148    #[inline(always)]
149    fn borrow(&self) -> &PyObject {
150        &self.obj
151    }
152}
153
154impl AsRef<PyObject> for ArgMapping {
155    #[inline(always)]
156    fn as_ref(&self) -> &PyObject {
157        &self.obj
158    }
159}
160
161impl From<ArgMapping> for PyObjectRef {
162    #[inline(always)]
163    fn from(value: ArgMapping) -> Self {
164        value.obj
165    }
166}
167
168impl ToPyObject for ArgMapping {
169    #[inline(always)]
170    fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
171        self.obj
172    }
173}
174
175impl TryFromObject for ArgMapping {
176    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
177        let _mapping = obj.try_mapping(vm)?;
178        Ok(Self { obj })
179    }
180}
181
182// this is not strictly related to PySequence protocol.
183#[derive(Clone)]
184pub struct ArgSequence<T = PyObjectRef>(Vec<T>);
185
186unsafe impl<T: Traverse> Traverse for ArgSequence<T> {
187    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
188        self.0.traverse(tracer_fn);
189    }
190}
191
192impl<T> ArgSequence<T> {
193    #[inline(always)]
194    pub fn into_vec(self) -> Vec<T> {
195        self.0
196    }
197    #[inline(always)]
198    pub fn as_slice(&self) -> &[T] {
199        &self.0
200    }
201}
202
203impl<T> core::ops::Deref for ArgSequence<T> {
204    type Target = [T];
205    #[inline(always)]
206    fn deref(&self) -> &[T] {
207        self.as_slice()
208    }
209}
210
211impl<'a, T> IntoIterator for &'a ArgSequence<T> {
212    type Item = &'a T;
213    type IntoIter = core::slice::Iter<'a, T>;
214    fn into_iter(self) -> Self::IntoIter {
215        self.iter()
216    }
217}
218impl<T> IntoIterator for ArgSequence<T> {
219    type Item = T;
220    type IntoIter = alloc::vec::IntoIter<T>;
221    fn into_iter(self) -> Self::IntoIter {
222        self.0.into_iter()
223    }
224}
225
226impl<T: TryFromObject> TryFromObject for ArgSequence<T> {
227    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
228        obj.try_to_value(vm).map(Self)
229    }
230}