pyo3_macros_backend/
pymethod.rs

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