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 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}