1use super::{PyStrRef, PyType};
5use crate::common::lock::PyRwLock;
6use crate::function::{IntoFuncArgs, PosArgs};
7use crate::{
8 AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
9 class::PyClassImpl,
10 function::{FuncArgs, PySetterValue},
11 types::{Constructor, GetDescriptor, Initializer},
12};
13use core::sync::atomic::{AtomicBool, Ordering};
14
15#[pyclass(module = false, name = "property", traverse)]
16#[derive(Debug)]
17pub struct PyProperty {
18 getter: PyRwLock<Option<PyObjectRef>>,
19 setter: PyRwLock<Option<PyObjectRef>>,
20 deleter: PyRwLock<Option<PyObjectRef>>,
21 doc: PyRwLock<Option<PyObjectRef>>,
22 name: PyRwLock<Option<PyObjectRef>>,
23 #[pytraverse(skip)]
24 getter_doc: core::sync::atomic::AtomicBool,
25}
26
27impl PyPayload for PyProperty {
28 #[inline]
29 fn class(ctx: &Context) -> &'static Py<PyType> {
30 ctx.types.property_type
31 }
32}
33
34#[derive(FromArgs)]
35pub struct PropertyArgs {
36 #[pyarg(any, default)]
37 fget: Option<PyObjectRef>,
38 #[pyarg(any, default)]
39 fset: Option<PyObjectRef>,
40 #[pyarg(any, default)]
41 fdel: Option<PyObjectRef>,
42 #[pyarg(any, default)]
43 doc: Option<PyObjectRef>,
44 #[pyarg(any, default)]
45 name: Option<PyStrRef>,
46}
47
48impl GetDescriptor for PyProperty {
49 fn descr_get(
50 zelf_obj: PyObjectRef,
51 obj: Option<PyObjectRef>,
52 _cls: Option<PyObjectRef>,
53 vm: &VirtualMachine,
54 ) -> PyResult {
55 let (zelf, obj) = Self::_unwrap(&zelf_obj, obj, vm)?;
56 if vm.is_none(&obj) {
57 Ok(zelf_obj)
58 } else if let Some(getter) = zelf.getter.read().clone() {
59 getter.call((obj,), vm)
61 } else {
62 let error_msg = zelf.format_property_error(&obj, "getter", vm)?;
63 Err(vm.new_attribute_error(error_msg))
64 }
65 }
66}
67
68#[pyclass(
69 with(Constructor, Initializer, GetDescriptor),
70 flags(BASETYPE, HAS_WEAKREF)
71)]
72impl PyProperty {
73 fn get_property_name(&self, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> {
76 if let Some(name) = self.name.read().clone() {
78 return Ok(Some(name));
79 }
80
81 let Some(getter) = self.getter.read().clone() else {
83 return Ok(None);
84 };
85
86 match getter.get_attr("__name__", vm) {
87 Ok(name) => Ok(Some(name)),
88 Err(e) => {
89 if e.class().is(vm.ctx.exceptions.attribute_error) {
92 Ok(None)
93 } else {
94 Err(e)
95 }
96 }
97 }
98 }
99
100 #[pyslot]
103 fn descr_set(
104 zelf: &PyObject,
105 obj: PyObjectRef,
106 value: PySetterValue,
107 vm: &VirtualMachine,
108 ) -> PyResult<()> {
109 let zelf = zelf.try_to_ref::<Self>(vm)?;
110 match value {
111 PySetterValue::Assign(value) => {
112 if let Some(setter) = zelf.setter.read().clone() {
114 setter.call((obj, value), vm).map(drop)
115 } else {
116 let error_msg = zelf.format_property_error(&obj, "setter", vm)?;
117 Err(vm.new_attribute_error(error_msg))
118 }
119 }
120 PySetterValue::Delete => {
121 if let Some(deleter) = zelf.deleter.read().clone() {
123 deleter.call((obj,), vm).map(drop)
124 } else {
125 let error_msg = zelf.format_property_error(&obj, "deleter", vm)?;
126 Err(vm.new_attribute_error(error_msg))
127 }
128 }
129 }
130 }
131
132 #[pygetset]
135 fn fget(&self) -> Option<PyObjectRef> {
136 self.getter.read().clone()
137 }
138
139 pub(crate) fn get_fget(&self) -> Option<PyObjectRef> {
140 self.getter.read().clone()
141 }
142
143 #[pygetset]
144 fn fset(&self) -> Option<PyObjectRef> {
145 self.setter.read().clone()
146 }
147
148 #[pygetset]
149 fn fdel(&self) -> Option<PyObjectRef> {
150 self.deleter.read().clone()
151 }
152
153 #[pygetset(name = "__name__")]
154 fn name_getter(&self, vm: &VirtualMachine) -> PyResult {
155 match self.get_property_name(vm)? {
156 Some(name) => Ok(name),
157 None => Err(vm.new_attribute_error("'property' object has no attribute '__name__'")),
158 }
159 }
160
161 #[pygetset(name = "__name__", setter)]
162 fn name_setter(&self, value: PyObjectRef) {
163 *self.name.write() = Some(value);
164 }
165
166 fn doc_getter(&self) -> Option<PyObjectRef> {
167 self.doc.read().clone()
168 }
169 fn doc_setter(&self, value: Option<PyObjectRef>) {
170 *self.doc.write() = value;
171 }
172
173 #[pymethod]
174 fn __set_name__(&self, args: PosArgs, vm: &VirtualMachine) -> PyResult<()> {
175 let func_args = args.into_args(vm);
176 let func_args_len = func_args.args.len();
177 let (_owner, name): (PyObjectRef, PyObjectRef) = func_args.bind(vm).map_err(|_e| {
178 vm.new_type_error(format!(
179 "__set_name__() takes 2 positional arguments but {func_args_len} were given"
180 ))
181 })?;
182
183 *self.name.write() = Some(name);
184
185 Ok(())
186 }
187
188 fn clone_property_with(
192 zelf: PyRef<Self>,
193 new_getter: Option<PyObjectRef>,
194 new_setter: Option<PyObjectRef>,
195 new_deleter: Option<PyObjectRef>,
196 vm: &VirtualMachine,
197 ) -> PyResult<PyRef<Self>> {
198 let doc = if zelf.getter_doc.load(Ordering::Relaxed) && new_getter.is_some() {
200 Some(vm.ctx.none())
203 } else if zelf.getter_doc.load(Ordering::Relaxed) {
204 Some(vm.ctx.none())
207 } else {
208 zelf.doc_getter()
210 };
211
212 let args = PropertyArgs {
214 fget: new_getter.or_else(|| zelf.fget()),
215 fset: new_setter.or_else(|| zelf.fset()),
216 fdel: new_deleter.or_else(|| zelf.fdel()),
217 doc,
218 name: None,
219 };
220
221 let new_prop = Self::slot_new(zelf.class().to_owned(), FuncArgs::default(), vm)?;
223 let new_prop_ref = new_prop.downcast::<Self>().unwrap();
224 Self::init(new_prop_ref.clone(), args, vm)?;
225
226 if let Some(name) = zelf.name.read().clone() {
228 *new_prop_ref.name.write() = Some(name);
229 }
230
231 Ok(new_prop_ref)
232 }
233
234 #[pymethod]
235 fn getter(
236 zelf: PyRef<Self>,
237 getter: Option<PyObjectRef>,
238 vm: &VirtualMachine,
239 ) -> PyResult<PyRef<Self>> {
240 Self::clone_property_with(zelf, getter, None, None, vm)
241 }
242
243 #[pymethod]
244 fn setter(
245 zelf: PyRef<Self>,
246 setter: Option<PyObjectRef>,
247 vm: &VirtualMachine,
248 ) -> PyResult<PyRef<Self>> {
249 Self::clone_property_with(zelf, None, setter, None, vm)
250 }
251
252 #[pymethod]
253 fn deleter(
254 zelf: PyRef<Self>,
255 deleter: Option<PyObjectRef>,
256 vm: &VirtualMachine,
257 ) -> PyResult<PyRef<Self>> {
258 Self::clone_property_with(zelf, None, None, deleter, vm)
259 }
260
261 #[pygetset]
262 fn __isabstractmethod__(&self, vm: &VirtualMachine) -> PyResult {
263 let is_abstract = |method: &PyObject| -> PyResult<bool> {
265 match method.get_attr("__isabstractmethod__", vm) {
266 Ok(isabstract) => isabstract.try_to_bool(vm),
267 Err(_) => Ok(false),
268 }
269 };
270
271 if let Some(getter) = self.getter.read().clone()
274 && is_abstract(&getter)?
275 {
276 return Ok(vm.ctx.new_bool(true).into());
277 }
278
279 if let Some(setter) = self.setter.read().clone()
281 && is_abstract(&setter)?
282 {
283 return Ok(vm.ctx.new_bool(true).into());
284 }
285
286 if let Some(deleter) = self.deleter.read().clone()
288 && is_abstract(&deleter)?
289 {
290 return Ok(vm.ctx.new_bool(true).into());
291 }
292
293 Ok(vm.ctx.new_bool(false).into())
294 }
295
296 #[pygetset(setter)]
297 fn set___isabstractmethod__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
298 if let Some(getter) = self.getter.read().clone() {
300 getter.set_attr("__isabstractmethod__", value, vm)?;
301 }
302 Ok(())
303 }
304
305 #[cold]
307 fn format_property_error(
308 &self,
309 obj: &PyObject,
310 error_type: &str,
311 vm: &VirtualMachine,
312 ) -> PyResult<String> {
313 let prop_name = self.get_property_name(vm)?;
314 let obj_type = obj.class();
315 let qualname = obj_type.__qualname__(vm);
316
317 match prop_name {
318 Some(name) => Ok(format!(
319 "property {} of {} object has no {}",
320 name.repr(vm)?,
321 qualname.repr(vm)?,
322 error_type
323 )),
324 None => Ok(format!(
325 "property of {} object has no {}",
326 qualname.repr(vm)?,
327 error_type
328 )),
329 }
330 }
331}
332
333impl Constructor for PyProperty {
334 type Args = FuncArgs;
335
336 fn py_new(_cls: &Py<PyType>, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<Self> {
337 Ok(Self {
338 getter: PyRwLock::new(None),
339 setter: PyRwLock::new(None),
340 deleter: PyRwLock::new(None),
341 doc: PyRwLock::new(None),
342 name: PyRwLock::new(None),
343 getter_doc: AtomicBool::new(false),
344 })
345 }
346}
347
348impl Initializer for PyProperty {
349 type Args = PropertyArgs;
350
351 fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
352 let mut getter_doc = false;
354
355 let get_getter_doc = |fget: &PyObject| -> Option<PyObjectRef> {
357 fget.get_attr("__doc__", vm)
358 .ok()
359 .filter(|doc| !vm.is_none(doc))
360 };
361
362 let doc = match args.doc {
363 Some(doc) if !vm.is_none(&doc) => Some(doc),
364 _ => {
365 args.fget.as_ref().and_then(|fget| {
367 get_getter_doc(fget).inspect(|_| {
368 getter_doc = true;
369 })
370 })
371 }
372 };
373
374 let is_exact_property = zelf.class().is(vm.ctx.types.property_type);
376
377 if is_exact_property {
378 *zelf.doc.write() = doc;
380 } else {
381 let doc_to_set = doc.unwrap_or_else(|| vm.ctx.none());
383 match zelf.as_object().set_attr("__doc__", doc_to_set, vm) {
384 Ok(()) => {}
385 Err(e) if !getter_doc && e.class().is(vm.ctx.exceptions.attribute_error) => {
386 }
389 Err(e) => return Err(e),
390 }
391 }
392
393 *zelf.getter.write() = args.fget;
394 *zelf.setter.write() = args.fset;
395 *zelf.deleter.write() = args.fdel;
396 *zelf.name.write() = args.name.map(|a| a.as_object().to_owned());
397 zelf.getter_doc.store(getter_doc, Ordering::Relaxed);
398
399 Ok(())
400 }
401}
402
403pub(crate) fn init(context: &'static Context) {
404 PyProperty::extend_class(context, context.types.property_type);
405
406 extend_class!(context, context.types.property_type, {
409 "__doc__" => context.new_static_getset(
410 "__doc__",
411 context.types.property_type,
412 PyProperty::doc_getter,
413 PyProperty::doc_setter,
414 ),
415 });
416}