rustpython_vm/builtins/
property.rs

1/*! Python `property` descriptor class.
2
3*/
4use super::{PyStrRef, PyType, PyTypeRef};
5use crate::common::lock::PyRwLock;
6use crate::function::{IntoFuncArgs, PosArgs};
7use crate::{
8    class::PyClassImpl,
9    function::{FuncArgs, PySetterValue},
10    types::{Constructor, GetDescriptor, Initializer},
11    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
12};
13
14#[pyclass(module = false, name = "property", traverse)]
15#[derive(Debug)]
16pub struct PyProperty {
17    getter: PyRwLock<Option<PyObjectRef>>,
18    setter: PyRwLock<Option<PyObjectRef>>,
19    deleter: PyRwLock<Option<PyObjectRef>>,
20    doc: PyRwLock<Option<PyObjectRef>>,
21    name: PyRwLock<Option<PyObjectRef>>,
22}
23
24impl PyPayload for PyProperty {
25    fn class(ctx: &Context) -> &'static Py<PyType> {
26        ctx.types.property_type
27    }
28}
29
30#[derive(FromArgs)]
31pub struct PropertyArgs {
32    #[pyarg(any, default)]
33    fget: Option<PyObjectRef>,
34    #[pyarg(any, default)]
35    fset: Option<PyObjectRef>,
36    #[pyarg(any, default)]
37    fdel: Option<PyObjectRef>,
38    #[pyarg(any, default)]
39    doc: Option<PyObjectRef>,
40    #[pyarg(any, default)]
41    name: Option<PyStrRef>,
42}
43
44impl GetDescriptor for PyProperty {
45    fn descr_get(
46        zelf_obj: PyObjectRef,
47        obj: Option<PyObjectRef>,
48        _cls: Option<PyObjectRef>,
49        vm: &VirtualMachine,
50    ) -> PyResult {
51        let (zelf, obj) = Self::_unwrap(&zelf_obj, obj, vm)?;
52        if vm.is_none(&obj) {
53            Ok(zelf_obj)
54        } else if let Some(getter) = zelf.getter.read().as_ref() {
55            getter.call((obj,), vm)
56        } else {
57            Err(vm.new_attribute_error("property has no getter".to_string()))
58        }
59    }
60}
61
62#[pyclass(with(Constructor, Initializer, GetDescriptor), flags(BASETYPE))]
63impl PyProperty {
64    // Descriptor methods
65
66    #[pyslot]
67    fn descr_set(
68        zelf: &PyObject,
69        obj: PyObjectRef,
70        value: PySetterValue,
71        vm: &VirtualMachine,
72    ) -> PyResult<()> {
73        let zelf = zelf.try_to_ref::<Self>(vm)?;
74        match value {
75            PySetterValue::Assign(value) => {
76                if let Some(setter) = zelf.setter.read().as_ref() {
77                    setter.call((obj, value), vm).map(drop)
78                } else {
79                    Err(vm.new_attribute_error("property has no setter".to_owned()))
80                }
81            }
82            PySetterValue::Delete => {
83                if let Some(deleter) = zelf.deleter.read().as_ref() {
84                    deleter.call((obj,), vm).map(drop)
85                } else {
86                    Err(vm.new_attribute_error("property has no deleter".to_owned()))
87                }
88            }
89        }
90    }
91    #[pymethod]
92    fn __set__(
93        zelf: PyObjectRef,
94        obj: PyObjectRef,
95        value: PyObjectRef,
96        vm: &VirtualMachine,
97    ) -> PyResult<()> {
98        Self::descr_set(&zelf, obj, PySetterValue::Assign(value), vm)
99    }
100    #[pymethod]
101    fn __delete__(zelf: PyObjectRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
102        Self::descr_set(&zelf, obj, PySetterValue::Delete, vm)
103    }
104
105    // Access functions
106
107    #[pygetset]
108    fn fget(&self) -> Option<PyObjectRef> {
109        self.getter.read().clone()
110    }
111
112    #[pygetset]
113    fn fset(&self) -> Option<PyObjectRef> {
114        self.setter.read().clone()
115    }
116
117    #[pygetset]
118    fn fdel(&self) -> Option<PyObjectRef> {
119        self.deleter.read().clone()
120    }
121
122    fn doc_getter(&self) -> Option<PyObjectRef> {
123        self.doc.read().clone()
124    }
125    fn doc_setter(&self, value: Option<PyObjectRef>) {
126        *self.doc.write() = value;
127    }
128
129    #[pymethod(magic)]
130    fn set_name(&self, args: PosArgs, vm: &VirtualMachine) -> PyResult<()> {
131        let func_args = args.into_args(vm);
132        let func_args_len = func_args.args.len();
133        let (_owner, name): (PyObjectRef, PyObjectRef) = func_args.bind(vm).map_err(|_e| {
134            vm.new_type_error(format!(
135                "__set_name__() takes 2 positional arguments but {} were given",
136                func_args_len
137            ))
138        })?;
139
140        *self.name.write() = Some(name);
141
142        Ok(())
143    }
144
145    // Python builder functions
146
147    #[pymethod]
148    fn getter(
149        zelf: PyRef<Self>,
150        getter: Option<PyObjectRef>,
151        vm: &VirtualMachine,
152    ) -> PyResult<PyRef<Self>> {
153        PyProperty {
154            getter: PyRwLock::new(getter.or_else(|| zelf.fget())),
155            setter: PyRwLock::new(zelf.fset()),
156            deleter: PyRwLock::new(zelf.fdel()),
157            doc: PyRwLock::new(None),
158            name: PyRwLock::new(None),
159        }
160        .into_ref_with_type(vm, zelf.class().to_owned())
161    }
162
163    #[pymethod]
164    fn setter(
165        zelf: PyRef<Self>,
166        setter: Option<PyObjectRef>,
167        vm: &VirtualMachine,
168    ) -> PyResult<PyRef<Self>> {
169        PyProperty {
170            getter: PyRwLock::new(zelf.fget()),
171            setter: PyRwLock::new(setter.or_else(|| zelf.fset())),
172            deleter: PyRwLock::new(zelf.fdel()),
173            doc: PyRwLock::new(None),
174            name: PyRwLock::new(None),
175        }
176        .into_ref_with_type(vm, zelf.class().to_owned())
177    }
178
179    #[pymethod]
180    fn deleter(
181        zelf: PyRef<Self>,
182        deleter: Option<PyObjectRef>,
183        vm: &VirtualMachine,
184    ) -> PyResult<PyRef<Self>> {
185        PyProperty {
186            getter: PyRwLock::new(zelf.fget()),
187            setter: PyRwLock::new(zelf.fset()),
188            deleter: PyRwLock::new(deleter.or_else(|| zelf.fdel())),
189            doc: PyRwLock::new(None),
190            name: PyRwLock::new(None),
191        }
192        .into_ref_with_type(vm, zelf.class().to_owned())
193    }
194
195    #[pygetset(magic)]
196    fn isabstractmethod(&self, vm: &VirtualMachine) -> PyObjectRef {
197        let getter_abstract = match self.getter.read().to_owned() {
198            Some(getter) => getter
199                .get_attr("__isabstractmethod__", vm)
200                .unwrap_or_else(|_| vm.ctx.new_bool(false).into()),
201            _ => vm.ctx.new_bool(false).into(),
202        };
203        let setter_abstract = match self.setter.read().to_owned() {
204            Some(setter) => setter
205                .get_attr("__isabstractmethod__", vm)
206                .unwrap_or_else(|_| vm.ctx.new_bool(false).into()),
207            _ => vm.ctx.new_bool(false).into(),
208        };
209        vm._or(&setter_abstract, &getter_abstract)
210            .unwrap_or_else(|_| vm.ctx.new_bool(false).into())
211    }
212
213    #[pygetset(magic, setter)]
214    fn set_isabstractmethod(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
215        if let Some(getter) = self.getter.read().to_owned() {
216            getter.set_attr("__isabstractmethod__", value, vm)?;
217        }
218        Ok(())
219    }
220}
221
222impl Constructor for PyProperty {
223    type Args = FuncArgs;
224
225    fn py_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
226        PyProperty {
227            getter: PyRwLock::new(None),
228            setter: PyRwLock::new(None),
229            deleter: PyRwLock::new(None),
230            doc: PyRwLock::new(None),
231            name: PyRwLock::new(None),
232        }
233        .into_ref_with_type(vm, cls)
234        .map(Into::into)
235    }
236}
237
238impl Initializer for PyProperty {
239    type Args = PropertyArgs;
240
241    fn init(zelf: PyRef<Self>, args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
242        *zelf.getter.write() = args.fget;
243        *zelf.setter.write() = args.fset;
244        *zelf.deleter.write() = args.fdel;
245        *zelf.doc.write() = args.doc;
246        *zelf.name.write() = args.name.map(|a| a.as_object().to_owned());
247
248        Ok(())
249    }
250}
251
252pub(crate) fn init(context: &Context) {
253    PyProperty::extend_class(context, context.types.property_type);
254
255    // This is a bit unfortunate, but this instance attribute overlaps with the
256    // class __doc__ string..
257    extend_class!(context, context.types.property_type, {
258        "__doc__" => context.new_getset(
259            "__doc__",
260            context.types.property_type,
261            PyProperty::doc_getter,
262            PyProperty::doc_setter,
263        ),
264    });
265}