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