pyo3_macros_backend/
pymethod.rs

1use std::borrow::Cow;
2use std::ffi::CString;
3
4use crate::attributes::{FromPyWithAttribute, NameAttribute, RenamingRule};
5use crate::method::{CallingConvention, ExtractErrorMode, PyArg};
6use crate::params::{impl_regular_arg_param, Holders};
7use crate::utils::{deprecated_from_py_with, PythonDoc};
8use crate::utils::{Ctx, LitCStr};
9use crate::{
10    method::{FnArg, FnSpec, FnType, SelfType},
11    pyfunction::PyFunctionOptions,
12};
13use crate::{quotes, utils};
14use proc_macro2::{Span, TokenStream};
15use quote::{format_ident, quote, quote_spanned, ToTokens};
16use syn::{ext::IdentExt, spanned::Spanned, Result};
17
18/// Generated code for a single pymethod item.
19pub struct MethodAndMethodDef {
20    /// The implementation of the Python wrapper for the pymethod
21    pub associated_method: TokenStream,
22    /// The method def which will be used to register this pymethod
23    pub method_def: TokenStream,
24}
25
26/// Generated code for a single pymethod item which is registered by a slot.
27pub struct MethodAndSlotDef {
28    /// The implementation of the Python wrapper for the pymethod
29    pub associated_method: TokenStream,
30    /// The slot def which will be used to register this pymethod
31    pub slot_def: TokenStream,
32}
33
34pub enum GeneratedPyMethod {
35    Method(MethodAndMethodDef),
36    Proto(MethodAndSlotDef),
37    SlotTraitImpl(String, TokenStream),
38}
39
40pub struct PyMethod<'a> {
41    kind: PyMethodKind,
42    method_name: String,
43    spec: FnSpec<'a>,
44}
45
46enum PyMethodKind {
47    Fn,
48    Proto(PyMethodProtoKind),
49}
50
51impl PyMethodKind {
52    fn from_name(name: &str) -> Self {
53        match name {
54            // Protocol implemented through slots
55            "__str__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__STR__)),
56            "__repr__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__REPR__)),
57            "__hash__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__HASH__)),
58            "__richcmp__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__RICHCMP__)),
59            "__get__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GET__)),
60            "__iter__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ITER__)),
61            "__next__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__NEXT__)),
62            "__await__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__AWAIT__)),
63            "__aiter__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__AITER__)),
64            "__anext__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ANEXT__)),
65            "__len__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__LEN__)),
66            "__contains__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__CONTAINS__)),
67            "__concat__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__CONCAT__)),
68            "__repeat__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__REPEAT__)),
69            "__inplace_concat__" => {
70                PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INPLACE_CONCAT__))
71            }
72            "__inplace_repeat__" => {
73                PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INPLACE_REPEAT__))
74            }
75            "__getitem__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GETITEM__)),
76            "__pos__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__POS__)),
77            "__neg__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__NEG__)),
78            "__abs__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ABS__)),
79            "__invert__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INVERT__)),
80            "__index__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INDEX__)),
81            "__int__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INT__)),
82            "__float__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__FLOAT__)),
83            "__bool__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__BOOL__)),
84            "__iadd__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IADD__)),
85            "__isub__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ISUB__)),
86            "__imul__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMUL__)),
87            "__imatmul__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMATMUL__)),
88            "__itruediv__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ITRUEDIV__)),
89            "__ifloordiv__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IFLOORDIV__)),
90            "__imod__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMOD__)),
91            "__ipow__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IPOW__)),
92            "__ilshift__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ILSHIFT__)),
93            "__irshift__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IRSHIFT__)),
94            "__iand__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IAND__)),
95            "__ixor__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IXOR__)),
96            "__ior__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IOR__)),
97            "__getbuffer__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GETBUFFER__)),
98            "__releasebuffer__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__RELEASEBUFFER__)),
99            // Protocols implemented through traits
100            "__getattribute__" => {
101                PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GETATTRIBUTE__))
102            }
103            "__getattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GETATTR__)),
104            "__setattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SETATTR__)),
105            "__delattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELATTR__)),
106            "__set__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SET__)),
107            "__delete__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELETE__)),
108            "__setitem__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SETITEM__)),
109            "__delitem__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELITEM__)),
110            "__add__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__ADD__)),
111            "__radd__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RADD__)),
112            "__sub__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SUB__)),
113            "__rsub__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RSUB__)),
114            "__mul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MUL__)),
115            "__rmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMUL__)),
116            "__matmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MATMUL__)),
117            "__rmatmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMATMUL__)),
118            "__floordiv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__FLOORDIV__)),
119            "__rfloordiv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RFLOORDIV__)),
120            "__truediv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__TRUEDIV__)),
121            "__rtruediv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RTRUEDIV__)),
122            "__divmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DIVMOD__)),
123            "__rdivmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RDIVMOD__)),
124            "__mod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MOD__)),
125            "__rmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMOD__)),
126            "__lshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LSHIFT__)),
127            "__rlshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RLSHIFT__)),
128            "__rshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RSHIFT__)),
129            "__rrshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RRSHIFT__)),
130            "__and__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__AND__)),
131            "__rand__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RAND__)),
132            "__xor__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__XOR__)),
133            "__rxor__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RXOR__)),
134            "__or__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__OR__)),
135            "__ror__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__ROR__)),
136            "__pow__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__POW__)),
137            "__rpow__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RPOW__)),
138            "__lt__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LT__)),
139            "__le__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LE__)),
140            "__eq__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__EQ__)),
141            "__ne__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__NE__)),
142            "__gt__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GT__)),
143            "__ge__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GE__)),
144            // Some tricky protocols which don't fit the pattern of the rest
145            "__call__" => PyMethodKind::Proto(PyMethodProtoKind::Call),
146            "__traverse__" => PyMethodKind::Proto(PyMethodProtoKind::Traverse),
147            "__clear__" => PyMethodKind::Proto(PyMethodProtoKind::Clear),
148            // Not a proto
149            _ => PyMethodKind::Fn,
150        }
151    }
152}
153
154enum PyMethodProtoKind {
155    Slot(&'static SlotDef),
156    Call,
157    Traverse,
158    Clear,
159    SlotFragment(&'static SlotFragmentDef),
160}
161
162impl<'a> PyMethod<'a> {
163    fn parse(
164        sig: &'a mut syn::Signature,
165        meth_attrs: &mut Vec<syn::Attribute>,
166        options: PyFunctionOptions,
167    ) -> Result<Self> {
168        let spec = FnSpec::parse(sig, meth_attrs, options)?;
169
170        let method_name = spec.python_name.to_string();
171        let kind = PyMethodKind::from_name(&method_name);
172
173        Ok(Self {
174            kind,
175            method_name,
176            spec,
177        })
178    }
179}
180
181pub fn is_proto_method(name: &str) -> bool {
182    match PyMethodKind::from_name(name) {
183        PyMethodKind::Fn => false,
184        PyMethodKind::Proto(_) => true,
185    }
186}
187
188pub fn gen_py_method(
189    cls: &syn::Type,
190    sig: &mut syn::Signature,
191    meth_attrs: &mut Vec<syn::Attribute>,
192    options: PyFunctionOptions,
193    ctx: &Ctx,
194) -> Result<GeneratedPyMethod> {
195    check_generic(sig)?;
196    ensure_function_options_valid(&options)?;
197    let method = PyMethod::parse(sig, meth_attrs, options)?;
198    let spec = &method.spec;
199    let Ctx { pyo3_path, .. } = ctx;
200
201    Ok(match (method.kind, &spec.tp) {
202        // Class attributes go before protos so that class attributes can be used to set proto
203        // method to None.
204        (_, FnType::ClassAttribute) => {
205            GeneratedPyMethod::Method(impl_py_class_attribute(cls, spec, ctx)?)
206        }
207        (PyMethodKind::Proto(proto_kind), _) => {
208            ensure_no_forbidden_protocol_attributes(&proto_kind, spec, &method.method_name)?;
209            match proto_kind {
210                PyMethodProtoKind::Slot(slot_def) => {
211                    let slot = slot_def.generate_type_slot(cls, spec, &method.method_name, ctx)?;
212                    GeneratedPyMethod::Proto(slot)
213                }
214                PyMethodProtoKind::Call => {
215                    GeneratedPyMethod::Proto(impl_call_slot(cls, method.spec, ctx)?)
216                }
217                PyMethodProtoKind::Traverse => {
218                    GeneratedPyMethod::Proto(impl_traverse_slot(cls, spec, ctx)?)
219                }
220                PyMethodProtoKind::Clear => {
221                    GeneratedPyMethod::Proto(impl_clear_slot(cls, spec, ctx)?)
222                }
223                PyMethodProtoKind::SlotFragment(slot_fragment_def) => {
224                    let proto = slot_fragment_def.generate_pyproto_fragment(cls, spec, ctx)?;
225                    GeneratedPyMethod::SlotTraitImpl(method.method_name, proto)
226                }
227            }
228        }
229        // ordinary functions (with some specialties)
230        (_, FnType::Fn(_)) => GeneratedPyMethod::Method(impl_py_method_def(
231            cls,
232            spec,
233            &spec.get_doc(meth_attrs, ctx),
234            None,
235            ctx,
236        )?),
237        (_, FnType::FnClass(_)) => GeneratedPyMethod::Method(impl_py_method_def(
238            cls,
239            spec,
240            &spec.get_doc(meth_attrs, ctx),
241            Some(quote!(#pyo3_path::ffi::METH_CLASS)),
242            ctx,
243        )?),
244        (_, FnType::FnStatic) => GeneratedPyMethod::Method(impl_py_method_def(
245            cls,
246            spec,
247            &spec.get_doc(meth_attrs, ctx),
248            Some(quote!(#pyo3_path::ffi::METH_STATIC)),
249            ctx,
250        )?),
251        // special prototypes
252        (_, FnType::FnNew) | (_, FnType::FnNewClass(_)) => {
253            GeneratedPyMethod::Proto(impl_py_method_def_new(cls, spec, ctx)?)
254        }
255
256        (_, FnType::Getter(self_type)) => GeneratedPyMethod::Method(impl_py_getter_def(
257            cls,
258            PropertyType::Function {
259                self_type,
260                spec,
261                doc: spec.get_doc(meth_attrs, ctx),
262            },
263            ctx,
264        )?),
265        (_, FnType::Setter(self_type)) => GeneratedPyMethod::Method(impl_py_setter_def(
266            cls,
267            PropertyType::Function {
268                self_type,
269                spec,
270                doc: spec.get_doc(meth_attrs, ctx),
271            },
272            ctx,
273        )?),
274        (_, FnType::FnModule(_)) => {
275            unreachable!("methods cannot be FnModule")
276        }
277    })
278}
279
280pub fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
281    let err_msg = |typ| format!("Python functions cannot have generic {} parameters", typ);
282    for param in &sig.generics.params {
283        match param {
284            syn::GenericParam::Lifetime(_) => {}
285            syn::GenericParam::Type(_) => bail_spanned!(param.span() => err_msg("type")),
286            syn::GenericParam::Const(_) => bail_spanned!(param.span() => err_msg("const")),
287        }
288    }
289    Ok(())
290}
291
292fn ensure_function_options_valid(options: &PyFunctionOptions) -> syn::Result<()> {
293    if let Some(pass_module) = &options.pass_module {
294        bail_spanned!(pass_module.span() => "`pass_module` cannot be used on Python methods");
295    }
296    Ok(())
297}
298
299fn ensure_no_forbidden_protocol_attributes(
300    proto_kind: &PyMethodProtoKind,
301    spec: &FnSpec<'_>,
302    method_name: &str,
303) -> syn::Result<()> {
304    if let Some(signature) = &spec.signature.attribute {
305        // __call__ is allowed to have a signature, but nothing else is.
306        if !matches!(proto_kind, PyMethodProtoKind::Call) {
307            bail_spanned!(signature.kw.span() => format!("`signature` cannot be used with magic method `{}`", method_name));
308        }
309    }
310    if let Some(text_signature) = &spec.text_signature {
311        bail_spanned!(text_signature.kw.span() => format!("`text_signature` cannot be used with magic method `{}`", method_name));
312    }
313    Ok(())
314}
315
316/// Also used by pyfunction.
317pub fn impl_py_method_def(
318    cls: &syn::Type,
319    spec: &FnSpec<'_>,
320    doc: &PythonDoc,
321    flags: Option<TokenStream>,
322    ctx: &Ctx,
323) -> Result<MethodAndMethodDef> {
324    let Ctx { pyo3_path, .. } = ctx;
325    let wrapper_ident = format_ident!("__pymethod_{}__", spec.python_name);
326    let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?;
327    let add_flags = flags.map(|flags| quote!(.flags(#flags)));
328    let methoddef_type = match spec.tp {
329        FnType::FnStatic => quote!(Static),
330        FnType::FnClass(_) => quote!(Class),
331        _ => quote!(Method),
332    };
333    let methoddef = spec.get_methoddef(quote! { #cls::#wrapper_ident }, doc, ctx);
334    let method_def = quote! {
335        #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
336            #pyo3_path::impl_::pymethods::PyMethodDefType::#methoddef_type(#methoddef #add_flags)
337        )
338    };
339    Ok(MethodAndMethodDef {
340        associated_method,
341        method_def,
342    })
343}
344
345/// Also used by pyclass.
346pub fn impl_py_method_def_new(
347    cls: &syn::Type,
348    spec: &FnSpec<'_>,
349    ctx: &Ctx,
350) -> Result<MethodAndSlotDef> {
351    let Ctx { pyo3_path, .. } = ctx;
352    let wrapper_ident = syn::Ident::new("__pymethod___new____", Span::call_site());
353    let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?;
354    // Use just the text_signature_call_signature() because the class' Python name
355    // isn't known to `#[pymethods]` - that has to be attached at runtime from the PyClassImpl
356    // trait implementation created by `#[pyclass]`.
357    let text_signature_body = spec.text_signature_call_signature().map_or_else(
358        || quote!(::std::option::Option::None),
359        |text_signature| quote!(::std::option::Option::Some(#text_signature)),
360    );
361    let slot_def = quote! {
362        #pyo3_path::ffi::PyType_Slot {
363            slot: #pyo3_path::ffi::Py_tp_new,
364            pfunc: {
365                unsafe extern "C" fn trampoline(
366                    subtype: *mut #pyo3_path::ffi::PyTypeObject,
367                    args: *mut #pyo3_path::ffi::PyObject,
368                    kwargs: *mut #pyo3_path::ffi::PyObject,
369                ) -> *mut #pyo3_path::ffi::PyObject {
370                    use #pyo3_path::impl_::pyclass::*;
371                    #[allow(unknown_lints, non_local_definitions)]
372                    impl PyClassNewTextSignature<#cls> for PyClassImplCollector<#cls> {
373                        #[inline]
374                        fn new_text_signature(self) -> ::std::option::Option<&'static str> {
375                            #text_signature_body
376                        }
377                    }
378
379                    #pyo3_path::impl_::trampoline::newfunc(
380                        subtype,
381                        args,
382                        kwargs,
383                        #cls::#wrapper_ident
384                    )
385                }
386                trampoline
387            } as #pyo3_path::ffi::newfunc as _
388        }
389    };
390    Ok(MethodAndSlotDef {
391        associated_method,
392        slot_def,
393    })
394}
395
396fn impl_call_slot(cls: &syn::Type, mut spec: FnSpec<'_>, ctx: &Ctx) -> Result<MethodAndSlotDef> {
397    let Ctx { pyo3_path, .. } = ctx;
398
399    // HACK: __call__ proto slot must always use varargs calling convention, so change the spec.
400    // Probably indicates there's a refactoring opportunity somewhere.
401    spec.convention = CallingConvention::Varargs;
402
403    let wrapper_ident = syn::Ident::new("__pymethod___call____", Span::call_site());
404    let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?;
405    let slot_def = quote! {
406        #pyo3_path::ffi::PyType_Slot {
407            slot: #pyo3_path::ffi::Py_tp_call,
408            pfunc: {
409                unsafe extern "C" fn trampoline(
410                    slf: *mut #pyo3_path::ffi::PyObject,
411                    args: *mut #pyo3_path::ffi::PyObject,
412                    kwargs: *mut #pyo3_path::ffi::PyObject,
413                ) -> *mut #pyo3_path::ffi::PyObject
414                {
415                    #pyo3_path::impl_::trampoline::ternaryfunc(
416                        slf,
417                        args,
418                        kwargs,
419                        #cls::#wrapper_ident
420                    )
421                }
422                trampoline
423            } as #pyo3_path::ffi::ternaryfunc as _
424        }
425    };
426    Ok(MethodAndSlotDef {
427        associated_method,
428        slot_def,
429    })
430}
431
432fn impl_traverse_slot(
433    cls: &syn::Type,
434    spec: &FnSpec<'_>,
435    ctx: &Ctx,
436) -> syn::Result<MethodAndSlotDef> {
437    let Ctx { pyo3_path, .. } = ctx;
438    if let (Some(py_arg), _) = split_off_python_arg(&spec.signature.arguments) {
439        return Err(syn::Error::new_spanned(py_arg.ty, "__traverse__ may not take `Python`. \
440            Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
441            should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited \
442            inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic."));
443    }
444
445    // check that the receiver does not try to smuggle an (implicit) `Python` token into here
446    if let FnType::Fn(SelfType::TryFromBoundRef(span))
447    | FnType::Fn(SelfType::Receiver {
448        mutable: true,
449        span,
450    }) = spec.tp
451    {
452        bail_spanned! { span =>
453            "__traverse__ may not take a receiver other than `&self`. Usually, an implementation of \
454            `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
455            should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited \
456            inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic."
457        }
458    }
459
460    let rust_fn_ident = spec.name;
461
462    let associated_method = quote! {
463        pub unsafe extern "C" fn __pymethod_traverse__(
464            slf: *mut #pyo3_path::ffi::PyObject,
465            visit: #pyo3_path::ffi::visitproc,
466            arg: *mut ::std::os::raw::c_void,
467        ) -> ::std::os::raw::c_int {
468            #pyo3_path::impl_::pymethods::_call_traverse::<#cls>(slf, #cls::#rust_fn_ident, visit, arg, #cls::__pymethod_traverse__)
469        }
470    };
471    let slot_def = quote! {
472        #pyo3_path::ffi::PyType_Slot {
473            slot: #pyo3_path::ffi::Py_tp_traverse,
474            pfunc: #cls::__pymethod_traverse__ as #pyo3_path::ffi::traverseproc as _
475        }
476    };
477    Ok(MethodAndSlotDef {
478        associated_method,
479        slot_def,
480    })
481}
482
483fn impl_clear_slot(cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx) -> syn::Result<MethodAndSlotDef> {
484    let Ctx { pyo3_path, .. } = ctx;
485    let (py_arg, args) = split_off_python_arg(&spec.signature.arguments);
486    let self_type = match &spec.tp {
487        FnType::Fn(self_type) => self_type,
488        _ => bail_spanned!(spec.name.span() => "expected instance method for `__clear__` function"),
489    };
490    let mut holders = Holders::new();
491    let slf = self_type.receiver(cls, ExtractErrorMode::Raise, &mut holders, ctx);
492
493    if let [arg, ..] = args {
494        bail_spanned!(arg.ty().span() => "`__clear__` function expected to have no arguments");
495    }
496
497    let name = &spec.name;
498    let holders = holders.init_holders(ctx);
499    let fncall = if py_arg.is_some() {
500        quote!(#cls::#name(#slf, py))
501    } else {
502        quote!(#cls::#name(#slf))
503    };
504
505    let associated_method = quote! {
506        pub unsafe extern "C" fn __pymethod___clear____(
507            _slf: *mut #pyo3_path::ffi::PyObject,
508        ) -> ::std::os::raw::c_int {
509            #pyo3_path::impl_::pymethods::_call_clear(_slf, |py, _slf| {
510                #holders
511                let result = #fncall;
512                let result = #pyo3_path::impl_::wrap::converter(&result).wrap(result)?;
513                ::std::result::Result::Ok(result)
514            }, #cls::__pymethod___clear____)
515        }
516    };
517    let slot_def = quote! {
518        #pyo3_path::ffi::PyType_Slot {
519            slot: #pyo3_path::ffi::Py_tp_clear,
520            pfunc: #cls::__pymethod___clear____ as #pyo3_path::ffi::inquiry as _
521        }
522    };
523    Ok(MethodAndSlotDef {
524        associated_method,
525        slot_def,
526    })
527}
528
529pub(crate) fn impl_py_class_attribute(
530    cls: &syn::Type,
531    spec: &FnSpec<'_>,
532    ctx: &Ctx,
533) -> syn::Result<MethodAndMethodDef> {
534    let Ctx { pyo3_path, .. } = ctx;
535    let (py_arg, args) = split_off_python_arg(&spec.signature.arguments);
536    ensure_spanned!(
537        args.is_empty(),
538        args[0].ty().span() => "#[classattr] can only have one argument (of type pyo3::Python)"
539    );
540
541    let name = &spec.name;
542    let fncall = if py_arg.is_some() {
543        quote!(function(py))
544    } else {
545        quote!(function())
546    };
547
548    let wrapper_ident = format_ident!("__pymethod_{}__", name);
549    let python_name = spec.null_terminated_python_name(ctx);
550    let body = quotes::ok_wrap(fncall, ctx);
551
552    let associated_method = quote! {
553        fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> {
554            let function = #cls::#name; // Shadow the method name to avoid #3017
555            let result = #body;
556            #pyo3_path::impl_::wrap::converter(&result).map_into_pyobject(py, result)
557        }
558    };
559
560    let method_def = quote! {
561        #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
562            #pyo3_path::impl_::pymethods::PyMethodDefType::ClassAttribute({
563                #pyo3_path::impl_::pymethods::PyClassAttributeDef::new(
564                    #python_name,
565                    #cls::#wrapper_ident
566                )
567            })
568        )
569    };
570
571    Ok(MethodAndMethodDef {
572        associated_method,
573        method_def,
574    })
575}
576
577fn impl_call_setter(
578    cls: &syn::Type,
579    spec: &FnSpec<'_>,
580    self_type: &SelfType,
581    holders: &mut Holders,
582    ctx: &Ctx,
583) -> syn::Result<TokenStream> {
584    let (py_arg, args) = split_off_python_arg(&spec.signature.arguments);
585    let slf = self_type.receiver(cls, ExtractErrorMode::Raise, holders, ctx);
586
587    if args.is_empty() {
588        bail_spanned!(spec.name.span() => "setter function expected to have one argument");
589    } else if args.len() > 1 {
590        bail_spanned!(
591            args[1].ty().span() =>
592            "setter function can have at most two arguments ([pyo3::Python,] and value)"
593        );
594    }
595
596    let name = &spec.name;
597    let fncall = if py_arg.is_some() {
598        quote!(#cls::#name(#slf, py, _val))
599    } else {
600        quote!(#cls::#name(#slf, _val))
601    };
602
603    Ok(fncall)
604}
605
606// Used here for PropertyType::Function, used in pyclass for descriptors.
607pub fn impl_py_setter_def(
608    cls: &syn::Type,
609    property_type: PropertyType<'_>,
610    ctx: &Ctx,
611) -> Result<MethodAndMethodDef> {
612    let Ctx { pyo3_path, .. } = ctx;
613    let python_name = property_type.null_terminated_python_name(ctx)?;
614    let doc = property_type.doc(ctx);
615    let mut holders = Holders::new();
616    let setter_impl = match property_type {
617        PropertyType::Descriptor {
618            field_index, field, ..
619        } => {
620            let slf = SelfType::Receiver {
621                mutable: true,
622                span: Span::call_site(),
623            }
624            .receiver(cls, ExtractErrorMode::Raise, &mut holders, ctx);
625            if let Some(ident) = &field.ident {
626                // named struct field
627                quote!({ #slf.#ident = _val; })
628            } else {
629                // tuple struct field
630                let index = syn::Index::from(field_index);
631                quote!({ #slf.#index = _val; })
632            }
633        }
634        PropertyType::Function {
635            spec, self_type, ..
636        } => impl_call_setter(cls, spec, self_type, &mut holders, ctx)?,
637    };
638
639    let wrapper_ident = match property_type {
640        PropertyType::Descriptor {
641            field: syn::Field {
642                ident: Some(ident), ..
643            },
644            ..
645        } => {
646            format_ident!("__pymethod_set_{}__", ident)
647        }
648        PropertyType::Descriptor { field_index, .. } => {
649            format_ident!("__pymethod_set_field_{}__", field_index)
650        }
651        PropertyType::Function { spec, .. } => {
652            format_ident!("__pymethod_set_{}__", spec.name)
653        }
654    };
655
656    let extract = match &property_type {
657        PropertyType::Function { spec, .. } => {
658            let (_, args) = split_off_python_arg(&spec.signature.arguments);
659            let value_arg = &args[0];
660            let (from_py_with, ident) =
661                if let Some(from_py_with) = &value_arg.from_py_with().as_ref().map(|f| &f.value) {
662                    let ident = syn::Ident::new("from_py_with", from_py_with.span());
663                    let d = deprecated_from_py_with(from_py_with).unwrap_or_default();
664                    (
665                        quote_spanned! { from_py_with.span() =>
666                            #d
667                            let #ident = #from_py_with;
668                        },
669                        ident,
670                    )
671                } else {
672                    (quote!(), syn::Ident::new("dummy", Span::call_site()))
673                };
674
675            let arg = if let FnArg::Regular(arg) = &value_arg {
676                arg
677            } else {
678                bail_spanned!(value_arg.name().span() => "The #[setter] value argument can't be *args, **kwargs or `cancel_handle`.");
679            };
680
681            let extract = impl_regular_arg_param(
682                arg,
683                ident,
684                quote!(::std::option::Option::Some(_value.into())),
685                &mut holders,
686                ctx,
687            );
688
689            quote! {
690                #from_py_with
691                let _val = #extract;
692            }
693        }
694        PropertyType::Descriptor { field, .. } => {
695            let span = field.ty.span();
696            let name = field
697                .ident
698                .as_ref()
699                .map(|i| i.to_string())
700                .unwrap_or_default();
701
702            let holder = holders.push_holder(span);
703            quote! {
704                let _val = #pyo3_path::impl_::extract_argument::extract_argument(_value.into(), &mut #holder, #name)?;
705            }
706        }
707    };
708
709    let mut cfg_attrs = TokenStream::new();
710    if let PropertyType::Descriptor { field, .. } = &property_type {
711        for attr in field
712            .attrs
713            .iter()
714            .filter(|attr| attr.path().is_ident("cfg"))
715        {
716            attr.to_tokens(&mut cfg_attrs);
717        }
718    }
719
720    let init_holders = holders.init_holders(ctx);
721    let associated_method = quote! {
722        #cfg_attrs
723        unsafe fn #wrapper_ident(
724            py: #pyo3_path::Python<'_>,
725            _slf: *mut #pyo3_path::ffi::PyObject,
726            _value: *mut #pyo3_path::ffi::PyObject,
727        ) -> #pyo3_path::PyResult<::std::os::raw::c_int> {
728            use ::std::convert::Into;
729            let _value = #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr_or_opt(py, &_value)
730                .ok_or_else(|| {
731                    #pyo3_path::exceptions::PyAttributeError::new_err("can't delete attribute")
732                })?;
733            #init_holders
734            #extract
735            let result = #setter_impl;
736            #pyo3_path::impl_::callback::convert(py, result)
737        }
738    };
739
740    let method_def = quote! {
741        #cfg_attrs
742        #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
743            #pyo3_path::impl_::pymethods::PyMethodDefType::Setter(
744                #pyo3_path::impl_::pymethods::PySetterDef::new(
745                    #python_name,
746                    #cls::#wrapper_ident,
747                    #doc
748                )
749            )
750        )
751    };
752
753    Ok(MethodAndMethodDef {
754        associated_method,
755        method_def,
756    })
757}
758
759fn impl_call_getter(
760    cls: &syn::Type,
761    spec: &FnSpec<'_>,
762    self_type: &SelfType,
763    holders: &mut Holders,
764    ctx: &Ctx,
765) -> syn::Result<TokenStream> {
766    let (py_arg, args) = split_off_python_arg(&spec.signature.arguments);
767    let slf = self_type.receiver(cls, ExtractErrorMode::Raise, holders, ctx);
768    ensure_spanned!(
769        args.is_empty(),
770        args[0].ty().span() => "getter function can only have one argument (of type pyo3::Python)"
771    );
772
773    let name = &spec.name;
774    let fncall = if py_arg.is_some() {
775        quote!(#cls::#name(#slf, py))
776    } else {
777        quote!(#cls::#name(#slf))
778    };
779
780    Ok(fncall)
781}
782
783// Used here for PropertyType::Function, used in pyclass for descriptors.
784pub fn impl_py_getter_def(
785    cls: &syn::Type,
786    property_type: PropertyType<'_>,
787    ctx: &Ctx,
788) -> Result<MethodAndMethodDef> {
789    let Ctx { pyo3_path, .. } = ctx;
790    let python_name = property_type.null_terminated_python_name(ctx)?;
791    let doc = property_type.doc(ctx);
792
793    let mut cfg_attrs = TokenStream::new();
794    if let PropertyType::Descriptor { field, .. } = &property_type {
795        for attr in field
796            .attrs
797            .iter()
798            .filter(|attr| attr.path().is_ident("cfg"))
799        {
800            attr.to_tokens(&mut cfg_attrs);
801        }
802    }
803
804    let mut holders = Holders::new();
805    match property_type {
806        PropertyType::Descriptor {
807            field_index, field, ..
808        } => {
809            let ty = &field.ty;
810            let field = if let Some(ident) = &field.ident {
811                ident.to_token_stream()
812            } else {
813                syn::Index::from(field_index).to_token_stream()
814            };
815
816            // TODO: on MSRV 1.77+, we can use `::std::mem::offset_of!` here, and it should
817            // make it possible for the `MaybeRuntimePyMethodDef` to be a `Static` variant.
818            let generator = quote_spanned! { ty.span() =>
819                #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Runtime(
820                    || GENERATOR.generate(#python_name, #doc)
821                )
822            };
823            // This is separate so that the unsafe below does not inherit the span and thus does not
824            // trigger the `unsafe_code` lint
825            let method_def = quote! {
826                #cfg_attrs
827                {
828                    #[allow(unused_imports)]  // might not be used if all probes are positve
829                    use #pyo3_path::impl_::pyclass::Probe;
830
831                    struct Offset;
832                    unsafe impl #pyo3_path::impl_::pyclass::OffsetCalculator<#cls, #ty> for Offset {
833                        fn offset() -> usize {
834                            #pyo3_path::impl_::pyclass::class_offset::<#cls>() +
835                            #pyo3_path::impl_::pyclass::offset_of!(#cls, #field)
836                        }
837                    }
838
839                    const GENERATOR: #pyo3_path::impl_::pyclass::PyClassGetterGenerator::<
840                        #cls,
841                        #ty,
842                        Offset,
843                        { #pyo3_path::impl_::pyclass::IsPyT::<#ty>::VALUE },
844                        { #pyo3_path::impl_::pyclass::IsToPyObject::<#ty>::VALUE },
845                        { #pyo3_path::impl_::pyclass::IsIntoPy::<#ty>::VALUE },
846                        { #pyo3_path::impl_::pyclass::IsIntoPyObjectRef::<#ty>::VALUE },
847                        { #pyo3_path::impl_::pyclass::IsIntoPyObject::<#ty>::VALUE },
848                    > = unsafe { #pyo3_path::impl_::pyclass::PyClassGetterGenerator::new() };
849                    #generator
850                }
851            };
852
853            Ok(MethodAndMethodDef {
854                associated_method: quote! {},
855                method_def,
856            })
857        }
858        // Forward to `IntoPyCallbackOutput`, to handle `#[getter]`s returning results.
859        PropertyType::Function {
860            spec, self_type, ..
861        } => {
862            let wrapper_ident = format_ident!("__pymethod_get_{}__", spec.name);
863            let call = impl_call_getter(cls, spec, self_type, &mut holders, ctx)?;
864            let body = quote! {
865                #pyo3_path::impl_::callback::convert(py, #call)
866            };
867
868            let init_holders = holders.init_holders(ctx);
869            let associated_method = quote! {
870                #cfg_attrs
871                unsafe fn #wrapper_ident(
872                    py: #pyo3_path::Python<'_>,
873                    _slf: *mut #pyo3_path::ffi::PyObject
874                ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
875                    #init_holders
876                    let result = #body;
877                    result
878                }
879            };
880
881            let method_def = quote! {
882                #cfg_attrs
883                #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
884                    #pyo3_path::impl_::pymethods::PyMethodDefType::Getter(
885                        #pyo3_path::impl_::pymethods::PyGetterDef::new(
886                            #python_name,
887                            #cls::#wrapper_ident,
888                            #doc
889                        )
890                    )
891                )
892            };
893
894            Ok(MethodAndMethodDef {
895                associated_method,
896                method_def,
897            })
898        }
899    }
900}
901
902/// Split an argument of pyo3::Python from the front of the arg list, if present
903fn split_off_python_arg<'a, 'b>(args: &'a [FnArg<'b>]) -> (Option<&'a PyArg<'b>>, &'a [FnArg<'b>]) {
904    match args {
905        [FnArg::Py(py), args @ ..] => (Some(py), args),
906        args => (None, args),
907    }
908}
909
910pub enum PropertyType<'a> {
911    Descriptor {
912        field_index: usize,
913        field: &'a syn::Field,
914        python_name: Option<&'a NameAttribute>,
915        renaming_rule: Option<RenamingRule>,
916    },
917    Function {
918        self_type: &'a SelfType,
919        spec: &'a FnSpec<'a>,
920        doc: PythonDoc,
921    },
922}
923
924impl PropertyType<'_> {
925    fn null_terminated_python_name(&self, ctx: &Ctx) -> Result<LitCStr> {
926        match self {
927            PropertyType::Descriptor {
928                field,
929                python_name,
930                renaming_rule,
931                ..
932            } => {
933                let name = match (python_name, &field.ident) {
934                    (Some(name), _) => name.value.0.to_string(),
935                    (None, Some(field_name)) => {
936                        let mut name = field_name.unraw().to_string();
937                        if let Some(rule) = renaming_rule {
938                            name = utils::apply_renaming_rule(*rule, &name);
939                        }
940                        name
941                    }
942                    (None, None) => {
943                        bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`");
944                    }
945                };
946                let name = CString::new(name).unwrap();
947                Ok(LitCStr::new(name, field.span(), ctx))
948            }
949            PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name(ctx)),
950        }
951    }
952
953    fn doc(&self, ctx: &Ctx) -> Cow<'_, PythonDoc> {
954        match self {
955            PropertyType::Descriptor { field, .. } => {
956                Cow::Owned(utils::get_doc(&field.attrs, None, ctx))
957            }
958            PropertyType::Function { doc, .. } => Cow::Borrowed(doc),
959        }
960    }
961}
962
963pub const __STR__: SlotDef = SlotDef::new("Py_tp_str", "reprfunc");
964pub const __REPR__: SlotDef = SlotDef::new("Py_tp_repr", "reprfunc");
965pub const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc")
966    .ret_ty(Ty::PyHashT)
967    .return_conversion(TokenGenerator(
968        |Ctx { pyo3_path, .. }: &Ctx| quote! { #pyo3_path::impl_::callback::HashCallbackOutput },
969    ));
970pub const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc")
971    .extract_error_mode(ExtractErrorMode::NotImplemented)
972    .arguments(&[Ty::Object, Ty::CompareOp]);
973const __GET__: SlotDef = SlotDef::new("Py_tp_descr_get", "descrgetfunc")
974    .arguments(&[Ty::MaybeNullObject, Ty::MaybeNullObject]);
975const __ITER__: SlotDef = SlotDef::new("Py_tp_iter", "getiterfunc");
976const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc")
977    .return_specialized_conversion(
978        TokenGenerator(|_| quote! { IterBaseKind, IterOptionKind, IterResultOptionKind }),
979        TokenGenerator(|_| quote! { iter_tag }),
980    );
981const __AWAIT__: SlotDef = SlotDef::new("Py_am_await", "unaryfunc");
982const __AITER__: SlotDef = SlotDef::new("Py_am_aiter", "unaryfunc");
983const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_specialized_conversion(
984    TokenGenerator(
985        |_| quote! { AsyncIterBaseKind, AsyncIterOptionKind, AsyncIterResultOptionKind },
986    ),
987    TokenGenerator(|_| quote! { async_iter_tag }),
988);
989pub const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc").ret_ty(Ty::PySsizeT);
990const __CONTAINS__: SlotDef = SlotDef::new("Py_sq_contains", "objobjproc")
991    .arguments(&[Ty::Object])
992    .ret_ty(Ty::Int);
993const __CONCAT__: SlotDef = SlotDef::new("Py_sq_concat", "binaryfunc").arguments(&[Ty::Object]);
994const __REPEAT__: SlotDef = SlotDef::new("Py_sq_repeat", "ssizeargfunc").arguments(&[Ty::PySsizeT]);
995const __INPLACE_CONCAT__: SlotDef =
996    SlotDef::new("Py_sq_concat", "binaryfunc").arguments(&[Ty::Object]);
997const __INPLACE_REPEAT__: SlotDef =
998    SlotDef::new("Py_sq_repeat", "ssizeargfunc").arguments(&[Ty::PySsizeT]);
999pub const __GETITEM__: SlotDef =
1000    SlotDef::new("Py_mp_subscript", "binaryfunc").arguments(&[Ty::Object]);
1001
1002const __POS__: SlotDef = SlotDef::new("Py_nb_positive", "unaryfunc");
1003const __NEG__: SlotDef = SlotDef::new("Py_nb_negative", "unaryfunc");
1004const __ABS__: SlotDef = SlotDef::new("Py_nb_absolute", "unaryfunc");
1005const __INVERT__: SlotDef = SlotDef::new("Py_nb_invert", "unaryfunc");
1006const __INDEX__: SlotDef = SlotDef::new("Py_nb_index", "unaryfunc");
1007pub const __INT__: SlotDef = SlotDef::new("Py_nb_int", "unaryfunc");
1008const __FLOAT__: SlotDef = SlotDef::new("Py_nb_float", "unaryfunc");
1009const __BOOL__: SlotDef = SlotDef::new("Py_nb_bool", "inquiry").ret_ty(Ty::Int);
1010
1011const __IADD__: SlotDef = SlotDef::new("Py_nb_inplace_add", "binaryfunc")
1012    .arguments(&[Ty::Object])
1013    .extract_error_mode(ExtractErrorMode::NotImplemented)
1014    .return_self();
1015const __ISUB__: SlotDef = SlotDef::new("Py_nb_inplace_subtract", "binaryfunc")
1016    .arguments(&[Ty::Object])
1017    .extract_error_mode(ExtractErrorMode::NotImplemented)
1018    .return_self();
1019const __IMUL__: SlotDef = SlotDef::new("Py_nb_inplace_multiply", "binaryfunc")
1020    .arguments(&[Ty::Object])
1021    .extract_error_mode(ExtractErrorMode::NotImplemented)
1022    .return_self();
1023const __IMATMUL__: SlotDef = SlotDef::new("Py_nb_inplace_matrix_multiply", "binaryfunc")
1024    .arguments(&[Ty::Object])
1025    .extract_error_mode(ExtractErrorMode::NotImplemented)
1026    .return_self();
1027const __ITRUEDIV__: SlotDef = SlotDef::new("Py_nb_inplace_true_divide", "binaryfunc")
1028    .arguments(&[Ty::Object])
1029    .extract_error_mode(ExtractErrorMode::NotImplemented)
1030    .return_self();
1031const __IFLOORDIV__: SlotDef = SlotDef::new("Py_nb_inplace_floor_divide", "binaryfunc")
1032    .arguments(&[Ty::Object])
1033    .extract_error_mode(ExtractErrorMode::NotImplemented)
1034    .return_self();
1035const __IMOD__: SlotDef = SlotDef::new("Py_nb_inplace_remainder", "binaryfunc")
1036    .arguments(&[Ty::Object])
1037    .extract_error_mode(ExtractErrorMode::NotImplemented)
1038    .return_self();
1039const __IPOW__: SlotDef = SlotDef::new("Py_nb_inplace_power", "ipowfunc")
1040    .arguments(&[Ty::Object, Ty::IPowModulo])
1041    .extract_error_mode(ExtractErrorMode::NotImplemented)
1042    .return_self();
1043const __ILSHIFT__: SlotDef = SlotDef::new("Py_nb_inplace_lshift", "binaryfunc")
1044    .arguments(&[Ty::Object])
1045    .extract_error_mode(ExtractErrorMode::NotImplemented)
1046    .return_self();
1047const __IRSHIFT__: SlotDef = SlotDef::new("Py_nb_inplace_rshift", "binaryfunc")
1048    .arguments(&[Ty::Object])
1049    .extract_error_mode(ExtractErrorMode::NotImplemented)
1050    .return_self();
1051const __IAND__: SlotDef = SlotDef::new("Py_nb_inplace_and", "binaryfunc")
1052    .arguments(&[Ty::Object])
1053    .extract_error_mode(ExtractErrorMode::NotImplemented)
1054    .return_self();
1055const __IXOR__: SlotDef = SlotDef::new("Py_nb_inplace_xor", "binaryfunc")
1056    .arguments(&[Ty::Object])
1057    .extract_error_mode(ExtractErrorMode::NotImplemented)
1058    .return_self();
1059const __IOR__: SlotDef = SlotDef::new("Py_nb_inplace_or", "binaryfunc")
1060    .arguments(&[Ty::Object])
1061    .extract_error_mode(ExtractErrorMode::NotImplemented)
1062    .return_self();
1063const __GETBUFFER__: SlotDef = SlotDef::new("Py_bf_getbuffer", "getbufferproc")
1064    .arguments(&[Ty::PyBuffer, Ty::Int])
1065    .ret_ty(Ty::Int)
1066    .require_unsafe();
1067const __RELEASEBUFFER__: SlotDef = SlotDef::new("Py_bf_releasebuffer", "releasebufferproc")
1068    .arguments(&[Ty::PyBuffer])
1069    .ret_ty(Ty::Void)
1070    .require_unsafe();
1071const __CLEAR__: SlotDef = SlotDef::new("Py_tp_clear", "inquiry")
1072    .arguments(&[])
1073    .ret_ty(Ty::Int);
1074
1075#[derive(Clone, Copy)]
1076enum Ty {
1077    Object,
1078    MaybeNullObject,
1079    NonNullObject,
1080    IPowModulo,
1081    CompareOp,
1082    Int,
1083    PyHashT,
1084    PySsizeT,
1085    Void,
1086    PyBuffer,
1087}
1088
1089impl Ty {
1090    fn ffi_type(self, ctx: &Ctx) -> TokenStream {
1091        let Ctx {
1092            pyo3_path,
1093            output_span,
1094        } = ctx;
1095        let pyo3_path = pyo3_path.to_tokens_spanned(*output_span);
1096        match self {
1097            Ty::Object | Ty::MaybeNullObject => quote! { *mut #pyo3_path::ffi::PyObject },
1098            Ty::NonNullObject => quote! { ::std::ptr::NonNull<#pyo3_path::ffi::PyObject> },
1099            Ty::IPowModulo => quote! { #pyo3_path::impl_::pymethods::IPowModulo },
1100            Ty::Int | Ty::CompareOp => quote! { ::std::os::raw::c_int },
1101            Ty::PyHashT => quote! { #pyo3_path::ffi::Py_hash_t },
1102            Ty::PySsizeT => quote! { #pyo3_path::ffi::Py_ssize_t },
1103            Ty::Void => quote! { () },
1104            Ty::PyBuffer => quote! { *mut #pyo3_path::ffi::Py_buffer },
1105        }
1106    }
1107
1108    fn extract(
1109        self,
1110        ident: &syn::Ident,
1111        arg: &FnArg<'_>,
1112        extract_error_mode: ExtractErrorMode,
1113        holders: &mut Holders,
1114        ctx: &Ctx,
1115    ) -> TokenStream {
1116        let Ctx { pyo3_path, .. } = ctx;
1117        match self {
1118            Ty::Object => extract_object(
1119                extract_error_mode,
1120                holders,
1121                arg,
1122                quote! { #ident },
1123                ctx
1124            ),
1125            Ty::MaybeNullObject => extract_object(
1126                extract_error_mode,
1127                holders,
1128                arg,
1129                quote! {
1130                    if #ident.is_null() {
1131                        #pyo3_path::ffi::Py_None()
1132                    } else {
1133                        #ident
1134                    }
1135                },
1136                ctx
1137            ),
1138            Ty::NonNullObject => extract_object(
1139                extract_error_mode,
1140                holders,
1141                arg,
1142                quote! { #ident.as_ptr() },
1143                ctx
1144            ),
1145            Ty::IPowModulo => extract_object(
1146                extract_error_mode,
1147                holders,
1148                arg,
1149                quote! { #ident.as_ptr() },
1150                ctx
1151            ),
1152            Ty::CompareOp => extract_error_mode.handle_error(
1153                quote! {
1154                    #pyo3_path::class::basic::CompareOp::from_raw(#ident)
1155                        .ok_or_else(|| #pyo3_path::exceptions::PyValueError::new_err("invalid comparison operator"))
1156                },
1157                ctx
1158            ),
1159            Ty::PySsizeT => {
1160                let ty = arg.ty();
1161                extract_error_mode.handle_error(
1162                    quote! {
1163                            ::std::convert::TryInto::<#ty>::try_into(#ident).map_err(|e| #pyo3_path::exceptions::PyValueError::new_err(e.to_string()))
1164                    },
1165                    ctx
1166                )
1167            }
1168            // Just pass other types through unmodified
1169            Ty::PyBuffer | Ty::Int | Ty::PyHashT | Ty::Void => quote! { #ident },
1170        }
1171    }
1172}
1173
1174fn extract_object(
1175    extract_error_mode: ExtractErrorMode,
1176    holders: &mut Holders,
1177    arg: &FnArg<'_>,
1178    source_ptr: TokenStream,
1179    ctx: &Ctx,
1180) -> TokenStream {
1181    let Ctx { pyo3_path, .. } = ctx;
1182    let name = arg.name().unraw().to_string();
1183
1184    let extract = if let Some(FromPyWithAttribute {
1185        kw,
1186        value: extractor,
1187    }) = arg.from_py_with()
1188    {
1189        let extractor = quote_spanned! { kw.span =>
1190            { let from_py_with: fn(_) -> _ = #extractor; from_py_with }
1191        };
1192
1193        quote! {
1194            #pyo3_path::impl_::extract_argument::from_py_with(
1195                unsafe { #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &#source_ptr).0 },
1196                #name,
1197                #extractor,
1198            )
1199        }
1200    } else {
1201        let holder = holders.push_holder(Span::call_site());
1202        quote! {
1203            #pyo3_path::impl_::extract_argument::extract_argument(
1204                unsafe { #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &#source_ptr).0 },
1205                &mut #holder,
1206                #name
1207            )
1208        }
1209    };
1210
1211    let extracted = extract_error_mode.handle_error(extract, ctx);
1212    quote!(#extracted)
1213}
1214
1215enum ReturnMode {
1216    ReturnSelf,
1217    Conversion(TokenGenerator),
1218    SpecializedConversion(TokenGenerator, TokenGenerator),
1219}
1220
1221impl ReturnMode {
1222    fn return_call_output(&self, call: TokenStream, ctx: &Ctx) -> TokenStream {
1223        let Ctx { pyo3_path, .. } = ctx;
1224        match self {
1225            ReturnMode::Conversion(conversion) => {
1226                let conversion = TokenGeneratorCtx(*conversion, ctx);
1227                quote! {
1228                    let _result: #pyo3_path::PyResult<#conversion> = #pyo3_path::impl_::callback::convert(py, #call);
1229                    #pyo3_path::impl_::callback::convert(py, _result)
1230                }
1231            }
1232            ReturnMode::SpecializedConversion(traits, tag) => {
1233                let traits = TokenGeneratorCtx(*traits, ctx);
1234                let tag = TokenGeneratorCtx(*tag, ctx);
1235                quote! {
1236                    let _result = #call;
1237                    use #pyo3_path::impl_::pymethods::{#traits};
1238                    (&_result).#tag().convert(py, _result)
1239                }
1240            }
1241            ReturnMode::ReturnSelf => quote! {
1242                let _result: #pyo3_path::PyResult<()> = #pyo3_path::impl_::callback::convert(py, #call);
1243                _result?;
1244                #pyo3_path::ffi::Py_XINCREF(_raw_slf);
1245                ::std::result::Result::Ok(_raw_slf)
1246            },
1247        }
1248    }
1249}
1250
1251pub struct SlotDef {
1252    slot: StaticIdent,
1253    func_ty: StaticIdent,
1254    arguments: &'static [Ty],
1255    ret_ty: Ty,
1256    extract_error_mode: ExtractErrorMode,
1257    return_mode: Option<ReturnMode>,
1258    require_unsafe: bool,
1259}
1260
1261const NO_ARGUMENTS: &[Ty] = &[];
1262
1263impl SlotDef {
1264    const fn new(slot: &'static str, func_ty: &'static str) -> Self {
1265        SlotDef {
1266            slot: StaticIdent(slot),
1267            func_ty: StaticIdent(func_ty),
1268            arguments: NO_ARGUMENTS,
1269            ret_ty: Ty::Object,
1270            extract_error_mode: ExtractErrorMode::Raise,
1271            return_mode: None,
1272            require_unsafe: false,
1273        }
1274    }
1275
1276    const fn arguments(mut self, arguments: &'static [Ty]) -> Self {
1277        self.arguments = arguments;
1278        self
1279    }
1280
1281    const fn ret_ty(mut self, ret_ty: Ty) -> Self {
1282        self.ret_ty = ret_ty;
1283        self
1284    }
1285
1286    const fn return_conversion(mut self, return_conversion: TokenGenerator) -> Self {
1287        self.return_mode = Some(ReturnMode::Conversion(return_conversion));
1288        self
1289    }
1290
1291    const fn return_specialized_conversion(
1292        mut self,
1293        traits: TokenGenerator,
1294        tag: TokenGenerator,
1295    ) -> Self {
1296        self.return_mode = Some(ReturnMode::SpecializedConversion(traits, tag));
1297        self
1298    }
1299
1300    const fn extract_error_mode(mut self, extract_error_mode: ExtractErrorMode) -> Self {
1301        self.extract_error_mode = extract_error_mode;
1302        self
1303    }
1304
1305    const fn return_self(mut self) -> Self {
1306        self.return_mode = Some(ReturnMode::ReturnSelf);
1307        self
1308    }
1309
1310    const fn require_unsafe(mut self) -> Self {
1311        self.require_unsafe = true;
1312        self
1313    }
1314
1315    pub fn generate_type_slot(
1316        &self,
1317        cls: &syn::Type,
1318        spec: &FnSpec<'_>,
1319        method_name: &str,
1320        ctx: &Ctx,
1321    ) -> Result<MethodAndSlotDef> {
1322        let Ctx { pyo3_path, .. } = ctx;
1323        let SlotDef {
1324            slot,
1325            func_ty,
1326            arguments,
1327            extract_error_mode,
1328            ret_ty,
1329            return_mode,
1330            require_unsafe,
1331        } = self;
1332        if *require_unsafe {
1333            ensure_spanned!(
1334                spec.unsafety.is_some(),
1335                spec.name.span() => format!("`{}` must be `unsafe fn`", method_name)
1336            );
1337        }
1338        let arg_types: &Vec<_> = &arguments.iter().map(|arg| arg.ffi_type(ctx)).collect();
1339        let arg_idents: &Vec<_> = &(0..arguments.len())
1340            .map(|i| format_ident!("arg{}", i))
1341            .collect();
1342        let wrapper_ident = format_ident!("__pymethod_{}__", method_name);
1343        let ret_ty = ret_ty.ffi_type(ctx);
1344        let mut holders = Holders::new();
1345        let body = generate_method_body(
1346            cls,
1347            spec,
1348            arguments,
1349            *extract_error_mode,
1350            &mut holders,
1351            return_mode.as_ref(),
1352            ctx,
1353        )?;
1354        let name = spec.name;
1355        let holders = holders.init_holders(ctx);
1356        let associated_method = quote! {
1357            #[allow(non_snake_case)]
1358            unsafe fn #wrapper_ident(
1359                py: #pyo3_path::Python<'_>,
1360                _raw_slf: *mut #pyo3_path::ffi::PyObject,
1361                #(#arg_idents: #arg_types),*
1362            ) -> #pyo3_path::PyResult<#ret_ty> {
1363                let function = #cls::#name; // Shadow the method name to avoid #3017
1364                let _slf = _raw_slf;
1365                #holders
1366                #body
1367            }
1368        };
1369        let slot_def = quote! {{
1370            unsafe extern "C" fn trampoline(
1371                _slf: *mut #pyo3_path::ffi::PyObject,
1372                #(#arg_idents: #arg_types),*
1373            ) -> #ret_ty
1374            {
1375                #pyo3_path::impl_::trampoline:: #func_ty (
1376                    _slf,
1377                    #(#arg_idents,)*
1378                    #cls::#wrapper_ident
1379                )
1380            }
1381
1382            #pyo3_path::ffi::PyType_Slot {
1383                slot: #pyo3_path::ffi::#slot,
1384                pfunc: trampoline as #pyo3_path::ffi::#func_ty as _
1385            }
1386        }};
1387        Ok(MethodAndSlotDef {
1388            associated_method,
1389            slot_def,
1390        })
1391    }
1392}
1393
1394fn generate_method_body(
1395    cls: &syn::Type,
1396    spec: &FnSpec<'_>,
1397    arguments: &[Ty],
1398    extract_error_mode: ExtractErrorMode,
1399    holders: &mut Holders,
1400    return_mode: Option<&ReturnMode>,
1401    ctx: &Ctx,
1402) -> Result<TokenStream> {
1403    let Ctx { pyo3_path, .. } = ctx;
1404    let self_arg = spec
1405        .tp
1406        .self_arg(Some(cls), extract_error_mode, holders, ctx);
1407    let rust_name = spec.name;
1408    let args = extract_proto_arguments(spec, arguments, extract_error_mode, holders, ctx)?;
1409    let call = quote! { #cls::#rust_name(#self_arg #(#args),*) };
1410    Ok(if let Some(return_mode) = return_mode {
1411        return_mode.return_call_output(call, ctx)
1412    } else {
1413        quote! {
1414            let result = #call;
1415            #pyo3_path::impl_::callback::convert(py, result)
1416        }
1417    })
1418}
1419
1420struct SlotFragmentDef {
1421    fragment: &'static str,
1422    arguments: &'static [Ty],
1423    extract_error_mode: ExtractErrorMode,
1424    ret_ty: Ty,
1425}
1426
1427impl SlotFragmentDef {
1428    const fn new(fragment: &'static str, arguments: &'static [Ty]) -> Self {
1429        SlotFragmentDef {
1430            fragment,
1431            arguments,
1432            extract_error_mode: ExtractErrorMode::Raise,
1433            ret_ty: Ty::Void,
1434        }
1435    }
1436
1437    const fn extract_error_mode(mut self, extract_error_mode: ExtractErrorMode) -> Self {
1438        self.extract_error_mode = extract_error_mode;
1439        self
1440    }
1441
1442    const fn ret_ty(mut self, ret_ty: Ty) -> Self {
1443        self.ret_ty = ret_ty;
1444        self
1445    }
1446
1447    fn generate_pyproto_fragment(
1448        &self,
1449        cls: &syn::Type,
1450        spec: &FnSpec<'_>,
1451        ctx: &Ctx,
1452    ) -> Result<TokenStream> {
1453        let Ctx { pyo3_path, .. } = ctx;
1454        let SlotFragmentDef {
1455            fragment,
1456            arguments,
1457            extract_error_mode,
1458            ret_ty,
1459        } = self;
1460        let fragment_trait = format_ident!("PyClass{}SlotFragment", fragment);
1461        let method = syn::Ident::new(fragment, Span::call_site());
1462        let wrapper_ident = format_ident!("__pymethod_{}__", fragment);
1463        let arg_types: &Vec<_> = &arguments.iter().map(|arg| arg.ffi_type(ctx)).collect();
1464        let arg_idents: &Vec<_> = &(0..arguments.len())
1465            .map(|i| format_ident!("arg{}", i))
1466            .collect();
1467        let mut holders = Holders::new();
1468        let body = generate_method_body(
1469            cls,
1470            spec,
1471            arguments,
1472            *extract_error_mode,
1473            &mut holders,
1474            None,
1475            ctx,
1476        )?;
1477        let ret_ty = ret_ty.ffi_type(ctx);
1478        let holders = holders.init_holders(ctx);
1479        Ok(quote! {
1480            impl #cls {
1481                #[allow(non_snake_case)]
1482                unsafe fn #wrapper_ident(
1483                    py: #pyo3_path::Python,
1484                    _raw_slf: *mut #pyo3_path::ffi::PyObject,
1485                    #(#arg_idents: #arg_types),*
1486                ) -> #pyo3_path::PyResult<#ret_ty> {
1487                    let _slf = _raw_slf;
1488                    #holders
1489                    #body
1490                }
1491            }
1492
1493            impl #pyo3_path::impl_::pyclass::#fragment_trait<#cls> for #pyo3_path::impl_::pyclass::PyClassImplCollector<#cls> {
1494
1495                #[inline]
1496                unsafe fn #method(
1497                    self,
1498                    py: #pyo3_path::Python,
1499                    _raw_slf: *mut #pyo3_path::ffi::PyObject,
1500                    #(#arg_idents: #arg_types),*
1501                ) -> #pyo3_path::PyResult<#ret_ty> {
1502                    #cls::#wrapper_ident(py, _raw_slf, #(#arg_idents),*)
1503                }
1504            }
1505        })
1506    }
1507}
1508
1509const __GETATTRIBUTE__: SlotFragmentDef =
1510    SlotFragmentDef::new("__getattribute__", &[Ty::Object]).ret_ty(Ty::Object);
1511const __GETATTR__: SlotFragmentDef =
1512    SlotFragmentDef::new("__getattr__", &[Ty::Object]).ret_ty(Ty::Object);
1513const __SETATTR__: SlotFragmentDef =
1514    SlotFragmentDef::new("__setattr__", &[Ty::Object, Ty::NonNullObject]);
1515const __DELATTR__: SlotFragmentDef = SlotFragmentDef::new("__delattr__", &[Ty::Object]);
1516const __SET__: SlotFragmentDef = SlotFragmentDef::new("__set__", &[Ty::Object, Ty::NonNullObject]);
1517const __DELETE__: SlotFragmentDef = SlotFragmentDef::new("__delete__", &[Ty::Object]);
1518const __SETITEM__: SlotFragmentDef =
1519    SlotFragmentDef::new("__setitem__", &[Ty::Object, Ty::NonNullObject]);
1520const __DELITEM__: SlotFragmentDef = SlotFragmentDef::new("__delitem__", &[Ty::Object]);
1521
1522macro_rules! binary_num_slot_fragment_def {
1523    ($ident:ident, $name:literal) => {
1524        const $ident: SlotFragmentDef = SlotFragmentDef::new($name, &[Ty::Object])
1525            .extract_error_mode(ExtractErrorMode::NotImplemented)
1526            .ret_ty(Ty::Object);
1527    };
1528}
1529
1530binary_num_slot_fragment_def!(__ADD__, "__add__");
1531binary_num_slot_fragment_def!(__RADD__, "__radd__");
1532binary_num_slot_fragment_def!(__SUB__, "__sub__");
1533binary_num_slot_fragment_def!(__RSUB__, "__rsub__");
1534binary_num_slot_fragment_def!(__MUL__, "__mul__");
1535binary_num_slot_fragment_def!(__RMUL__, "__rmul__");
1536binary_num_slot_fragment_def!(__MATMUL__, "__matmul__");
1537binary_num_slot_fragment_def!(__RMATMUL__, "__rmatmul__");
1538binary_num_slot_fragment_def!(__FLOORDIV__, "__floordiv__");
1539binary_num_slot_fragment_def!(__RFLOORDIV__, "__rfloordiv__");
1540binary_num_slot_fragment_def!(__TRUEDIV__, "__truediv__");
1541binary_num_slot_fragment_def!(__RTRUEDIV__, "__rtruediv__");
1542binary_num_slot_fragment_def!(__DIVMOD__, "__divmod__");
1543binary_num_slot_fragment_def!(__RDIVMOD__, "__rdivmod__");
1544binary_num_slot_fragment_def!(__MOD__, "__mod__");
1545binary_num_slot_fragment_def!(__RMOD__, "__rmod__");
1546binary_num_slot_fragment_def!(__LSHIFT__, "__lshift__");
1547binary_num_slot_fragment_def!(__RLSHIFT__, "__rlshift__");
1548binary_num_slot_fragment_def!(__RSHIFT__, "__rshift__");
1549binary_num_slot_fragment_def!(__RRSHIFT__, "__rrshift__");
1550binary_num_slot_fragment_def!(__AND__, "__and__");
1551binary_num_slot_fragment_def!(__RAND__, "__rand__");
1552binary_num_slot_fragment_def!(__XOR__, "__xor__");
1553binary_num_slot_fragment_def!(__RXOR__, "__rxor__");
1554binary_num_slot_fragment_def!(__OR__, "__or__");
1555binary_num_slot_fragment_def!(__ROR__, "__ror__");
1556
1557const __POW__: SlotFragmentDef = SlotFragmentDef::new("__pow__", &[Ty::Object, Ty::Object])
1558    .extract_error_mode(ExtractErrorMode::NotImplemented)
1559    .ret_ty(Ty::Object);
1560const __RPOW__: SlotFragmentDef = SlotFragmentDef::new("__rpow__", &[Ty::Object, Ty::Object])
1561    .extract_error_mode(ExtractErrorMode::NotImplemented)
1562    .ret_ty(Ty::Object);
1563
1564const __LT__: SlotFragmentDef = SlotFragmentDef::new("__lt__", &[Ty::Object])
1565    .extract_error_mode(ExtractErrorMode::NotImplemented)
1566    .ret_ty(Ty::Object);
1567const __LE__: SlotFragmentDef = SlotFragmentDef::new("__le__", &[Ty::Object])
1568    .extract_error_mode(ExtractErrorMode::NotImplemented)
1569    .ret_ty(Ty::Object);
1570const __EQ__: SlotFragmentDef = SlotFragmentDef::new("__eq__", &[Ty::Object])
1571    .extract_error_mode(ExtractErrorMode::NotImplemented)
1572    .ret_ty(Ty::Object);
1573const __NE__: SlotFragmentDef = SlotFragmentDef::new("__ne__", &[Ty::Object])
1574    .extract_error_mode(ExtractErrorMode::NotImplemented)
1575    .ret_ty(Ty::Object);
1576const __GT__: SlotFragmentDef = SlotFragmentDef::new("__gt__", &[Ty::Object])
1577    .extract_error_mode(ExtractErrorMode::NotImplemented)
1578    .ret_ty(Ty::Object);
1579const __GE__: SlotFragmentDef = SlotFragmentDef::new("__ge__", &[Ty::Object])
1580    .extract_error_mode(ExtractErrorMode::NotImplemented)
1581    .ret_ty(Ty::Object);
1582
1583fn extract_proto_arguments(
1584    spec: &FnSpec<'_>,
1585    proto_args: &[Ty],
1586    extract_error_mode: ExtractErrorMode,
1587    holders: &mut Holders,
1588    ctx: &Ctx,
1589) -> Result<Vec<TokenStream>> {
1590    let mut args = Vec::with_capacity(spec.signature.arguments.len());
1591    let mut non_python_args = 0;
1592
1593    for arg in &spec.signature.arguments {
1594        if let FnArg::Py(..) = arg {
1595            args.push(quote! { py });
1596        } else {
1597            let ident = syn::Ident::new(&format!("arg{}", non_python_args), Span::call_site());
1598            let conversions = proto_args.get(non_python_args)
1599                .ok_or_else(|| err_spanned!(arg.ty().span() => format!("Expected at most {} non-python arguments", proto_args.len())))?
1600                .extract(&ident, arg, extract_error_mode, holders, ctx);
1601            non_python_args += 1;
1602            args.push(conversions);
1603        }
1604    }
1605
1606    if non_python_args != proto_args.len() {
1607        bail_spanned!(spec.name.span() => format!("Expected {} arguments, got {}", proto_args.len(), non_python_args));
1608    }
1609    Ok(args)
1610}
1611
1612struct StaticIdent(&'static str);
1613
1614impl ToTokens for StaticIdent {
1615    fn to_tokens(&self, tokens: &mut TokenStream) {
1616        syn::Ident::new(self.0, Span::call_site()).to_tokens(tokens)
1617    }
1618}
1619
1620#[derive(Clone, Copy)]
1621struct TokenGenerator(fn(&Ctx) -> TokenStream);
1622
1623struct TokenGeneratorCtx<'ctx>(TokenGenerator, &'ctx Ctx);
1624
1625impl ToTokens for TokenGeneratorCtx<'_> {
1626    fn to_tokens(&self, tokens: &mut TokenStream) {
1627        let Self(TokenGenerator(gen), ctx) = self;
1628        (gen)(ctx).to_tokens(tokens)
1629    }
1630}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here