1use super::{PyBoundMethod, PyGenericAlias, PyStr, PyType, PyTypeRef};
2use crate::{
3 AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
4 class::PyClassImpl,
5 common::lock::PyMutex,
6 function::{FuncArgs, PySetterValue},
7 types::{Constructor, GetDescriptor, Initializer, Representable},
8};
9
10#[pyclass(module = false, name = "classmethod")]
31#[derive(Debug)]
32pub struct PyClassMethod {
33 callable: PyMutex<PyObjectRef>,
34}
35
36impl From<PyObjectRef> for PyClassMethod {
37 fn from(callable: PyObjectRef) -> Self {
38 Self {
39 callable: PyMutex::new(callable),
40 }
41 }
42}
43
44impl PyPayload for PyClassMethod {
45 #[inline]
46 fn class(ctx: &Context) -> &'static Py<PyType> {
47 ctx.types.classmethod_type
48 }
49}
50
51impl GetDescriptor for PyClassMethod {
52 fn descr_get(
53 zelf: PyObjectRef,
54 obj: Option<PyObjectRef>,
55 cls: Option<PyObjectRef>,
56 vm: &VirtualMachine,
57 ) -> PyResult {
58 let (zelf, _obj) = Self::_unwrap(&zelf, obj, vm)?;
59 let cls = cls.unwrap_or_else(|| _obj.class().to_owned().into());
60 let callable = zelf.callable.lock().clone();
61 Ok(PyBoundMethod::new(cls, callable).into_ref(&vm.ctx).into())
62 }
63}
64
65impl Constructor for PyClassMethod {
66 type Args = PyObjectRef;
67
68 fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
69 let callable: Self::Args = args.bind(vm)?;
70 let dict = vm.ctx.new_dict();
72
73 if let Ok(doc) = callable.get_attr("__doc__", vm) {
76 dict.set_item(identifier!(vm.ctx, __doc__), doc, vm)?;
77 }
78 if let Ok(name) = callable.get_attr("__name__", vm) {
79 dict.set_item(identifier!(vm.ctx, __name__), name, vm)?;
80 }
81 if let Ok(qualname) = callable.get_attr("__qualname__", vm) {
82 dict.set_item(identifier!(vm.ctx, __qualname__), qualname, vm)?;
83 }
84 if let Ok(module) = callable.get_attr("__module__", vm) {
85 dict.set_item(identifier!(vm.ctx, __module__), module, vm)?;
86 }
87 if let Ok(annotations) = callable.get_attr("__annotations__", vm) {
88 dict.set_item(identifier!(vm.ctx, __annotations__), annotations, vm)?;
89 }
90
91 let classmethod = Self {
93 callable: PyMutex::new(callable),
94 };
95
96 let result = PyRef::new_ref(classmethod, cls, Some(dict));
97 Ok(PyObjectRef::from(result))
98 }
99
100 fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
101 unimplemented!("use slot_new")
102 }
103}
104
105impl Initializer for PyClassMethod {
106 type Args = PyObjectRef;
107
108 fn init(zelf: PyRef<Self>, callable: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
109 *zelf.callable.lock() = callable;
110 Ok(())
111 }
112}
113
114impl PyClassMethod {
115 #[deprecated(note = "use PyClassMethod::from(...).into_ref() instead")]
116 pub fn new_ref(callable: PyObjectRef, ctx: &Context) -> PyRef<Self> {
117 Self::from(callable).into_ref(ctx)
118 }
119}
120
121#[pyclass(
122 with(GetDescriptor, Constructor, Initializer, Representable),
123 flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
124)]
125impl PyClassMethod {
126 #[pygetset]
127 fn __func__(&self) -> PyObjectRef {
128 self.callable.lock().clone()
129 }
130
131 #[pygetset]
132 fn __wrapped__(&self) -> PyObjectRef {
133 self.callable.lock().clone()
134 }
135
136 #[pygetset]
137 fn __module__(&self, vm: &VirtualMachine) -> PyResult {
138 self.callable.lock().get_attr("__module__", vm)
139 }
140
141 #[pygetset]
142 fn __qualname__(&self, vm: &VirtualMachine) -> PyResult {
143 self.callable.lock().get_attr("__qualname__", vm)
144 }
145
146 #[pygetset]
147 fn __name__(&self, vm: &VirtualMachine) -> PyResult {
148 self.callable.lock().get_attr("__name__", vm)
149 }
150
151 #[pygetset]
152 fn __annotations__(&self, vm: &VirtualMachine) -> PyResult {
153 self.callable.lock().get_attr("__annotations__", vm)
154 }
155
156 #[pygetset(setter)]
157 fn set___annotations__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
158 match value {
159 PySetterValue::Assign(v) => self.callable.lock().set_attr("__annotations__", v, vm),
160 PySetterValue::Delete => Ok(()), }
162 }
163
164 #[pygetset]
165 fn __annotate__(&self, vm: &VirtualMachine) -> PyResult {
166 self.callable.lock().get_attr("__annotate__", vm)
167 }
168
169 #[pygetset(setter)]
170 fn set___annotate__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
171 match value {
172 PySetterValue::Assign(v) => self.callable.lock().set_attr("__annotate__", v, vm),
173 PySetterValue::Delete => Ok(()), }
175 }
176
177 #[pygetset]
178 fn __isabstractmethod__(&self, vm: &VirtualMachine) -> PyObjectRef {
179 match vm.get_attribute_opt(self.callable.lock().clone(), "__isabstractmethod__") {
180 Ok(Some(is_abstract)) => is_abstract,
181 _ => vm.ctx.new_bool(false).into(),
182 }
183 }
184
185 #[pygetset(setter)]
186 fn set___isabstractmethod__(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
187 self.callable
188 .lock()
189 .set_attr("__isabstractmethod__", value, vm)?;
190 Ok(())
191 }
192
193 #[pyclassmethod]
194 fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
195 PyGenericAlias::from_args(cls, args, vm)
196 }
197}
198
199impl Representable for PyClassMethod {
200 #[inline]
201 fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
202 let callable = zelf.callable.lock().repr(vm).unwrap();
203 let class = Self::class(&vm.ctx);
204
205 let repr = match (
206 class
207 .__qualname__(vm)
208 .downcast_ref::<PyStr>()
209 .map(|n| n.as_wtf8()),
210 class
211 .__module__(vm)
212 .downcast_ref::<PyStr>()
213 .map(|m| m.as_wtf8()),
214 ) {
215 (None, _) => return Err(vm.new_type_error("Unknown qualified name")),
216 (Some(qualname), Some(module)) if module != "builtins" => {
217 format!("<{module}.{qualname}({callable})>")
218 }
219 _ => format!("<{}({})>", class.slot_name(), callable),
220 };
221 Ok(repr)
222 }
223}
224
225pub(crate) fn init(context: &'static Context) {
226 PyClassMethod::extend_class(context, context.types.classmethod_type);
227}