1use 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 #[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 #[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 #[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 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}