Skip to main content

rustpython_vm/builtins/
classmethod.rs

1use super::{
2    PyBoundMethod, PyGenericAlias, PyStr, PyStrInterned, PyType, PyTypeRef, object_get_dict,
3};
4use crate::{
5    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
6    class::{PyClassDef, PyClassImpl},
7    function::{FuncArgs, PySetterValue},
8    object::PyAtomicRef,
9    types::{Constructor, GetDescriptor, Initializer, Representable},
10};
11
12#[pyclass(module = false, name = "classmethod", traverse)]
13#[derive(Debug)]
14pub struct PyClassMethod {
15    #[pymember(name = "__func__")]
16    #[pymember(name = "__wrapped__")]
17    callable: PyAtomicRef<PyObject>,
18}
19
20impl From<PyObjectRef> for PyClassMethod {
21    fn from(callable: PyObjectRef) -> Self {
22        Self {
23            callable: PyAtomicRef::from(callable),
24        }
25    }
26}
27
28impl PyPayload for PyClassMethod {
29    #[inline]
30    fn class(ctx: &Context) -> &'static Py<PyType> {
31        ctx.types.classmethod_type
32    }
33}
34
35impl GetDescriptor for PyClassMethod {
36    fn descr_get(
37        zelf: &PyObject,
38        obj: Option<&PyObject>,
39        cls: Option<&PyObject>,
40        vm: &VirtualMachine,
41    ) -> PyResult {
42        let (zelf, _obj) = Self::_unwrap(zelf, obj, vm)?;
43        let cls = match cls {
44            Some(cls) => cls.to_owned(),
45            None => _obj.class().to_owned().into(),
46        };
47        let callable = zelf.callable.load_owned();
48        Ok(PyBoundMethod::new(cls, callable).into_ref(&vm.ctx).into())
49    }
50}
51
52#[derive(FromArgs)]
53pub struct ClassMethodArgs {
54    #[pyarg(positional)]
55    function: PyObjectRef,
56}
57
58impl Constructor for PyClassMethod {
59    type Args = ClassMethodArgs;
60
61    fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
62        // Validate the signature here, but defer storing the callable and
63        // copying its attributes to `__init__` so that subclasses overriding
64        // `__init__` without calling `super().__init__()` see `__func__` as
65        // `None`, matching CPython.
66        let _: ClassMethodArgs = args.bind_for(vm, Self::NAME)?;
67        let classmethod = Self {
68            callable: PyAtomicRef::from(vm.ctx.none()),
69        };
70        let result = PyRef::new_ref(classmethod, cls, Some(vm.ctx.new_dict()));
71        Ok(PyObjectRef::from(result))
72    }
73
74    fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
75        unimplemented!("use slot_new")
76    }
77}
78
79impl Initializer for PyClassMethod {
80    type Args = ClassMethodArgs;
81
82    fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
83        let callable = args.function;
84        zelf.callable.store(callable.clone());
85        functools_wraps(zelf.as_object(), &callable, vm)
86    }
87}
88
89impl PyClassMethod {
90    #[deprecated(note = "use PyClassMethod::from(...).into_ref() instead")]
91    pub fn new_ref(callable: PyObjectRef, ctx: &Context) -> PyRef<Self> {
92        Self::from(callable).into_ref(ctx)
93    }
94}
95
96#[pyclass(
97    with(GetDescriptor, Constructor, Initializer, Representable),
98    flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
99)]
100impl Py<PyClassMethod> {
101    #[pygetset]
102    fn __annotations__(zelf: &Self, vm: &VirtualMachine) -> PyResult {
103        let callable = zelf.callable.load_owned();
104        descriptor_get_wrapped_attribute(
105            &callable,
106            zelf.as_object(),
107            identifier!(vm.ctx, __annotations__),
108            vm,
109        )
110    }
111
112    #[pygetset(setter)]
113    fn set___annotations__(zelf: &Self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
114        descriptor_set_wrapped_attribute(
115            zelf.as_object(),
116            identifier!(vm.ctx, __annotations__),
117            value,
118            "classmethod",
119            vm,
120        )
121    }
122
123    #[pygetset]
124    fn __annotate__(zelf: &Self, vm: &VirtualMachine) -> PyResult {
125        let callable = zelf.callable.load_owned();
126        descriptor_get_wrapped_attribute(
127            &callable,
128            zelf.as_object(),
129            identifier!(vm.ctx, __annotate__),
130            vm,
131        )
132    }
133
134    #[pygetset(setter)]
135    fn set___annotate__(zelf: &Self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
136        descriptor_set_wrapped_attribute(
137            zelf.as_object(),
138            identifier!(vm.ctx, __annotate__),
139            value,
140            "classmethod",
141            vm,
142        )
143    }
144
145    #[pygetset]
146    fn __isabstractmethod__(&self, vm: &VirtualMachine) -> PyObjectRef {
147        let callable = self.callable.load_owned();
148        if let Ok(Some(is_abstract)) = vm.get_attribute_opt(&callable, "__isabstractmethod__") {
149            is_abstract
150        } else {
151            vm.ctx.new_bool(false).into()
152        }
153    }
154
155    #[pygetset(setter)]
156    fn set___isabstractmethod__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
157        self.callable
158            .load_owned()
159            .set_attr("__isabstractmethod__", value, vm)?;
160        Ok(())
161    }
162
163    #[pyclassmethod]
164    fn __class_getitem__(
165        cls: PyTypeRef,
166        object: PyObjectRef,
167        vm: &VirtualMachine,
168    ) -> PyResult<PyGenericAlias> {
169        PyGenericAlias::from_args(cls, object, vm)
170    }
171}
172
173impl Representable for PyClassMethod {
174    #[inline]
175    fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
176        let callable = zelf.callable.load_owned().repr(vm)?;
177        let class = Self::class(&vm.ctx);
178
179        let module = class.__module__(vm)?;
180        let repr = match (
181            class
182                .__qualname__(vm)
183                .downcast_ref::<PyStr>()
184                .map(|n| n.as_wtf8()),
185            module.downcast_ref::<PyStr>().map(|m| m.as_wtf8()),
186        ) {
187            (None, _) => return Err(vm.new_type_error("Unknown qualified name")),
188            (Some(qualname), Some(module)) if module != "builtins" => {
189                format!("<{module}.{qualname}({callable})>")
190            }
191            _ => format!("<{}({})>", class.slot_name(), callable),
192        };
193        Ok(repr)
194    }
195}
196
197pub(crate) fn init(context: &'static Context) {
198    PyClassMethod::extend_class(context, context.types.classmethod_type);
199}
200
201pub(crate) fn functools_wraps(
202    wrapper: &PyObject,
203    wrapped: &PyObject,
204    vm: &VirtualMachine,
205) -> PyResult<()> {
206    for attr in [
207        identifier!(vm.ctx, __module__),
208        identifier!(vm.ctx, __name__),
209        identifier!(vm.ctx, __qualname__),
210        identifier!(vm.ctx, __doc__),
211    ] {
212        if let Some(value) = vm.get_attribute_opt(wrapped, attr)? {
213            wrapper.set_attr(attr, value, vm)?;
214        }
215    }
216    Ok(())
217}
218
219pub(crate) fn descriptor_get_wrapped_attribute(
220    wrapped: &PyObject,
221    obj: &PyObject,
222    name: &'static PyStrInterned,
223    vm: &VirtualMachine,
224) -> PyResult {
225    let dict = object_get_dict(obj.to_owned(), vm)?;
226    if let Some(res) = dict.get_item_opt(name, vm)? {
227        return Ok(res);
228    }
229    let res = wrapped.get_attr(name, vm)?;
230    dict.set_item(name, res.clone(), vm)?;
231    Ok(res)
232}
233
234pub(crate) fn descriptor_set_wrapped_attribute(
235    obj: &PyObject,
236    name: &'static PyStrInterned,
237    value: PySetterValue,
238    type_name: &str,
239    vm: &VirtualMachine,
240) -> PyResult<()> {
241    let dict = object_get_dict(obj.to_owned(), vm)?;
242    match value {
243        PySetterValue::Delete => match dict.del_item(name, vm) {
244            Ok(()) => Ok(()),
245            Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => Err(vm
246                .new_attribute_error(format!(
247                    "'{type_name}' object has no attribute '{}'",
248                    name.as_str()
249                ))),
250            Err(e) => Err(e),
251        },
252        PySetterValue::Assign(value) => dict.set_item(name, value, vm),
253    }
254}