rustpython_vm/builtins/
descriptor.rs

1use super::{PyStr, PyStrInterned, PyType};
2use crate::{
3    builtins::{builtin_func::PyNativeMethod, type_, PyTypeRef},
4    class::PyClassImpl,
5    function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue},
6    types::{Callable, GetDescriptor, Representable, Unconstructible},
7    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
8};
9use rustpython_common::lock::PyRwLock;
10
11#[derive(Debug)]
12pub struct PyDescriptor {
13    pub typ: &'static Py<PyType>,
14    pub name: &'static PyStrInterned,
15    pub qualname: PyRwLock<Option<String>>,
16}
17
18#[derive(Debug)]
19pub struct PyDescriptorOwned {
20    pub typ: PyRef<PyType>,
21    pub name: &'static PyStrInterned,
22    pub qualname: PyRwLock<Option<String>>,
23}
24
25#[pyclass(name = "method_descriptor", module = false)]
26pub struct PyMethodDescriptor {
27    pub common: PyDescriptor,
28    pub method: &'static PyMethodDef,
29    // vectorcall: vectorcallfunc,
30    pub objclass: &'static Py<PyType>, // TODO: move to tp_members
31}
32
33impl PyMethodDescriptor {
34    pub fn new(method: &'static PyMethodDef, typ: &'static Py<PyType>, ctx: &Context) -> Self {
35        Self {
36            common: PyDescriptor {
37                typ,
38                name: ctx.intern_str(method.name),
39                qualname: PyRwLock::new(None),
40            },
41            method,
42            objclass: typ,
43        }
44    }
45}
46
47impl PyPayload for PyMethodDescriptor {
48    fn class(ctx: &Context) -> &'static Py<PyType> {
49        ctx.types.method_descriptor_type
50    }
51}
52
53impl std::fmt::Debug for PyMethodDescriptor {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        write!(f, "method descriptor for '{}'", self.common.name)
56    }
57}
58
59impl GetDescriptor for PyMethodDescriptor {
60    fn descr_get(
61        zelf: PyObjectRef,
62        obj: Option<PyObjectRef>,
63        cls: Option<PyObjectRef>,
64        vm: &VirtualMachine,
65    ) -> PyResult {
66        let descr = Self::_as_pyref(&zelf, vm).unwrap();
67        let bound = match obj {
68            Some(obj) => {
69                if descr.method.flags.contains(PyMethodFlags::METHOD) {
70                    if cls.map_or(false, |c| c.fast_isinstance(vm.ctx.types.type_type)) {
71                        obj
72                    } else {
73                        return Err(vm.new_type_error(format!(
74                            "descriptor '{}' needs a type, not '{}', as arg 2",
75                            descr.common.name.as_str(),
76                            obj.class().name()
77                        )));
78                    }
79                } else if descr.method.flags.contains(PyMethodFlags::CLASS) {
80                    obj.class().to_owned().into()
81                } else {
82                    unimplemented!()
83                }
84            }
85            None if descr.method.flags.contains(PyMethodFlags::CLASS) => cls.unwrap(),
86            None => return Ok(zelf),
87        };
88        // Ok(descr.method.build_bound_method(&vm.ctx, bound, class).into())
89        Ok(descr.bind(bound, &vm.ctx).into())
90    }
91}
92
93impl Callable for PyMethodDescriptor {
94    type Args = FuncArgs;
95    #[inline]
96    fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
97        (zelf.method.func)(vm, args)
98    }
99}
100
101impl PyMethodDescriptor {
102    pub fn bind(&self, obj: PyObjectRef, ctx: &Context) -> PyRef<PyNativeMethod> {
103        self.method.build_bound_method(ctx, obj, self.common.typ)
104    }
105}
106
107#[pyclass(
108    with(GetDescriptor, Callable, Unconstructible, Representable),
109    flags(METHOD_DESCRIPTOR)
110)]
111impl PyMethodDescriptor {
112    #[pygetset(magic)]
113    fn name(&self) -> &'static PyStrInterned {
114        self.common.name
115    }
116    #[pygetset(magic)]
117    fn qualname(&self) -> String {
118        format!("{}.{}", self.common.typ.name(), &self.common.name)
119    }
120    #[pygetset(magic)]
121    fn doc(&self) -> Option<&'static str> {
122        self.method.doc
123    }
124    #[pygetset(magic)]
125    fn text_signature(&self) -> Option<String> {
126        self.method.doc.and_then(|doc| {
127            type_::get_text_signature_from_internal_doc(self.method.name, doc)
128                .map(|signature| signature.to_string())
129        })
130    }
131    #[pygetset(magic)]
132    fn objclass(&self) -> PyTypeRef {
133        self.objclass.to_owned()
134    }
135    #[pymethod(magic)]
136    fn reduce(
137        &self,
138        vm: &VirtualMachine,
139    ) -> (Option<PyObjectRef>, (Option<PyObjectRef>, &'static str)) {
140        let builtins_getattr = vm.builtins.get_attr("getattr", vm).ok();
141        let classname = vm.builtins.get_attr(&self.common.typ.__name__(vm), vm).ok();
142        (builtins_getattr, (classname, self.method.name))
143    }
144}
145
146impl Representable for PyMethodDescriptor {
147    #[inline]
148    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
149        Ok(format!(
150            "<method '{}' of '{}' objects>",
151            &zelf.method.name,
152            zelf.common.typ.name()
153        ))
154    }
155}
156
157impl Unconstructible for PyMethodDescriptor {}
158
159#[derive(Debug)]
160pub enum MemberKind {
161    Bool = 14,
162    ObjectEx = 16,
163}
164
165pub type MemberSetterFunc = Option<fn(&VirtualMachine, PyObjectRef, PySetterValue) -> PyResult<()>>;
166
167pub enum MemberGetter {
168    Getter(fn(&VirtualMachine, PyObjectRef) -> PyResult),
169    Offset(usize),
170}
171
172pub enum MemberSetter {
173    Setter(MemberSetterFunc),
174    Offset(usize),
175}
176
177pub struct PyMemberDef {
178    pub name: String,
179    pub kind: MemberKind,
180    pub getter: MemberGetter,
181    pub setter: MemberSetter,
182    pub doc: Option<String>,
183}
184
185impl PyMemberDef {
186    fn get(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
187        match self.getter {
188            MemberGetter::Getter(getter) => (getter)(vm, obj),
189            MemberGetter::Offset(offset) => get_slot_from_object(obj, offset, self, vm),
190        }
191    }
192
193    fn set(
194        &self,
195        obj: PyObjectRef,
196        value: PySetterValue<PyObjectRef>,
197        vm: &VirtualMachine,
198    ) -> PyResult<()> {
199        match self.setter {
200            MemberSetter::Setter(setter) => match setter {
201                Some(setter) => (setter)(vm, obj, value),
202                None => Err(vm.new_attribute_error("readonly attribute".to_string())),
203            },
204            MemberSetter::Offset(offset) => set_slot_at_object(obj, offset, self, value, vm),
205        }
206    }
207}
208
209impl std::fmt::Debug for PyMemberDef {
210    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211        f.debug_struct("PyMemberDef")
212            .field("name", &self.name)
213            .field("kind", &self.kind)
214            .field("doc", &self.doc)
215            .finish()
216    }
217}
218
219// PyMemberDescrObject in CPython
220#[pyclass(name = "member_descriptor", module = false)]
221#[derive(Debug)]
222pub struct PyMemberDescriptor {
223    pub common: PyDescriptorOwned,
224    pub member: PyMemberDef,
225}
226
227impl PyPayload for PyMemberDescriptor {
228    fn class(ctx: &Context) -> &'static Py<PyType> {
229        ctx.types.member_descriptor_type
230    }
231}
232
233fn calculate_qualname(descr: &PyDescriptorOwned, vm: &VirtualMachine) -> PyResult<Option<String>> {
234    if let Some(qualname) = vm.get_attribute_opt(descr.typ.clone().into(), "__qualname__")? {
235        let str = qualname.downcast::<PyStr>().map_err(|_| {
236            vm.new_type_error(
237                "<descriptor>.__objclass__.__qualname__ is not a unicode object".to_owned(),
238            )
239        })?;
240        Ok(Some(format!("{}.{}", str, descr.name)))
241    } else {
242        Ok(None)
243    }
244}
245
246#[pyclass(with(GetDescriptor, Unconstructible, Representable), flags(BASETYPE))]
247impl PyMemberDescriptor {
248    #[pygetset(magic)]
249    fn doc(&self) -> Option<String> {
250        self.member.doc.to_owned()
251    }
252
253    #[pygetset(magic)]
254    fn qualname(&self, vm: &VirtualMachine) -> PyResult<Option<String>> {
255        let qualname = self.common.qualname.read();
256        Ok(if qualname.is_none() {
257            drop(qualname);
258            let calculated = calculate_qualname(&self.common, vm)?;
259            calculated.clone_into(&mut self.common.qualname.write());
260            calculated
261        } else {
262            qualname.to_owned()
263        })
264    }
265
266    #[pyslot]
267    fn descr_set(
268        zelf: &PyObject,
269        obj: PyObjectRef,
270        value: PySetterValue<PyObjectRef>,
271        vm: &VirtualMachine,
272    ) -> PyResult<()> {
273        let zelf = Self::_as_pyref(zelf, vm)?;
274        zelf.member.set(obj, value, vm)
275    }
276}
277
278// PyMember_GetOne
279fn get_slot_from_object(
280    obj: PyObjectRef,
281    offset: usize,
282    member: &PyMemberDef,
283    vm: &VirtualMachine,
284) -> PyResult {
285    let slot = match member.kind {
286        MemberKind::Bool => obj
287            .get_slot(offset)
288            .unwrap_or_else(|| vm.ctx.new_bool(false).into()),
289        MemberKind::ObjectEx => obj.get_slot(offset).ok_or_else(|| {
290            vm.new_attribute_error(format!(
291                "'{}' object has no attribute '{}'",
292                obj.class().name(),
293                member.name
294            ))
295        })?,
296    };
297    Ok(slot)
298}
299
300// PyMember_SetOne
301fn set_slot_at_object(
302    obj: PyObjectRef,
303    offset: usize,
304    member: &PyMemberDef,
305    value: PySetterValue,
306    vm: &VirtualMachine,
307) -> PyResult<()> {
308    match member.kind {
309        MemberKind::Bool => {
310            match value {
311                PySetterValue::Assign(v) => {
312                    if !v.class().is(vm.ctx.types.bool_type) {
313                        return Err(
314                            vm.new_type_error("attribute value type must be bool".to_owned())
315                        );
316                    }
317
318                    obj.set_slot(offset, Some(v))
319                }
320                PySetterValue::Delete => obj.set_slot(offset, None),
321            };
322        }
323        MemberKind::ObjectEx => {
324            let value = match value {
325                PySetterValue::Assign(v) => Some(v),
326                PySetterValue::Delete => None,
327            };
328            obj.set_slot(offset, value);
329        }
330    }
331
332    Ok(())
333}
334
335impl Unconstructible for PyMemberDescriptor {}
336
337impl Representable for PyMemberDescriptor {
338    #[inline]
339    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
340        Ok(format!(
341            "<member '{}' of '{}' objects>",
342            zelf.common.name,
343            zelf.common.typ.name(),
344        ))
345    }
346}
347
348impl GetDescriptor for PyMemberDescriptor {
349    fn descr_get(
350        zelf: PyObjectRef,
351        obj: Option<PyObjectRef>,
352        _cls: Option<PyObjectRef>,
353        vm: &VirtualMachine,
354    ) -> PyResult {
355        match obj {
356            Some(x) => {
357                let zelf = Self::_as_pyref(&zelf, vm)?;
358                zelf.member.get(x, vm)
359            }
360            None => Ok(zelf),
361        }
362    }
363}
364
365pub fn init(ctx: &Context) {
366    PyMemberDescriptor::extend_class(ctx, ctx.types.member_descriptor_type);
367    PyMethodDescriptor::extend_class(ctx, ctx.types.method_descriptor_type);
368}