Skip to main content

rustpython_vm/builtins/
getset.rs

1//! Python `attribute` descriptor class. (PyGetSet)
2
3use super::PyType;
4use crate::{
5    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
6    class::PyClassImpl,
7    function::{
8        IntoPyGetterFunc, IntoPySetterFunc, ItemDoc, PyGetterFunc, PySetterFunc, PySetterValue,
9        plain_doc,
10    },
11    object::{Traverse, TraverseFn},
12    types::{GetDescriptor, Representable},
13};
14
15#[pyclass(module = false, name = "getset_descriptor", traverse = "manual")]
16pub struct PyGetSet {
17    #[pymember(name = "__name__")]
18    name: &'static crate::builtins::PyStrInterned,
19    // `d_type`. Owned: a type's namespace can outlive the type, and the
20    // descriptors it holds have to stay valid for as long as it does.
21    #[pymember(name = "__objclass__")]
22    class: PyRef<PyType>,
23    getter: Option<PyGetterFunc>,
24    setter: Option<PySetterFunc>,
25    doc: ItemDoc,
26}
27
28impl core::fmt::Debug for PyGetSet {
29    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
30        write!(
31            f,
32            "PyGetSet {{ name: {}, getter: {}, setter: {} }}",
33            self.name.as_str(),
34            if self.getter.is_some() {
35                "Some"
36            } else {
37                "None"
38            },
39            if self.setter.is_some() {
40                "Some"
41            } else {
42                "None"
43            },
44        )
45    }
46}
47
48// Only `class` is traced: the getter and setter closures are plain functions.
49unsafe impl Traverse for PyGetSet {
50    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
51        self.class.traverse(tracer_fn);
52    }
53}
54
55impl PyPayload for PyGetSet {
56    #[inline]
57    fn class(ctx: &Context) -> &'static Py<PyType> {
58        ctx.types.getset_type
59    }
60}
61
62impl GetDescriptor for PyGetSet {
63    fn descr_get(
64        zelf: &PyObject,
65        obj: Option<&PyObject>,
66        _cls: Option<&PyObject>,
67        vm: &VirtualMachine,
68    ) -> PyResult {
69        let (zelf, obj) = match Self::_check(zelf, obj, vm) {
70            Some(obj) => obj,
71            None => return Ok(zelf.to_owned()),
72        };
73        if let Some(ref f) = zelf.getter {
74            f(vm, obj.to_owned())
75        } else {
76            Err(vm.new_attribute_error(format!(
77                "attribute '{}' of '{}' objects is not readable",
78                zelf.name.as_str(),
79                Self::class(&vm.ctx).name()
80            )))
81        }
82    }
83}
84
85impl PyGetSet {
86    #[must_use]
87    pub fn new(name: &str, class: &Py<PyType>, ctx: &Context) -> Self {
88        Self {
89            name: ctx.intern_str(name),
90            class: class.to_owned(),
91            getter: None,
92            setter: None,
93            doc: ItemDoc::NONE,
94        }
95    }
96
97    #[must_use]
98    pub fn with_doc(mut self, doc: ItemDoc) -> Self {
99        self.doc = doc;
100        self
101    }
102
103    #[must_use]
104    pub fn with_get<G, X>(mut self, getter: G) -> Self
105    where
106        G: IntoPyGetterFunc<X>,
107    {
108        self.getter = Some(getter.into_getter());
109        self
110    }
111
112    #[must_use]
113    pub fn with_set<S, X>(mut self, setter: S) -> Self
114    where
115        S: IntoPySetterFunc<X>,
116    {
117        self.setter = Some(setter.into_setter());
118        self
119    }
120}
121
122#[pyclass(flags(DISALLOW_INSTANTIATION), with(GetDescriptor, Representable))]
123impl Py<PyGetSet> {
124    // Descriptor methods
125
126    #[pyslot]
127    fn descr_set(
128        zelf: &PyObject,
129        obj: PyObjectRef,
130        value: PySetterValue<PyObjectRef>,
131        vm: &VirtualMachine,
132    ) -> PyResult<()> {
133        let zelf = zelf.try_to_ref::<PyGetSet>(vm)?;
134        if let Some(ref f) = zelf.setter {
135            f(vm, obj, value)
136        } else {
137            Err(vm.new_attribute_error(format!(
138                "attribute '{}' of '{}' objects is not writable",
139                zelf.name.as_str(),
140                obj.class().name()
141            )))
142        }
143    }
144
145    #[pygetset]
146    fn __qualname__(&self) -> String {
147        format!("{}.{}", self.class.slot_name(), self.name.as_str())
148    }
149
150    #[pygetset]
151    fn __doc__(&self) -> Option<&'static str> {
152        plain_doc(self.doc)
153    }
154}
155
156impl Representable for PyGetSet {
157    #[inline]
158    fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
159        let class = &zelf.class;
160        // Special case for object type
161        if class.is(vm.ctx.types.object_type) {
162            Ok(format!("<attribute '{}'>", zelf.name.as_str()))
163        } else {
164            Ok(format!(
165                "<attribute '{}' of '{}' objects>",
166                zelf.name.as_str(),
167                class.name()
168            ))
169        }
170    }
171}
172
173pub(crate) fn init(context: &'static Context) {
174    PyGetSet::extend_class(context, context.types.getset_type);
175}