Skip to main content

pyo3_macros_backend/
pymethod.rs

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