pyo3_macros_backend/
pymethod.rs

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