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