1use super::{PyStr, PyStrInterned, PyType};
2use crate::{
3 builtins::{builtin_func::PyNativeMethod, type_, PyTypeRef},
4 class::PyClassImpl,
5 function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue},
6 types::{Callable, GetDescriptor, Representable, Unconstructible},
7 AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
8};
9use rustpython_common::lock::PyRwLock;
10
11#[derive(Debug)]
12pub struct PyDescriptor {
13 pub typ: &'static Py<PyType>,
14 pub name: &'static PyStrInterned,
15 pub qualname: PyRwLock<Option<String>>,
16}
17
18#[derive(Debug)]
19pub struct PyDescriptorOwned {
20 pub typ: PyRef<PyType>,
21 pub name: &'static PyStrInterned,
22 pub qualname: PyRwLock<Option<String>>,
23}
24
25#[pyclass(name = "method_descriptor", module = false)]
26pub struct PyMethodDescriptor {
27 pub common: PyDescriptor,
28 pub method: &'static PyMethodDef,
29 pub objclass: &'static Py<PyType>, }
32
33impl PyMethodDescriptor {
34 pub fn new(method: &'static PyMethodDef, typ: &'static Py<PyType>, ctx: &Context) -> Self {
35 Self {
36 common: PyDescriptor {
37 typ,
38 name: ctx.intern_str(method.name),
39 qualname: PyRwLock::new(None),
40 },
41 method,
42 objclass: typ,
43 }
44 }
45}
46
47impl PyPayload for PyMethodDescriptor {
48 fn class(ctx: &Context) -> &'static Py<PyType> {
49 ctx.types.method_descriptor_type
50 }
51}
52
53impl std::fmt::Debug for PyMethodDescriptor {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 write!(f, "method descriptor for '{}'", self.common.name)
56 }
57}
58
59impl GetDescriptor for PyMethodDescriptor {
60 fn descr_get(
61 zelf: PyObjectRef,
62 obj: Option<PyObjectRef>,
63 cls: Option<PyObjectRef>,
64 vm: &VirtualMachine,
65 ) -> PyResult {
66 let descr = Self::_as_pyref(&zelf, vm).unwrap();
67 let bound = match obj {
68 Some(obj) => {
69 if descr.method.flags.contains(PyMethodFlags::METHOD) {
70 if cls.map_or(false, |c| c.fast_isinstance(vm.ctx.types.type_type)) {
71 obj
72 } else {
73 return Err(vm.new_type_error(format!(
74 "descriptor '{}' needs a type, not '{}', as arg 2",
75 descr.common.name.as_str(),
76 obj.class().name()
77 )));
78 }
79 } else if descr.method.flags.contains(PyMethodFlags::CLASS) {
80 obj.class().to_owned().into()
81 } else {
82 unimplemented!()
83 }
84 }
85 None if descr.method.flags.contains(PyMethodFlags::CLASS) => cls.unwrap(),
86 None => return Ok(zelf),
87 };
88 Ok(descr.bind(bound, &vm.ctx).into())
90 }
91}
92
93impl Callable for PyMethodDescriptor {
94 type Args = FuncArgs;
95 #[inline]
96 fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
97 (zelf.method.func)(vm, args)
98 }
99}
100
101impl PyMethodDescriptor {
102 pub fn bind(&self, obj: PyObjectRef, ctx: &Context) -> PyRef<PyNativeMethod> {
103 self.method.build_bound_method(ctx, obj, self.common.typ)
104 }
105}
106
107#[pyclass(
108 with(GetDescriptor, Callable, Unconstructible, Representable),
109 flags(METHOD_DESCRIPTOR)
110)]
111impl PyMethodDescriptor {
112 #[pygetset(magic)]
113 fn name(&self) -> &'static PyStrInterned {
114 self.common.name
115 }
116 #[pygetset(magic)]
117 fn qualname(&self) -> String {
118 format!("{}.{}", self.common.typ.name(), &self.common.name)
119 }
120 #[pygetset(magic)]
121 fn doc(&self) -> Option<&'static str> {
122 self.method.doc
123 }
124 #[pygetset(magic)]
125 fn text_signature(&self) -> Option<String> {
126 self.method.doc.and_then(|doc| {
127 type_::get_text_signature_from_internal_doc(self.method.name, doc)
128 .map(|signature| signature.to_string())
129 })
130 }
131 #[pygetset(magic)]
132 fn objclass(&self) -> PyTypeRef {
133 self.objclass.to_owned()
134 }
135 #[pymethod(magic)]
136 fn reduce(
137 &self,
138 vm: &VirtualMachine,
139 ) -> (Option<PyObjectRef>, (Option<PyObjectRef>, &'static str)) {
140 let builtins_getattr = vm.builtins.get_attr("getattr", vm).ok();
141 let classname = vm.builtins.get_attr(&self.common.typ.__name__(vm), vm).ok();
142 (builtins_getattr, (classname, self.method.name))
143 }
144}
145
146impl Representable for PyMethodDescriptor {
147 #[inline]
148 fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
149 Ok(format!(
150 "<method '{}' of '{}' objects>",
151 &zelf.method.name,
152 zelf.common.typ.name()
153 ))
154 }
155}
156
157impl Unconstructible for PyMethodDescriptor {}
158
159#[derive(Debug)]
160pub enum MemberKind {
161 Bool = 14,
162 ObjectEx = 16,
163}
164
165pub type MemberSetterFunc = Option<fn(&VirtualMachine, PyObjectRef, PySetterValue) -> PyResult<()>>;
166
167pub enum MemberGetter {
168 Getter(fn(&VirtualMachine, PyObjectRef) -> PyResult),
169 Offset(usize),
170}
171
172pub enum MemberSetter {
173 Setter(MemberSetterFunc),
174 Offset(usize),
175}
176
177pub struct PyMemberDef {
178 pub name: String,
179 pub kind: MemberKind,
180 pub getter: MemberGetter,
181 pub setter: MemberSetter,
182 pub doc: Option<String>,
183}
184
185impl PyMemberDef {
186 fn get(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
187 match self.getter {
188 MemberGetter::Getter(getter) => (getter)(vm, obj),
189 MemberGetter::Offset(offset) => get_slot_from_object(obj, offset, self, vm),
190 }
191 }
192
193 fn set(
194 &self,
195 obj: PyObjectRef,
196 value: PySetterValue<PyObjectRef>,
197 vm: &VirtualMachine,
198 ) -> PyResult<()> {
199 match self.setter {
200 MemberSetter::Setter(setter) => match setter {
201 Some(setter) => (setter)(vm, obj, value),
202 None => Err(vm.new_attribute_error("readonly attribute".to_string())),
203 },
204 MemberSetter::Offset(offset) => set_slot_at_object(obj, offset, self, value, vm),
205 }
206 }
207}
208
209impl std::fmt::Debug for PyMemberDef {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 f.debug_struct("PyMemberDef")
212 .field("name", &self.name)
213 .field("kind", &self.kind)
214 .field("doc", &self.doc)
215 .finish()
216 }
217}
218
219#[pyclass(name = "member_descriptor", module = false)]
221#[derive(Debug)]
222pub struct PyMemberDescriptor {
223 pub common: PyDescriptorOwned,
224 pub member: PyMemberDef,
225}
226
227impl PyPayload for PyMemberDescriptor {
228 fn class(ctx: &Context) -> &'static Py<PyType> {
229 ctx.types.member_descriptor_type
230 }
231}
232
233fn calculate_qualname(descr: &PyDescriptorOwned, vm: &VirtualMachine) -> PyResult<Option<String>> {
234 if let Some(qualname) = vm.get_attribute_opt(descr.typ.clone().into(), "__qualname__")? {
235 let str = qualname.downcast::<PyStr>().map_err(|_| {
236 vm.new_type_error(
237 "<descriptor>.__objclass__.__qualname__ is not a unicode object".to_owned(),
238 )
239 })?;
240 Ok(Some(format!("{}.{}", str, descr.name)))
241 } else {
242 Ok(None)
243 }
244}
245
246#[pyclass(with(GetDescriptor, Unconstructible, Representable), flags(BASETYPE))]
247impl PyMemberDescriptor {
248 #[pygetset(magic)]
249 fn doc(&self) -> Option<String> {
250 self.member.doc.to_owned()
251 }
252
253 #[pygetset(magic)]
254 fn qualname(&self, vm: &VirtualMachine) -> PyResult<Option<String>> {
255 let qualname = self.common.qualname.read();
256 Ok(if qualname.is_none() {
257 drop(qualname);
258 let calculated = calculate_qualname(&self.common, vm)?;
259 calculated.clone_into(&mut self.common.qualname.write());
260 calculated
261 } else {
262 qualname.to_owned()
263 })
264 }
265
266 #[pyslot]
267 fn descr_set(
268 zelf: &PyObject,
269 obj: PyObjectRef,
270 value: PySetterValue<PyObjectRef>,
271 vm: &VirtualMachine,
272 ) -> PyResult<()> {
273 let zelf = Self::_as_pyref(zelf, vm)?;
274 zelf.member.set(obj, value, vm)
275 }
276}
277
278fn get_slot_from_object(
280 obj: PyObjectRef,
281 offset: usize,
282 member: &PyMemberDef,
283 vm: &VirtualMachine,
284) -> PyResult {
285 let slot = match member.kind {
286 MemberKind::Bool => obj
287 .get_slot(offset)
288 .unwrap_or_else(|| vm.ctx.new_bool(false).into()),
289 MemberKind::ObjectEx => obj.get_slot(offset).ok_or_else(|| {
290 vm.new_attribute_error(format!(
291 "'{}' object has no attribute '{}'",
292 obj.class().name(),
293 member.name
294 ))
295 })?,
296 };
297 Ok(slot)
298}
299
300fn set_slot_at_object(
302 obj: PyObjectRef,
303 offset: usize,
304 member: &PyMemberDef,
305 value: PySetterValue,
306 vm: &VirtualMachine,
307) -> PyResult<()> {
308 match member.kind {
309 MemberKind::Bool => {
310 match value {
311 PySetterValue::Assign(v) => {
312 if !v.class().is(vm.ctx.types.bool_type) {
313 return Err(
314 vm.new_type_error("attribute value type must be bool".to_owned())
315 );
316 }
317
318 obj.set_slot(offset, Some(v))
319 }
320 PySetterValue::Delete => obj.set_slot(offset, None),
321 };
322 }
323 MemberKind::ObjectEx => {
324 let value = match value {
325 PySetterValue::Assign(v) => Some(v),
326 PySetterValue::Delete => None,
327 };
328 obj.set_slot(offset, value);
329 }
330 }
331
332 Ok(())
333}
334
335impl Unconstructible for PyMemberDescriptor {}
336
337impl Representable for PyMemberDescriptor {
338 #[inline]
339 fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
340 Ok(format!(
341 "<member '{}' of '{}' objects>",
342 zelf.common.name,
343 zelf.common.typ.name(),
344 ))
345 }
346}
347
348impl GetDescriptor for PyMemberDescriptor {
349 fn descr_get(
350 zelf: PyObjectRef,
351 obj: Option<PyObjectRef>,
352 _cls: Option<PyObjectRef>,
353 vm: &VirtualMachine,
354 ) -> PyResult {
355 match obj {
356 Some(x) => {
357 let zelf = Self::_as_pyref(&zelf, vm)?;
358 zelf.member.get(x, vm)
359 }
360 None => Ok(zelf),
361 }
362 }
363}
364
365pub fn init(ctx: &Context) {
366 PyMemberDescriptor::extend_class(ctx, ctx.types.member_descriptor_type);
367 PyMethodDescriptor::extend_class(ctx, ctx.types.method_descriptor_type);
368}