pyo3_macros_backend/
pyclass.rs

1use std::borrow::Cow;
2use std::fmt::Debug;
3
4use proc_macro2::{Ident, Span, TokenStream};
5use quote::{format_ident, quote, quote_spanned, ToTokens};
6use syn::ext::IdentExt;
7use syn::parse::{Parse, ParseStream};
8use syn::punctuated::Punctuated;
9use syn::{parse_quote, parse_quote_spanned, spanned::Spanned, ImplItemFn, Result, Token};
10
11use crate::attributes::kw::frozen;
12use crate::attributes::{
13    self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute,
14    ModuleAttribute, NameAttribute, NameLitStr, RenameAllAttribute, StrFormatterAttribute,
15};
16use crate::combine_errors::CombineErrors;
17#[cfg(feature = "experimental-inspect")]
18use crate::introspection::{
19    class_introspection_code, function_introspection_code, introspection_id_const,
20    unique_element_id,
21};
22use crate::konst::{ConstAttributes, ConstSpec};
23use crate::method::{FnArg, FnSpec, PyArg, RegularArg};
24use crate::pyfunction::ConstructorAttribute;
25#[cfg(feature = "experimental-inspect")]
26use crate::pyfunction::FunctionSignature;
27use crate::pyimpl::{gen_py_const, get_cfg_attributes, PyClassMethodsType};
28use crate::pymethod::{
29    impl_py_class_attribute, impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef,
30    MethodAndSlotDef, PropertyType, SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__,
31    __RICHCMP__, __STR__,
32};
33use crate::pyversions::{is_abi3_before, is_py_before};
34use crate::utils::{self, apply_renaming_rule, Ctx, LitCStr, PythonDoc};
35use crate::PyFunctionOptions;
36
37/// If the class is derived from a Rust `struct` or `enum`.
38#[derive(Copy, Clone, Debug, PartialEq, Eq)]
39pub enum PyClassKind {
40    Struct,
41    Enum,
42}
43
44/// The parsed arguments of the pyclass macro
45#[derive(Clone)]
46pub struct PyClassArgs {
47    pub class_kind: PyClassKind,
48    pub options: PyClassPyO3Options,
49}
50
51impl PyClassArgs {
52    fn parse(input: ParseStream<'_>, kind: PyClassKind) -> Result<Self> {
53        Ok(PyClassArgs {
54            class_kind: kind,
55            options: PyClassPyO3Options::parse(input)?,
56        })
57    }
58
59    pub fn parse_struct_args(input: ParseStream<'_>) -> syn::Result<Self> {
60        Self::parse(input, PyClassKind::Struct)
61    }
62
63    pub fn parse_enum_args(input: ParseStream<'_>) -> syn::Result<Self> {
64        Self::parse(input, PyClassKind::Enum)
65    }
66}
67
68#[derive(Clone, Default)]
69pub struct PyClassPyO3Options {
70    pub krate: Option<CrateAttribute>,
71    pub dict: Option<kw::dict>,
72    pub eq: Option<kw::eq>,
73    pub eq_int: Option<kw::eq_int>,
74    pub extends: Option<ExtendsAttribute>,
75    pub get_all: Option<kw::get_all>,
76    pub freelist: Option<FreelistAttribute>,
77    pub frozen: Option<kw::frozen>,
78    pub hash: Option<kw::hash>,
79    pub immutable_type: Option<kw::immutable_type>,
80    pub mapping: Option<kw::mapping>,
81    pub module: Option<ModuleAttribute>,
82    pub name: Option<NameAttribute>,
83    pub ord: Option<kw::ord>,
84    pub rename_all: Option<RenameAllAttribute>,
85    pub sequence: Option<kw::sequence>,
86    pub set_all: Option<kw::set_all>,
87    pub str: Option<StrFormatterAttribute>,
88    pub subclass: Option<kw::subclass>,
89    pub unsendable: Option<kw::unsendable>,
90    pub weakref: Option<kw::weakref>,
91    pub generic: Option<kw::generic>,
92}
93
94pub enum PyClassPyO3Option {
95    Crate(CrateAttribute),
96    Dict(kw::dict),
97    Eq(kw::eq),
98    EqInt(kw::eq_int),
99    Extends(ExtendsAttribute),
100    Freelist(FreelistAttribute),
101    Frozen(kw::frozen),
102    GetAll(kw::get_all),
103    Hash(kw::hash),
104    ImmutableType(kw::immutable_type),
105    Mapping(kw::mapping),
106    Module(ModuleAttribute),
107    Name(NameAttribute),
108    Ord(kw::ord),
109    RenameAll(RenameAllAttribute),
110    Sequence(kw::sequence),
111    SetAll(kw::set_all),
112    Str(StrFormatterAttribute),
113    Subclass(kw::subclass),
114    Unsendable(kw::unsendable),
115    Weakref(kw::weakref),
116    Generic(kw::generic),
117}
118
119impl Parse for PyClassPyO3Option {
120    fn parse(input: ParseStream<'_>) -> Result<Self> {
121        let lookahead = input.lookahead1();
122        if lookahead.peek(Token![crate]) {
123            input.parse().map(PyClassPyO3Option::Crate)
124        } else if lookahead.peek(kw::dict) {
125            input.parse().map(PyClassPyO3Option::Dict)
126        } else if lookahead.peek(kw::eq) {
127            input.parse().map(PyClassPyO3Option::Eq)
128        } else if lookahead.peek(kw::eq_int) {
129            input.parse().map(PyClassPyO3Option::EqInt)
130        } else if lookahead.peek(kw::extends) {
131            input.parse().map(PyClassPyO3Option::Extends)
132        } else if lookahead.peek(attributes::kw::freelist) {
133            input.parse().map(PyClassPyO3Option::Freelist)
134        } else if lookahead.peek(attributes::kw::frozen) {
135            input.parse().map(PyClassPyO3Option::Frozen)
136        } else if lookahead.peek(attributes::kw::get_all) {
137            input.parse().map(PyClassPyO3Option::GetAll)
138        } else if lookahead.peek(attributes::kw::hash) {
139            input.parse().map(PyClassPyO3Option::Hash)
140        } else if lookahead.peek(attributes::kw::immutable_type) {
141            input.parse().map(PyClassPyO3Option::ImmutableType)
142        } else if lookahead.peek(attributes::kw::mapping) {
143            input.parse().map(PyClassPyO3Option::Mapping)
144        } else if lookahead.peek(attributes::kw::module) {
145            input.parse().map(PyClassPyO3Option::Module)
146        } else if lookahead.peek(kw::name) {
147            input.parse().map(PyClassPyO3Option::Name)
148        } else if lookahead.peek(attributes::kw::ord) {
149            input.parse().map(PyClassPyO3Option::Ord)
150        } else if lookahead.peek(kw::rename_all) {
151            input.parse().map(PyClassPyO3Option::RenameAll)
152        } else if lookahead.peek(attributes::kw::sequence) {
153            input.parse().map(PyClassPyO3Option::Sequence)
154        } else if lookahead.peek(attributes::kw::set_all) {
155            input.parse().map(PyClassPyO3Option::SetAll)
156        } else if lookahead.peek(attributes::kw::str) {
157            input.parse().map(PyClassPyO3Option::Str)
158        } else if lookahead.peek(attributes::kw::subclass) {
159            input.parse().map(PyClassPyO3Option::Subclass)
160        } else if lookahead.peek(attributes::kw::unsendable) {
161            input.parse().map(PyClassPyO3Option::Unsendable)
162        } else if lookahead.peek(attributes::kw::weakref) {
163            input.parse().map(PyClassPyO3Option::Weakref)
164        } else if lookahead.peek(attributes::kw::generic) {
165            input.parse().map(PyClassPyO3Option::Generic)
166        } else {
167            Err(lookahead.error())
168        }
169    }
170}
171
172impl Parse for PyClassPyO3Options {
173    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
174        let mut options: PyClassPyO3Options = Default::default();
175
176        for option in Punctuated::<PyClassPyO3Option, syn::Token![,]>::parse_terminated(input)? {
177            options.set_option(option)?;
178        }
179
180        Ok(options)
181    }
182}
183
184impl PyClassPyO3Options {
185    pub fn take_pyo3_options(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
186        take_pyo3_options(attrs)?
187            .into_iter()
188            .try_for_each(|option| self.set_option(option))
189    }
190
191    fn set_option(&mut self, option: PyClassPyO3Option) -> syn::Result<()> {
192        macro_rules! set_option {
193            ($key:ident) => {
194                {
195                    ensure_spanned!(
196                        self.$key.is_none(),
197                        $key.span() => concat!("`", stringify!($key), "` may only be specified once")
198                    );
199                    self.$key = Some($key);
200                }
201            };
202        }
203
204        match option {
205            PyClassPyO3Option::Crate(krate) => set_option!(krate),
206            PyClassPyO3Option::Dict(dict) => {
207                ensure_spanned!(
208                    !is_abi3_before(3, 9),
209                    dict.span() => "`dict` requires Python >= 3.9 when using the `abi3` feature"
210                );
211                set_option!(dict);
212            }
213            PyClassPyO3Option::Eq(eq) => set_option!(eq),
214            PyClassPyO3Option::EqInt(eq_int) => set_option!(eq_int),
215            PyClassPyO3Option::Extends(extends) => set_option!(extends),
216            PyClassPyO3Option::Freelist(freelist) => set_option!(freelist),
217            PyClassPyO3Option::Frozen(frozen) => set_option!(frozen),
218            PyClassPyO3Option::GetAll(get_all) => set_option!(get_all),
219            PyClassPyO3Option::ImmutableType(immutable_type) => {
220                ensure_spanned!(
221                    !(is_py_before(3, 10) || is_abi3_before(3, 14)),
222                    immutable_type.span() => "`immutable_type` requires Python >= 3.10 or >= 3.14 (ABI3)"
223                );
224                set_option!(immutable_type)
225            }
226            PyClassPyO3Option::Hash(hash) => set_option!(hash),
227            PyClassPyO3Option::Mapping(mapping) => set_option!(mapping),
228            PyClassPyO3Option::Module(module) => set_option!(module),
229            PyClassPyO3Option::Name(name) => set_option!(name),
230            PyClassPyO3Option::Ord(ord) => set_option!(ord),
231            PyClassPyO3Option::RenameAll(rename_all) => set_option!(rename_all),
232            PyClassPyO3Option::Sequence(sequence) => set_option!(sequence),
233            PyClassPyO3Option::SetAll(set_all) => set_option!(set_all),
234            PyClassPyO3Option::Str(str) => set_option!(str),
235            PyClassPyO3Option::Subclass(subclass) => set_option!(subclass),
236            PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable),
237            PyClassPyO3Option::Weakref(weakref) => {
238                ensure_spanned!(
239                    !is_abi3_before(3, 9),
240                    weakref.span() => "`weakref` requires Python >= 3.9 when using the `abi3` feature"
241                );
242                set_option!(weakref);
243            }
244            PyClassPyO3Option::Generic(generic) => set_option!(generic),
245        }
246        Ok(())
247    }
248}
249
250pub fn build_py_class(
251    class: &mut syn::ItemStruct,
252    mut args: PyClassArgs,
253    methods_type: PyClassMethodsType,
254) -> syn::Result<TokenStream> {
255    args.options.take_pyo3_options(&mut class.attrs)?;
256
257    let ctx = &Ctx::new(&args.options.krate, None);
258    let doc = utils::get_doc(&class.attrs, None, ctx)?;
259
260    if let Some(lt) = class.generics.lifetimes().next() {
261        bail_spanned!(
262            lt.span() => concat!(
263                "#[pyclass] cannot have lifetime parameters. For an explanation, see \
264                https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#no-lifetime-parameters"
265            )
266        );
267    }
268
269    ensure_spanned!(
270        class.generics.params.is_empty(),
271        class.generics.span() => concat!(
272            "#[pyclass] cannot have generic parameters. For an explanation, see \
273            https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#no-generic-parameters"
274        )
275    );
276
277    let mut field_options: Vec<(&syn::Field, FieldPyO3Options)> = match &mut class.fields {
278        syn::Fields::Named(fields) => fields
279            .named
280            .iter_mut()
281            .map(
282                |field| match FieldPyO3Options::take_pyo3_options(&mut field.attrs) {
283                    Ok(options) => Ok((&*field, options)),
284                    Err(e) => Err(e),
285                },
286            )
287            .collect::<Vec<_>>(),
288        syn::Fields::Unnamed(fields) => fields
289            .unnamed
290            .iter_mut()
291            .map(
292                |field| match FieldPyO3Options::take_pyo3_options(&mut field.attrs) {
293                    Ok(options) => Ok((&*field, options)),
294                    Err(e) => Err(e),
295                },
296            )
297            .collect::<Vec<_>>(),
298        syn::Fields::Unit => {
299            let mut results = Vec::new();
300
301            if let Some(attr) = args.options.set_all {
302                results.push(Err(syn::Error::new_spanned(attr, UNIT_SET)));
303            };
304            if let Some(attr) = args.options.get_all {
305                results.push(Err(syn::Error::new_spanned(attr, UNIT_GET)));
306            };
307
308            results
309        }
310    }
311    .into_iter()
312    .try_combine_syn_errors()?;
313
314    if let Some(attr) = args.options.get_all {
315        for (_, FieldPyO3Options { get, .. }) in &mut field_options {
316            if let Some(old_get) = get.replace(Annotated::Struct(attr)) {
317                return Err(syn::Error::new(old_get.span(), DUPE_GET));
318            }
319        }
320    }
321
322    if let Some(attr) = args.options.set_all {
323        for (_, FieldPyO3Options { set, .. }) in &mut field_options {
324            if let Some(old_set) = set.replace(Annotated::Struct(attr)) {
325                return Err(syn::Error::new(old_set.span(), DUPE_SET));
326            }
327        }
328    }
329
330    impl_class(&class.ident, &args, doc, field_options, methods_type, ctx)
331}
332
333enum Annotated<X, Y> {
334    Field(X),
335    Struct(Y),
336}
337
338impl<X: Spanned, Y: Spanned> Annotated<X, Y> {
339    fn span(&self) -> Span {
340        match self {
341            Self::Field(x) => x.span(),
342            Self::Struct(y) => y.span(),
343        }
344    }
345}
346
347/// `#[pyo3()]` options for pyclass fields
348struct FieldPyO3Options {
349    get: Option<Annotated<kw::get, kw::get_all>>,
350    set: Option<Annotated<kw::set, kw::set_all>>,
351    name: Option<NameAttribute>,
352}
353
354enum FieldPyO3Option {
355    Get(attributes::kw::get),
356    Set(attributes::kw::set),
357    Name(NameAttribute),
358}
359
360impl Parse for FieldPyO3Option {
361    fn parse(input: ParseStream<'_>) -> Result<Self> {
362        let lookahead = input.lookahead1();
363        if lookahead.peek(attributes::kw::get) {
364            input.parse().map(FieldPyO3Option::Get)
365        } else if lookahead.peek(attributes::kw::set) {
366            input.parse().map(FieldPyO3Option::Set)
367        } else if lookahead.peek(attributes::kw::name) {
368            input.parse().map(FieldPyO3Option::Name)
369        } else {
370            Err(lookahead.error())
371        }
372    }
373}
374
375impl FieldPyO3Options {
376    fn take_pyo3_options(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
377        let mut options = FieldPyO3Options {
378            get: None,
379            set: None,
380            name: None,
381        };
382
383        for option in take_pyo3_options(attrs)? {
384            match option {
385                FieldPyO3Option::Get(kw) => {
386                    if options.get.replace(Annotated::Field(kw)).is_some() {
387                        return Err(syn::Error::new(kw.span(), UNIQUE_GET));
388                    }
389                }
390                FieldPyO3Option::Set(kw) => {
391                    if options.set.replace(Annotated::Field(kw)).is_some() {
392                        return Err(syn::Error::new(kw.span(), UNIQUE_SET));
393                    }
394                }
395                FieldPyO3Option::Name(name) => {
396                    if options.name.replace(name).is_some() {
397                        return Err(syn::Error::new(options.name.span(), UNIQUE_NAME));
398                    }
399                }
400            }
401        }
402
403        Ok(options)
404    }
405}
406
407fn get_class_python_name<'a>(cls: &'a syn::Ident, args: &'a PyClassArgs) -> Cow<'a, syn::Ident> {
408    args.options
409        .name
410        .as_ref()
411        .map(|name_attr| Cow::Borrowed(&name_attr.value.0))
412        .unwrap_or_else(|| Cow::Owned(cls.unraw()))
413}
414
415fn get_class_python_module_and_name<'a>(cls: &'a Ident, args: &'a PyClassArgs) -> String {
416    let name = get_class_python_name(cls, args);
417    if let Some(module) = &args.options.module {
418        let value = module.value.value();
419        format!("{value}.{name}")
420    } else {
421        name.to_string()
422    }
423}
424
425fn impl_class(
426    cls: &syn::Ident,
427    args: &PyClassArgs,
428    doc: PythonDoc,
429    field_options: Vec<(&syn::Field, FieldPyO3Options)>,
430    methods_type: PyClassMethodsType,
431    ctx: &Ctx,
432) -> syn::Result<TokenStream> {
433    let Ctx { pyo3_path, .. } = ctx;
434    let pytypeinfo_impl = impl_pytypeinfo(cls, args, ctx);
435
436    if let Some(str) = &args.options.str {
437        if str.value.is_some() {
438            // check if any renaming is present
439            let no_naming_conflict = field_options.iter().all(|x| x.1.name.is_none())
440                & args.options.name.is_none()
441                & args.options.rename_all.is_none();
442            ensure_spanned!(no_naming_conflict, str.value.span() => "The format string syntax is incompatible with any renaming via `name` or `rename_all`");
443        }
444    }
445
446    let mut default_methods = descriptors_to_items(
447        cls,
448        args.options.rename_all.as_ref(),
449        args.options.frozen,
450        field_options,
451        ctx,
452    )?;
453
454    let (default_class_geitem, default_class_geitem_method) =
455        pyclass_class_geitem(&args.options, &syn::parse_quote!(#cls), ctx)?;
456
457    if let Some(default_class_geitem_method) = default_class_geitem_method {
458        default_methods.push(default_class_geitem_method);
459    }
460
461    let (default_str, default_str_slot) =
462        implement_pyclass_str(&args.options, &syn::parse_quote!(#cls), ctx);
463
464    let (default_richcmp, default_richcmp_slot) =
465        pyclass_richcmp(&args.options, &syn::parse_quote!(#cls), ctx)?;
466
467    let (default_hash, default_hash_slot) =
468        pyclass_hash(&args.options, &syn::parse_quote!(#cls), ctx)?;
469
470    let mut slots = Vec::new();
471    slots.extend(default_richcmp_slot);
472    slots.extend(default_hash_slot);
473    slots.extend(default_str_slot);
474
475    let py_class_impl = PyClassImplsBuilder::new(cls, args, methods_type, default_methods, slots)
476        .doc(doc)
477        .impl_all(ctx)?;
478
479    Ok(quote! {
480        impl #pyo3_path::types::DerefToPyAny for #cls {}
481
482        #pytypeinfo_impl
483
484        #py_class_impl
485
486        #[doc(hidden)]
487        #[allow(non_snake_case)]
488        impl #cls {
489            #default_richcmp
490            #default_hash
491            #default_str
492            #default_class_geitem
493        }
494    })
495}
496
497enum PyClassEnum<'a> {
498    Simple(PyClassSimpleEnum<'a>),
499    Complex(PyClassComplexEnum<'a>),
500}
501
502impl<'a> PyClassEnum<'a> {
503    fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result<Self> {
504        let has_only_unit_variants = enum_
505            .variants
506            .iter()
507            .all(|variant| matches!(variant.fields, syn::Fields::Unit));
508
509        Ok(if has_only_unit_variants {
510            let simple_enum = PyClassSimpleEnum::new(enum_)?;
511            Self::Simple(simple_enum)
512        } else {
513            let complex_enum = PyClassComplexEnum::new(enum_)?;
514            Self::Complex(complex_enum)
515        })
516    }
517}
518
519pub fn build_py_enum(
520    enum_: &mut syn::ItemEnum,
521    mut args: PyClassArgs,
522    method_type: PyClassMethodsType,
523) -> syn::Result<TokenStream> {
524    args.options.take_pyo3_options(&mut enum_.attrs)?;
525
526    let ctx = &Ctx::new(&args.options.krate, None);
527    if let Some(extends) = &args.options.extends {
528        bail_spanned!(extends.span() => "enums can't extend from other classes");
529    } else if let Some(subclass) = &args.options.subclass {
530        bail_spanned!(subclass.span() => "enums can't be inherited by other classes");
531    } else if enum_.variants.is_empty() {
532        bail_spanned!(enum_.brace_token.span.join() => "#[pyclass] can't be used on enums without any variants");
533    }
534
535    if let Some(generic) = &args.options.generic {
536        bail_spanned!(generic.span() => "enums do not support #[pyclass(generic)]");
537    }
538
539    let doc = utils::get_doc(&enum_.attrs, None, ctx)?;
540    let enum_ = PyClassEnum::new(enum_)?;
541    impl_enum(enum_, &args, doc, method_type, ctx)
542}
543
544struct PyClassSimpleEnum<'a> {
545    ident: &'a syn::Ident,
546    // The underlying #[repr] of the enum, used to implement __int__ and __richcmp__.
547    // This matters when the underlying representation may not fit in `isize`.
548    repr_type: syn::Ident,
549    variants: Vec<PyClassEnumUnitVariant<'a>>,
550}
551
552impl<'a> PyClassSimpleEnum<'a> {
553    fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result<Self> {
554        fn is_numeric_type(t: &syn::Ident) -> bool {
555            [
556                "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "u128", "i128", "usize",
557                "isize",
558            ]
559            .iter()
560            .any(|&s| t == s)
561        }
562
563        fn extract_unit_variant_data(
564            variant: &mut syn::Variant,
565        ) -> syn::Result<PyClassEnumUnitVariant<'_>> {
566            use syn::Fields;
567            let ident = match &variant.fields {
568                Fields::Unit => &variant.ident,
569                _ => bail_spanned!(variant.span() => "Must be a unit variant."),
570            };
571            let options = EnumVariantPyO3Options::take_pyo3_options(&mut variant.attrs)?;
572            let cfg_attrs = get_cfg_attributes(&variant.attrs);
573            Ok(PyClassEnumUnitVariant {
574                ident,
575                options,
576                cfg_attrs,
577            })
578        }
579
580        let ident = &enum_.ident;
581
582        // According to the [reference](https://doc.rust-lang.org/reference/items/enumerations.html),
583        // "Under the default representation, the specified discriminant is interpreted as an isize
584        // value", so `isize` should be enough by default.
585        let mut repr_type = syn::Ident::new("isize", proc_macro2::Span::call_site());
586        if let Some(attr) = enum_.attrs.iter().find(|attr| attr.path().is_ident("repr")) {
587            let args =
588                attr.parse_args_with(Punctuated::<TokenStream, Token![!]>::parse_terminated)?;
589            if let Some(ident) = args
590                .into_iter()
591                .filter_map(|ts| syn::parse2::<syn::Ident>(ts).ok())
592                .find(is_numeric_type)
593            {
594                repr_type = ident;
595            }
596        }
597
598        let variants: Vec<_> = enum_
599            .variants
600            .iter_mut()
601            .map(extract_unit_variant_data)
602            .collect::<syn::Result<_>>()?;
603        Ok(Self {
604            ident,
605            repr_type,
606            variants,
607        })
608    }
609}
610
611struct PyClassComplexEnum<'a> {
612    ident: &'a syn::Ident,
613    variants: Vec<PyClassEnumVariant<'a>>,
614}
615
616impl<'a> PyClassComplexEnum<'a> {
617    fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result<Self> {
618        let witness = enum_
619            .variants
620            .iter()
621            .find(|variant| !matches!(variant.fields, syn::Fields::Unit))
622            .expect("complex enum has a non-unit variant")
623            .ident
624            .to_owned();
625
626        let extract_variant_data =
627            |variant: &'a mut syn::Variant| -> syn::Result<PyClassEnumVariant<'a>> {
628                use syn::Fields;
629                let ident = &variant.ident;
630                let options = EnumVariantPyO3Options::take_pyo3_options(&mut variant.attrs)?;
631
632                let variant = match &variant.fields {
633                    Fields::Unit => {
634                        bail_spanned!(variant.span() => format!(
635                            "Unit variant `{ident}` is not yet supported in a complex enum\n\
636                            = help: change to an empty tuple variant instead: `{ident}()`\n\
637                            = note: the enum is complex because of non-unit variant `{witness}`",
638                            ident=ident, witness=witness))
639                    }
640                    Fields::Named(fields) => {
641                        let fields = fields
642                            .named
643                            .iter()
644                            .map(|field| PyClassEnumVariantNamedField {
645                                ident: field.ident.as_ref().expect("named field has an identifier"),
646                                ty: &field.ty,
647                                span: field.span(),
648                            })
649                            .collect();
650
651                        PyClassEnumVariant::Struct(PyClassEnumStructVariant {
652                            ident,
653                            fields,
654                            options,
655                        })
656                    }
657                    Fields::Unnamed(types) => {
658                        let fields = types
659                            .unnamed
660                            .iter()
661                            .map(|field| PyClassEnumVariantUnnamedField {
662                                ty: &field.ty,
663                                span: field.span(),
664                            })
665                            .collect();
666
667                        PyClassEnumVariant::Tuple(PyClassEnumTupleVariant {
668                            ident,
669                            fields,
670                            options,
671                        })
672                    }
673                };
674
675                Ok(variant)
676            };
677
678        let ident = &enum_.ident;
679
680        let variants: Vec<_> = enum_
681            .variants
682            .iter_mut()
683            .map(extract_variant_data)
684            .collect::<syn::Result<_>>()?;
685
686        Ok(Self { ident, variants })
687    }
688}
689
690enum PyClassEnumVariant<'a> {
691    // TODO(mkovaxx): Unit(PyClassEnumUnitVariant<'a>),
692    Struct(PyClassEnumStructVariant<'a>),
693    Tuple(PyClassEnumTupleVariant<'a>),
694}
695
696trait EnumVariant {
697    fn get_ident(&self) -> &syn::Ident;
698    fn get_options(&self) -> &EnumVariantPyO3Options;
699
700    fn get_python_name(&self, args: &PyClassArgs) -> Cow<'_, syn::Ident> {
701        self.get_options()
702            .name
703            .as_ref()
704            .map(|name_attr| Cow::Borrowed(&name_attr.value.0))
705            .unwrap_or_else(|| {
706                let name = self.get_ident().unraw();
707                if let Some(attr) = &args.options.rename_all {
708                    let new_name = apply_renaming_rule(attr.value.rule, &name.to_string());
709                    Cow::Owned(Ident::new(&new_name, Span::call_site()))
710                } else {
711                    Cow::Owned(name)
712                }
713            })
714    }
715}
716
717impl EnumVariant for PyClassEnumVariant<'_> {
718    fn get_ident(&self) -> &syn::Ident {
719        match self {
720            PyClassEnumVariant::Struct(struct_variant) => struct_variant.ident,
721            PyClassEnumVariant::Tuple(tuple_variant) => tuple_variant.ident,
722        }
723    }
724
725    fn get_options(&self) -> &EnumVariantPyO3Options {
726        match self {
727            PyClassEnumVariant::Struct(struct_variant) => &struct_variant.options,
728            PyClassEnumVariant::Tuple(tuple_variant) => &tuple_variant.options,
729        }
730    }
731}
732
733/// A unit variant has no fields
734struct PyClassEnumUnitVariant<'a> {
735    ident: &'a syn::Ident,
736    options: EnumVariantPyO3Options,
737    cfg_attrs: Vec<&'a syn::Attribute>,
738}
739
740impl EnumVariant for PyClassEnumUnitVariant<'_> {
741    fn get_ident(&self) -> &syn::Ident {
742        self.ident
743    }
744
745    fn get_options(&self) -> &EnumVariantPyO3Options {
746        &self.options
747    }
748}
749
750/// A struct variant has named fields
751struct PyClassEnumStructVariant<'a> {
752    ident: &'a syn::Ident,
753    fields: Vec<PyClassEnumVariantNamedField<'a>>,
754    options: EnumVariantPyO3Options,
755}
756
757struct PyClassEnumTupleVariant<'a> {
758    ident: &'a syn::Ident,
759    fields: Vec<PyClassEnumVariantUnnamedField<'a>>,
760    options: EnumVariantPyO3Options,
761}
762
763struct PyClassEnumVariantNamedField<'a> {
764    ident: &'a syn::Ident,
765    ty: &'a syn::Type,
766    span: Span,
767}
768
769struct PyClassEnumVariantUnnamedField<'a> {
770    ty: &'a syn::Type,
771    span: Span,
772}
773
774/// `#[pyo3()]` options for pyclass enum variants
775#[derive(Clone, Default)]
776struct EnumVariantPyO3Options {
777    name: Option<NameAttribute>,
778    constructor: Option<ConstructorAttribute>,
779}
780
781enum EnumVariantPyO3Option {
782    Name(NameAttribute),
783    Constructor(ConstructorAttribute),
784}
785
786impl Parse for EnumVariantPyO3Option {
787    fn parse(input: ParseStream<'_>) -> Result<Self> {
788        let lookahead = input.lookahead1();
789        if lookahead.peek(attributes::kw::name) {
790            input.parse().map(EnumVariantPyO3Option::Name)
791        } else if lookahead.peek(attributes::kw::constructor) {
792            input.parse().map(EnumVariantPyO3Option::Constructor)
793        } else {
794            Err(lookahead.error())
795        }
796    }
797}
798
799impl EnumVariantPyO3Options {
800    fn take_pyo3_options(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
801        let mut options = EnumVariantPyO3Options::default();
802
803        take_pyo3_options(attrs)?
804            .into_iter()
805            .try_for_each(|option| options.set_option(option))?;
806
807        Ok(options)
808    }
809
810    fn set_option(&mut self, option: EnumVariantPyO3Option) -> syn::Result<()> {
811        macro_rules! set_option {
812            ($key:ident) => {
813                {
814                    ensure_spanned!(
815                        self.$key.is_none(),
816                        $key.span() => concat!("`", stringify!($key), "` may only be specified once")
817                    );
818                    self.$key = Some($key);
819                }
820            };
821        }
822
823        match option {
824            EnumVariantPyO3Option::Constructor(constructor) => set_option!(constructor),
825            EnumVariantPyO3Option::Name(name) => set_option!(name),
826        }
827        Ok(())
828    }
829}
830
831// todo(remove this dead code allowance once __repr__ is implemented
832#[allow(dead_code)]
833pub enum PyFmtName {
834    Str,
835    Repr,
836}
837
838fn implement_py_formatting(
839    ty: &syn::Type,
840    ctx: &Ctx,
841    option: &StrFormatterAttribute,
842) -> (ImplItemFn, MethodAndSlotDef) {
843    let mut fmt_impl = match &option.value {
844        Some(opt) => {
845            let fmt = &opt.fmt;
846            let args = &opt
847                .args
848                .iter()
849                .map(|member| quote! {self.#member})
850                .collect::<Vec<TokenStream>>();
851            let fmt_impl: ImplItemFn = syn::parse_quote! {
852                fn __pyo3__generated____str__(&self) -> ::std::string::String {
853                    ::std::format!(#fmt, #(#args, )*)
854                }
855            };
856            fmt_impl
857        }
858        None => {
859            let fmt_impl: syn::ImplItemFn = syn::parse_quote! {
860                fn __pyo3__generated____str__(&self) -> ::std::string::String {
861                    ::std::format!("{}", &self)
862                }
863            };
864            fmt_impl
865        }
866    };
867    let fmt_slot = generate_protocol_slot(
868        ty,
869        &mut fmt_impl,
870        &__STR__,
871        "__str__",
872        #[cfg(feature = "experimental-inspect")]
873        FunctionIntrospectionData {
874            names: &["__str__"],
875            arguments: Vec::new(),
876            returns: parse_quote! { ::std::string::String },
877        },
878        ctx,
879    )
880    .unwrap();
881    (fmt_impl, fmt_slot)
882}
883
884fn implement_pyclass_str(
885    options: &PyClassPyO3Options,
886    ty: &syn::Type,
887    ctx: &Ctx,
888) -> (Option<ImplItemFn>, Option<MethodAndSlotDef>) {
889    match &options.str {
890        Some(option) => {
891            let (default_str, default_str_slot) = implement_py_formatting(ty, ctx, option);
892            (Some(default_str), Some(default_str_slot))
893        }
894        _ => (None, None),
895    }
896}
897
898fn impl_enum(
899    enum_: PyClassEnum<'_>,
900    args: &PyClassArgs,
901    doc: PythonDoc,
902    methods_type: PyClassMethodsType,
903    ctx: &Ctx,
904) -> Result<TokenStream> {
905    if let Some(str_fmt) = &args.options.str {
906        ensure_spanned!(str_fmt.value.is_none(), str_fmt.value.span() => "The format string syntax cannot be used with enums")
907    }
908
909    match enum_ {
910        PyClassEnum::Simple(simple_enum) => {
911            impl_simple_enum(simple_enum, args, doc, methods_type, ctx)
912        }
913        PyClassEnum::Complex(complex_enum) => {
914            impl_complex_enum(complex_enum, args, doc, methods_type, ctx)
915        }
916    }
917}
918
919fn impl_simple_enum(
920    simple_enum: PyClassSimpleEnum<'_>,
921    args: &PyClassArgs,
922    doc: PythonDoc,
923    methods_type: PyClassMethodsType,
924    ctx: &Ctx,
925) -> Result<TokenStream> {
926    let cls = simple_enum.ident;
927    let ty: syn::Type = syn::parse_quote!(#cls);
928    let variants = simple_enum.variants;
929    let pytypeinfo = impl_pytypeinfo(cls, args, ctx);
930
931    for variant in &variants {
932        ensure_spanned!(variant.options.constructor.is_none(), variant.options.constructor.span() => "`constructor` can't be used on a simple enum variant");
933    }
934
935    let variant_cfg_check = generate_cfg_check(&variants, cls);
936
937    let (default_repr, default_repr_slot) = {
938        let variants_repr = variants.iter().map(|variant| {
939            let variant_name = variant.ident;
940            let cfg_attrs = &variant.cfg_attrs;
941            // Assuming all variants are unit variants because they are the only type we support.
942            let repr = format!(
943                "{}.{}",
944                get_class_python_name(cls, args),
945                variant.get_python_name(args),
946            );
947            quote! { #(#cfg_attrs)* #cls::#variant_name => #repr, }
948        });
949        let mut repr_impl: syn::ImplItemFn = syn::parse_quote! {
950            fn __pyo3__repr__(&self) -> &'static str {
951                match *self {
952                    #(#variants_repr)*
953                }
954            }
955        };
956        let repr_slot = generate_default_protocol_slot(&ty, &mut repr_impl, &__REPR__, ctx)?;
957        (repr_impl, repr_slot)
958    };
959
960    let (default_str, default_str_slot) = implement_pyclass_str(&args.options, &ty, ctx);
961
962    let repr_type = &simple_enum.repr_type;
963
964    let (default_int, default_int_slot) = {
965        // This implementation allows us to convert &T to #repr_type without implementing `Copy`
966        let variants_to_int = variants.iter().map(|variant| {
967            let variant_name = variant.ident;
968            let cfg_attrs = &variant.cfg_attrs;
969            quote! { #(#cfg_attrs)* #cls::#variant_name => #cls::#variant_name as #repr_type, }
970        });
971        let mut int_impl: syn::ImplItemFn = syn::parse_quote! {
972            fn __pyo3__int__(&self) -> #repr_type {
973                match *self {
974                    #(#variants_to_int)*
975                }
976            }
977        };
978        let int_slot = generate_default_protocol_slot(&ty, &mut int_impl, &__INT__, ctx)?;
979        (int_impl, int_slot)
980    };
981
982    let (default_richcmp, default_richcmp_slot) = pyclass_richcmp_simple_enum(
983        &args.options,
984        &ty,
985        repr_type,
986        #[cfg(feature = "experimental-inspect")]
987        &get_class_python_name(cls, args).to_string(),
988        ctx,
989    )?;
990    let (default_hash, default_hash_slot) = pyclass_hash(&args.options, &ty, ctx)?;
991
992    let mut default_slots = vec![default_repr_slot, default_int_slot];
993    default_slots.extend(default_richcmp_slot);
994    default_slots.extend(default_hash_slot);
995    default_slots.extend(default_str_slot);
996
997    let pyclass_impls = PyClassImplsBuilder::new(
998        cls,
999        args,
1000        methods_type,
1001        simple_enum_default_methods(
1002            cls,
1003            variants
1004                .iter()
1005                .map(|v| (v.ident, v.get_python_name(args), &v.cfg_attrs)),
1006            ctx,
1007        ),
1008        default_slots,
1009    )
1010    .doc(doc)
1011    .impl_all(ctx)?;
1012
1013    Ok(quote! {
1014        #variant_cfg_check
1015
1016        #pytypeinfo
1017
1018        #pyclass_impls
1019
1020        #[doc(hidden)]
1021        #[allow(non_snake_case)]
1022        impl #cls {
1023            #default_repr
1024            #default_int
1025            #default_richcmp
1026            #default_hash
1027            #default_str
1028        }
1029    })
1030}
1031
1032fn impl_complex_enum(
1033    complex_enum: PyClassComplexEnum<'_>,
1034    args: &PyClassArgs,
1035    doc: PythonDoc,
1036    methods_type: PyClassMethodsType,
1037    ctx: &Ctx,
1038) -> Result<TokenStream> {
1039    let Ctx { pyo3_path, .. } = ctx;
1040    let cls = complex_enum.ident;
1041    let ty: syn::Type = syn::parse_quote!(#cls);
1042
1043    // Need to rig the enum PyClass options
1044    let args = {
1045        let mut rigged_args = args.clone();
1046        // Needs to be frozen to disallow `&mut self` methods, which could break a runtime invariant
1047        rigged_args.options.frozen = parse_quote!(frozen);
1048        // Needs to be subclassable by the variant PyClasses
1049        rigged_args.options.subclass = parse_quote!(subclass);
1050        rigged_args
1051    };
1052
1053    let ctx = &Ctx::new(&args.options.krate, None);
1054    let cls = complex_enum.ident;
1055    let variants = complex_enum.variants;
1056    let pytypeinfo = impl_pytypeinfo(cls, &args, ctx);
1057
1058    let (default_richcmp, default_richcmp_slot) = pyclass_richcmp(&args.options, &ty, ctx)?;
1059    let (default_hash, default_hash_slot) = pyclass_hash(&args.options, &ty, ctx)?;
1060
1061    let (default_str, default_str_slot) = implement_pyclass_str(&args.options, &ty, ctx);
1062
1063    let mut default_slots = vec![];
1064    default_slots.extend(default_richcmp_slot);
1065    default_slots.extend(default_hash_slot);
1066    default_slots.extend(default_str_slot);
1067
1068    let impl_builder = PyClassImplsBuilder::new(
1069        cls,
1070        &args,
1071        methods_type,
1072        complex_enum_default_methods(
1073            cls,
1074            variants
1075                .iter()
1076                .map(|v| (v.get_ident(), v.get_python_name(&args))),
1077            ctx,
1078        ),
1079        default_slots,
1080    )
1081    .doc(doc);
1082
1083    let enum_into_pyobject_impl = {
1084        let match_arms = variants
1085            .iter()
1086            .map(|variant| {
1087                let variant_ident = variant.get_ident();
1088                let variant_cls = gen_complex_enum_variant_class_ident(cls, variant.get_ident());
1089                quote! {
1090                    #cls::#variant_ident { .. } => {
1091                        let pyclass_init = <#pyo3_path::PyClassInitializer<Self> as ::std::convert::From<Self>>::from(self).add_subclass(#variant_cls);
1092                        unsafe { #pyo3_path::Bound::new(py, pyclass_init).map(|b| b.cast_into_unchecked()) }
1093                    }
1094                }
1095            });
1096        let output_type = if cfg!(feature = "experimental-inspect") {
1097            let full_name = get_class_python_module_and_name(cls, &args);
1098            quote! { const OUTPUT_TYPE: &'static str = #full_name; }
1099        } else {
1100            quote! {}
1101        };
1102        quote! {
1103            impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls {
1104                type Target = Self;
1105                type Output = #pyo3_path::Bound<'py, <Self as #pyo3_path::conversion::IntoPyObject<'py>>::Target>;
1106                type Error = #pyo3_path::PyErr;
1107                #output_type
1108
1109                fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result<
1110                    <Self as #pyo3_path::conversion::IntoPyObject>::Output,
1111                    <Self as #pyo3_path::conversion::IntoPyObject>::Error,
1112                > {
1113                    match self {
1114                        #(#match_arms)*
1115                    }
1116                }
1117            }
1118        }
1119    };
1120
1121    let pyclass_impls: TokenStream = [
1122        impl_builder.impl_pyclass(ctx),
1123        impl_builder.impl_extractext(ctx),
1124        enum_into_pyobject_impl,
1125        impl_builder.impl_pyclassimpl(ctx)?,
1126        impl_builder.impl_add_to_module(ctx),
1127        impl_builder.impl_freelist(ctx),
1128        impl_builder.impl_introspection(ctx),
1129    ]
1130    .into_iter()
1131    .collect();
1132
1133    let mut variant_cls_zsts = vec![];
1134    let mut variant_cls_pytypeinfos = vec![];
1135    let mut variant_cls_pyclass_impls = vec![];
1136    let mut variant_cls_impls = vec![];
1137    for variant in variants {
1138        let variant_cls = gen_complex_enum_variant_class_ident(cls, variant.get_ident());
1139
1140        let variant_cls_zst = quote! {
1141            #[doc(hidden)]
1142            #[allow(non_camel_case_types)]
1143            struct #variant_cls;
1144        };
1145        variant_cls_zsts.push(variant_cls_zst);
1146
1147        let variant_args = PyClassArgs {
1148            class_kind: PyClassKind::Struct,
1149            // TODO(mkovaxx): propagate variant.options
1150            options: {
1151                let mut rigged_options: PyClassPyO3Options = parse_quote!(extends = #cls, frozen);
1152                // If a specific module was given to the base class, use it for all variants.
1153                rigged_options.module.clone_from(&args.options.module);
1154                rigged_options
1155            },
1156        };
1157
1158        let variant_cls_pytypeinfo = impl_pytypeinfo(&variant_cls, &variant_args, ctx);
1159        variant_cls_pytypeinfos.push(variant_cls_pytypeinfo);
1160
1161        let (variant_cls_impl, field_getters, mut slots) =
1162            impl_complex_enum_variant_cls(cls, &variant, ctx)?;
1163        variant_cls_impls.push(variant_cls_impl);
1164
1165        let variant_new = complex_enum_variant_new(cls, variant, ctx)?;
1166        slots.push(variant_new);
1167
1168        let pyclass_impl = PyClassImplsBuilder::new(
1169            &variant_cls,
1170            &variant_args,
1171            methods_type,
1172            field_getters,
1173            slots,
1174        )
1175        .impl_all(ctx)?;
1176
1177        variant_cls_pyclass_impls.push(pyclass_impl);
1178    }
1179
1180    Ok(quote! {
1181        #pytypeinfo
1182
1183        #pyclass_impls
1184
1185        #[doc(hidden)]
1186        #[allow(non_snake_case)]
1187        impl #cls {
1188            #default_richcmp
1189            #default_hash
1190            #default_str
1191        }
1192
1193        #(#variant_cls_zsts)*
1194
1195        #(#variant_cls_pytypeinfos)*
1196
1197        #(#variant_cls_pyclass_impls)*
1198
1199        #(#variant_cls_impls)*
1200    })
1201}
1202
1203fn impl_complex_enum_variant_cls(
1204    enum_name: &syn::Ident,
1205    variant: &PyClassEnumVariant<'_>,
1206    ctx: &Ctx,
1207) -> Result<(TokenStream, Vec<MethodAndMethodDef>, Vec<MethodAndSlotDef>)> {
1208    match variant {
1209        PyClassEnumVariant::Struct(struct_variant) => {
1210            impl_complex_enum_struct_variant_cls(enum_name, struct_variant, ctx)
1211        }
1212        PyClassEnumVariant::Tuple(tuple_variant) => {
1213            impl_complex_enum_tuple_variant_cls(enum_name, tuple_variant, ctx)
1214        }
1215    }
1216}
1217
1218fn impl_complex_enum_variant_match_args(
1219    ctx @ Ctx { pyo3_path, .. }: &Ctx,
1220    variant_cls_type: &syn::Type,
1221    field_names: &[Ident],
1222) -> syn::Result<(MethodAndMethodDef, syn::ImplItemFn)> {
1223    let ident = format_ident!("__match_args__");
1224    let field_names_unraw = field_names.iter().map(|name| name.unraw());
1225    let mut match_args_impl: syn::ImplItemFn = {
1226        parse_quote! {
1227            #[classattr]
1228            fn #ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'_, #pyo3_path::types::PyTuple>> {
1229                #pyo3_path::types::PyTuple::new::<&str, _>(py, [
1230                    #(stringify!(#field_names_unraw),)*
1231                ])
1232            }
1233        }
1234    };
1235
1236    let spec = FnSpec::parse(
1237        &mut match_args_impl.sig,
1238        &mut match_args_impl.attrs,
1239        Default::default(),
1240    )?;
1241    let variant_match_args = impl_py_class_attribute(variant_cls_type, &spec, ctx)?;
1242
1243    Ok((variant_match_args, match_args_impl))
1244}
1245
1246fn impl_complex_enum_struct_variant_cls(
1247    enum_name: &syn::Ident,
1248    variant: &PyClassEnumStructVariant<'_>,
1249    ctx: &Ctx,
1250) -> Result<(TokenStream, Vec<MethodAndMethodDef>, Vec<MethodAndSlotDef>)> {
1251    let Ctx { pyo3_path, .. } = ctx;
1252    let variant_ident = &variant.ident;
1253    let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident);
1254    let variant_cls_type = parse_quote!(#variant_cls);
1255
1256    let mut field_names: Vec<Ident> = vec![];
1257    let mut fields_with_types: Vec<TokenStream> = vec![];
1258    let mut field_getters = vec![];
1259    let mut field_getter_impls: Vec<TokenStream> = vec![];
1260    for field in &variant.fields {
1261        let field_name = field.ident;
1262        let field_type = field.ty;
1263        let field_with_type = quote! { #field_name: #field_type };
1264
1265        let field_getter =
1266            complex_enum_variant_field_getter(&variant_cls_type, field_name, field.span, ctx)?;
1267
1268        let field_getter_impl = quote! {
1269            fn #field_name(slf: #pyo3_path::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> {
1270                #[allow(unused_imports)]
1271                use #pyo3_path::impl_::pyclass::Probe as _;
1272                match &*slf.into_super() {
1273                    #enum_name::#variant_ident { #field_name, .. } =>
1274                        #pyo3_path::impl_::pyclass::ConvertField::<
1275                            { #pyo3_path::impl_::pyclass::IsIntoPyObjectRef::<#field_type>::VALUE },
1276                            { #pyo3_path::impl_::pyclass::IsIntoPyObject::<#field_type>::VALUE },
1277                        >::convert_field::<#field_type>(#field_name, py),
1278                    _ => ::core::unreachable!("Wrong complex enum variant found in variant wrapper PyClass"),
1279                }
1280            }
1281        };
1282
1283        field_names.push(field_name.clone());
1284        fields_with_types.push(field_with_type);
1285        field_getters.push(field_getter);
1286        field_getter_impls.push(field_getter_impl);
1287    }
1288
1289    let (variant_match_args, match_args_const_impl) =
1290        impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &field_names)?;
1291
1292    field_getters.push(variant_match_args);
1293
1294    let cls_impl = quote! {
1295        #[doc(hidden)]
1296        #[allow(non_snake_case)]
1297        impl #variant_cls {
1298            #[allow(clippy::too_many_arguments)]
1299            fn __pymethod_constructor__(py: #pyo3_path::Python<'_>, #(#fields_with_types,)*) -> #pyo3_path::PyClassInitializer<#variant_cls> {
1300                let base_value = #enum_name::#variant_ident { #(#field_names,)* };
1301                <#pyo3_path::PyClassInitializer<#enum_name> as ::std::convert::From<#enum_name>>::from(base_value).add_subclass(#variant_cls)
1302            }
1303
1304            #match_args_const_impl
1305
1306            #(#field_getter_impls)*
1307        }
1308    };
1309
1310    Ok((cls_impl, field_getters, Vec::new()))
1311}
1312
1313fn impl_complex_enum_tuple_variant_field_getters(
1314    ctx: &Ctx,
1315    variant: &PyClassEnumTupleVariant<'_>,
1316    enum_name: &syn::Ident,
1317    variant_cls_type: &syn::Type,
1318    variant_ident: &&Ident,
1319    field_names: &mut Vec<Ident>,
1320    fields_types: &mut Vec<syn::Type>,
1321) -> Result<(Vec<MethodAndMethodDef>, Vec<syn::ImplItemFn>)> {
1322    let Ctx { pyo3_path, .. } = ctx;
1323
1324    let mut field_getters = vec![];
1325    let mut field_getter_impls = vec![];
1326
1327    for (index, field) in variant.fields.iter().enumerate() {
1328        let field_name = format_ident!("_{}", index);
1329        let field_type = field.ty;
1330
1331        let field_getter =
1332            complex_enum_variant_field_getter(variant_cls_type, &field_name, field.span, ctx)?;
1333
1334        // Generate the match arms needed to destructure the tuple and access the specific field
1335        let field_access_tokens: Vec<_> = (0..variant.fields.len())
1336            .map(|i| {
1337                if i == index {
1338                    quote! { val }
1339                } else {
1340                    quote! { _ }
1341                }
1342            })
1343            .collect();
1344        let field_getter_impl: syn::ImplItemFn = parse_quote! {
1345            fn #field_name(slf: #pyo3_path::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> {
1346                #[allow(unused_imports)]
1347                use #pyo3_path::impl_::pyclass::Probe as _;
1348                match &*slf.into_super() {
1349                    #enum_name::#variant_ident ( #(#field_access_tokens), *) =>
1350                        #pyo3_path::impl_::pyclass::ConvertField::<
1351                            { #pyo3_path::impl_::pyclass::IsIntoPyObjectRef::<#field_type>::VALUE },
1352                            { #pyo3_path::impl_::pyclass::IsIntoPyObject::<#field_type>::VALUE },
1353                        >::convert_field::<#field_type>(val, py),
1354                    _ => ::core::unreachable!("Wrong complex enum variant found in variant wrapper PyClass"),
1355                }
1356            }
1357        };
1358
1359        field_names.push(field_name);
1360        fields_types.push(field_type.clone());
1361        field_getters.push(field_getter);
1362        field_getter_impls.push(field_getter_impl);
1363    }
1364
1365    Ok((field_getters, field_getter_impls))
1366}
1367
1368fn impl_complex_enum_tuple_variant_len(
1369    ctx: &Ctx,
1370
1371    variant_cls_type: &syn::Type,
1372    num_fields: usize,
1373) -> Result<(MethodAndSlotDef, syn::ImplItemFn)> {
1374    let Ctx { pyo3_path, .. } = ctx;
1375
1376    let mut len_method_impl: syn::ImplItemFn = parse_quote! {
1377        fn __len__(slf: #pyo3_path::PyClassGuard<'_, Self>) -> #pyo3_path::PyResult<usize> {
1378            ::std::result::Result::Ok(#num_fields)
1379        }
1380    };
1381
1382    let variant_len =
1383        generate_default_protocol_slot(variant_cls_type, &mut len_method_impl, &__LEN__, ctx)?;
1384
1385    Ok((variant_len, len_method_impl))
1386}
1387
1388fn impl_complex_enum_tuple_variant_getitem(
1389    ctx: &Ctx,
1390    variant_cls: &syn::Ident,
1391    variant_cls_type: &syn::Type,
1392    num_fields: usize,
1393) -> Result<(MethodAndSlotDef, syn::ImplItemFn)> {
1394    let Ctx { pyo3_path, .. } = ctx;
1395
1396    let match_arms: Vec<_> = (0..num_fields)
1397        .map(|i| {
1398            let field_access = format_ident!("_{}", i);
1399            quote! { #i =>
1400                #pyo3_path::IntoPyObjectExt::into_py_any(#variant_cls::#field_access(slf, py)?, py)
1401            }
1402        })
1403        .collect();
1404
1405    let mut get_item_method_impl: syn::ImplItemFn = parse_quote! {
1406        fn __getitem__(slf: #pyo3_path::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>, idx: usize) -> #pyo3_path::PyResult< #pyo3_path::Py<#pyo3_path::PyAny>> {
1407            match idx {
1408                #( #match_arms, )*
1409                _ => ::std::result::Result::Err(#pyo3_path::exceptions::PyIndexError::new_err("tuple index out of range")),
1410            }
1411        }
1412    };
1413
1414    let variant_getitem = generate_default_protocol_slot(
1415        variant_cls_type,
1416        &mut get_item_method_impl,
1417        &__GETITEM__,
1418        ctx,
1419    )?;
1420
1421    Ok((variant_getitem, get_item_method_impl))
1422}
1423
1424fn impl_complex_enum_tuple_variant_cls(
1425    enum_name: &syn::Ident,
1426    variant: &PyClassEnumTupleVariant<'_>,
1427    ctx: &Ctx,
1428) -> Result<(TokenStream, Vec<MethodAndMethodDef>, Vec<MethodAndSlotDef>)> {
1429    let Ctx { pyo3_path, .. } = ctx;
1430    let variant_ident = &variant.ident;
1431    let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident);
1432    let variant_cls_type = parse_quote!(#variant_cls);
1433
1434    let mut slots = vec![];
1435
1436    // represents the index of the field
1437    let mut field_names: Vec<Ident> = vec![];
1438    let mut field_types: Vec<syn::Type> = vec![];
1439
1440    let (mut field_getters, field_getter_impls) = impl_complex_enum_tuple_variant_field_getters(
1441        ctx,
1442        variant,
1443        enum_name,
1444        &variant_cls_type,
1445        variant_ident,
1446        &mut field_names,
1447        &mut field_types,
1448    )?;
1449
1450    let num_fields = variant.fields.len();
1451
1452    let (variant_len, len_method_impl) =
1453        impl_complex_enum_tuple_variant_len(ctx, &variant_cls_type, num_fields)?;
1454
1455    slots.push(variant_len);
1456
1457    let (variant_getitem, getitem_method_impl) =
1458        impl_complex_enum_tuple_variant_getitem(ctx, &variant_cls, &variant_cls_type, num_fields)?;
1459
1460    slots.push(variant_getitem);
1461
1462    let (variant_match_args, match_args_method_impl) =
1463        impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &field_names)?;
1464
1465    field_getters.push(variant_match_args);
1466
1467    let cls_impl = quote! {
1468        #[doc(hidden)]
1469        #[allow(non_snake_case)]
1470        impl #variant_cls {
1471            #[allow(clippy::too_many_arguments)]
1472            fn __pymethod_constructor__(py: #pyo3_path::Python<'_>, #(#field_names : #field_types,)*) -> #pyo3_path::PyClassInitializer<#variant_cls> {
1473                let base_value = #enum_name::#variant_ident ( #(#field_names,)* );
1474                <#pyo3_path::PyClassInitializer<#enum_name> as ::std::convert::From<#enum_name>>::from(base_value).add_subclass(#variant_cls)
1475            }
1476
1477            #len_method_impl
1478
1479            #getitem_method_impl
1480
1481            #match_args_method_impl
1482
1483            #(#field_getter_impls)*
1484        }
1485    };
1486
1487    Ok((cls_impl, field_getters, slots))
1488}
1489
1490fn gen_complex_enum_variant_class_ident(enum_: &syn::Ident, variant: &syn::Ident) -> syn::Ident {
1491    format_ident!("{}_{}", enum_, variant)
1492}
1493
1494#[cfg(feature = "experimental-inspect")]
1495struct FunctionIntrospectionData<'a> {
1496    names: &'a [&'a str],
1497    arguments: Vec<FnArg<'a>>,
1498    returns: syn::Type,
1499}
1500
1501fn generate_protocol_slot(
1502    cls: &syn::Type,
1503    method: &mut syn::ImplItemFn,
1504    slot: &SlotDef,
1505    name: &str,
1506    #[cfg(feature = "experimental-inspect")] introspection_data: FunctionIntrospectionData<'_>,
1507    ctx: &Ctx,
1508) -> syn::Result<MethodAndSlotDef> {
1509    let spec = FnSpec::parse(
1510        &mut method.sig,
1511        &mut Vec::new(),
1512        PyFunctionOptions::default(),
1513    )?;
1514    #[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))]
1515    let mut def = slot.generate_type_slot(&syn::parse_quote!(#cls), &spec, name, ctx)?;
1516    #[cfg(feature = "experimental-inspect")]
1517    {
1518        // We generate introspection data
1519        let associated_method = def.associated_method;
1520        let signature = FunctionSignature::from_arguments(introspection_data.arguments);
1521        let returns = introspection_data.returns;
1522        let introspection = introspection_data
1523            .names
1524            .iter()
1525            .map(|name| {
1526                function_introspection_code(
1527                    &ctx.pyo3_path,
1528                    None,
1529                    name,
1530                    &signature,
1531                    Some("self"),
1532                    parse_quote!(-> #returns),
1533                    [],
1534                    Some(cls),
1535                )
1536            })
1537            .collect::<Vec<_>>();
1538        let const_name = format_ident!("_{}", unique_element_id()); // We need an explicit name here
1539        def.associated_method = quote! {
1540            #associated_method
1541            const #const_name: () = {
1542                #(#introspection)*
1543            };
1544        };
1545    }
1546    Ok(def)
1547}
1548
1549fn generate_default_protocol_slot(
1550    cls: &syn::Type,
1551    method: &mut syn::ImplItemFn,
1552    slot: &SlotDef,
1553    ctx: &Ctx,
1554) -> syn::Result<MethodAndSlotDef> {
1555    let spec = FnSpec::parse(
1556        &mut method.sig,
1557        &mut Vec::new(),
1558        PyFunctionOptions::default(),
1559    )?;
1560    let name = spec.name.to_string();
1561    slot.generate_type_slot(
1562        &syn::parse_quote!(#cls),
1563        &spec,
1564        &format!("__default_{name}__"),
1565        ctx,
1566    )
1567}
1568
1569fn simple_enum_default_methods<'a>(
1570    cls: &'a syn::Ident,
1571    unit_variant_names: impl IntoIterator<
1572        Item = (
1573            &'a syn::Ident,
1574            Cow<'a, syn::Ident>,
1575            &'a Vec<&'a syn::Attribute>,
1576        ),
1577    >,
1578    ctx: &Ctx,
1579) -> Vec<MethodAndMethodDef> {
1580    let cls_type = syn::parse_quote!(#cls);
1581    let variant_to_attribute = |var_ident: &syn::Ident, py_ident: &syn::Ident| ConstSpec {
1582        rust_ident: var_ident.clone(),
1583        attributes: ConstAttributes {
1584            is_class_attr: true,
1585            name: Some(NameAttribute {
1586                kw: syn::parse_quote! { name },
1587                value: NameLitStr(py_ident.clone()),
1588            }),
1589        },
1590    };
1591    unit_variant_names
1592        .into_iter()
1593        .map(|(var, py_name, attrs)| {
1594            let method = gen_py_const(&cls_type, &variant_to_attribute(var, &py_name), ctx);
1595            let associated_method_tokens = method.associated_method;
1596            let method_def_tokens = method.method_def;
1597
1598            let associated_method = quote! {
1599                #(#attrs)*
1600                #associated_method_tokens
1601            };
1602            let method_def = quote! {
1603                #(#attrs)*
1604                #method_def_tokens
1605            };
1606
1607            MethodAndMethodDef {
1608                associated_method,
1609                method_def,
1610            }
1611        })
1612        .collect()
1613}
1614
1615fn complex_enum_default_methods<'a>(
1616    cls: &'a syn::Ident,
1617    variant_names: impl IntoIterator<Item = (&'a syn::Ident, Cow<'a, syn::Ident>)>,
1618    ctx: &Ctx,
1619) -> Vec<MethodAndMethodDef> {
1620    let cls_type = syn::parse_quote!(#cls);
1621    let variant_to_attribute = |var_ident: &syn::Ident, py_ident: &syn::Ident| ConstSpec {
1622        rust_ident: var_ident.clone(),
1623        attributes: ConstAttributes {
1624            is_class_attr: true,
1625            name: Some(NameAttribute {
1626                kw: syn::parse_quote! { name },
1627                value: NameLitStr(py_ident.clone()),
1628            }),
1629        },
1630    };
1631    variant_names
1632        .into_iter()
1633        .map(|(var, py_name)| {
1634            gen_complex_enum_variant_attr(cls, &cls_type, &variant_to_attribute(var, &py_name), ctx)
1635        })
1636        .collect()
1637}
1638
1639pub fn gen_complex_enum_variant_attr(
1640    cls: &syn::Ident,
1641    cls_type: &syn::Type,
1642    spec: &ConstSpec,
1643    ctx: &Ctx,
1644) -> MethodAndMethodDef {
1645    let Ctx { pyo3_path, .. } = ctx;
1646    let member = &spec.rust_ident;
1647    let wrapper_ident = format_ident!("__pymethod_variant_cls_{}__", member);
1648    let python_name = spec.null_terminated_python_name(ctx);
1649
1650    let variant_cls = format_ident!("{}_{}", cls, member);
1651    let associated_method = quote! {
1652        fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> {
1653            ::std::result::Result::Ok(py.get_type::<#variant_cls>().into_any().unbind())
1654        }
1655    };
1656
1657    let method_def = quote! {
1658        #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
1659            #pyo3_path::impl_::pymethods::PyMethodDefType::ClassAttribute({
1660                #pyo3_path::impl_::pymethods::PyClassAttributeDef::new(
1661                    #python_name,
1662                    #cls_type::#wrapper_ident
1663                )
1664            })
1665        )
1666    };
1667
1668    MethodAndMethodDef {
1669        associated_method,
1670        method_def,
1671    }
1672}
1673
1674fn complex_enum_variant_new<'a>(
1675    cls: &'a syn::Ident,
1676    variant: PyClassEnumVariant<'a>,
1677    ctx: &Ctx,
1678) -> Result<MethodAndSlotDef> {
1679    match variant {
1680        PyClassEnumVariant::Struct(struct_variant) => {
1681            complex_enum_struct_variant_new(cls, struct_variant, ctx)
1682        }
1683        PyClassEnumVariant::Tuple(tuple_variant) => {
1684            complex_enum_tuple_variant_new(cls, tuple_variant, ctx)
1685        }
1686    }
1687}
1688
1689fn complex_enum_struct_variant_new<'a>(
1690    cls: &'a syn::Ident,
1691    variant: PyClassEnumStructVariant<'a>,
1692    ctx: &Ctx,
1693) -> Result<MethodAndSlotDef> {
1694    let Ctx { pyo3_path, .. } = ctx;
1695    let variant_cls = format_ident!("{}_{}", cls, variant.ident);
1696    let variant_cls_type: syn::Type = parse_quote!(#variant_cls);
1697
1698    let arg_py_ident: syn::Ident = parse_quote!(py);
1699    let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>);
1700
1701    let args = {
1702        let mut args = vec![
1703            // py: Python<'_>
1704            FnArg::Py(PyArg {
1705                name: &arg_py_ident,
1706                ty: &arg_py_type,
1707            }),
1708        ];
1709
1710        for field in &variant.fields {
1711            args.push(FnArg::Regular(RegularArg {
1712                name: Cow::Borrowed(field.ident),
1713                ty: field.ty,
1714                from_py_with: None,
1715                default_value: None,
1716                option_wrapped_type: None,
1717                #[cfg(feature = "experimental-inspect")]
1718                annotation: None,
1719            }));
1720        }
1721        args
1722    };
1723
1724    let signature = if let Some(constructor) = variant.options.constructor {
1725        crate::pyfunction::FunctionSignature::from_arguments_and_attribute(
1726            args,
1727            constructor.into_signature(),
1728        )?
1729    } else {
1730        crate::pyfunction::FunctionSignature::from_arguments(args)
1731    };
1732
1733    let spec = FnSpec {
1734        tp: crate::method::FnType::FnNew,
1735        name: &format_ident!("__pymethod_constructor__"),
1736        python_name: format_ident!("__new__"),
1737        signature,
1738        convention: crate::method::CallingConvention::TpNew,
1739        text_signature: None,
1740        asyncness: None,
1741        unsafety: None,
1742        warnings: vec![],
1743        #[cfg(feature = "experimental-inspect")]
1744        output: syn::ReturnType::Default,
1745    };
1746
1747    crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx)
1748}
1749
1750fn complex_enum_tuple_variant_new<'a>(
1751    cls: &'a syn::Ident,
1752    variant: PyClassEnumTupleVariant<'a>,
1753    ctx: &Ctx,
1754) -> Result<MethodAndSlotDef> {
1755    let Ctx { pyo3_path, .. } = ctx;
1756
1757    let variant_cls: Ident = format_ident!("{}_{}", cls, variant.ident);
1758    let variant_cls_type: syn::Type = parse_quote!(#variant_cls);
1759
1760    let arg_py_ident: syn::Ident = parse_quote!(py);
1761    let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>);
1762
1763    let args = {
1764        let mut args = vec![FnArg::Py(PyArg {
1765            name: &arg_py_ident,
1766            ty: &arg_py_type,
1767        })];
1768
1769        for (i, field) in variant.fields.iter().enumerate() {
1770            args.push(FnArg::Regular(RegularArg {
1771                name: std::borrow::Cow::Owned(format_ident!("_{}", i)),
1772                ty: field.ty,
1773                from_py_with: None,
1774                default_value: None,
1775                option_wrapped_type: None,
1776                #[cfg(feature = "experimental-inspect")]
1777                annotation: None,
1778            }));
1779        }
1780        args
1781    };
1782
1783    let signature = if let Some(constructor) = variant.options.constructor {
1784        crate::pyfunction::FunctionSignature::from_arguments_and_attribute(
1785            args,
1786            constructor.into_signature(),
1787        )?
1788    } else {
1789        crate::pyfunction::FunctionSignature::from_arguments(args)
1790    };
1791
1792    let spec = FnSpec {
1793        tp: crate::method::FnType::FnNew,
1794        name: &format_ident!("__pymethod_constructor__"),
1795        python_name: format_ident!("__new__"),
1796        signature,
1797        convention: crate::method::CallingConvention::TpNew,
1798        text_signature: None,
1799        asyncness: None,
1800        unsafety: None,
1801        warnings: vec![],
1802        #[cfg(feature = "experimental-inspect")]
1803        output: syn::ReturnType::Default,
1804    };
1805
1806    crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx)
1807}
1808
1809fn complex_enum_variant_field_getter<'a>(
1810    variant_cls_type: &'a syn::Type,
1811    field_name: &'a syn::Ident,
1812    field_span: Span,
1813    ctx: &Ctx,
1814) -> Result<MethodAndMethodDef> {
1815    let mut arg = parse_quote!(py: Python<'_>);
1816    let py = FnArg::parse(&mut arg)?;
1817    let signature = crate::pyfunction::FunctionSignature::from_arguments(vec![py]);
1818
1819    let self_type = crate::method::SelfType::TryFromBoundRef(field_span);
1820
1821    let spec = FnSpec {
1822        tp: crate::method::FnType::Getter(self_type.clone()),
1823        name: field_name,
1824        python_name: field_name.unraw(),
1825        signature,
1826        convention: crate::method::CallingConvention::Noargs,
1827        text_signature: None,
1828        asyncness: None,
1829        unsafety: None,
1830        warnings: vec![],
1831        #[cfg(feature = "experimental-inspect")]
1832        output: syn::ReturnType::Type(Token![->](field_span), Box::new(variant_cls_type.clone())),
1833    };
1834
1835    let property_type = crate::pymethod::PropertyType::Function {
1836        self_type: &self_type,
1837        spec: &spec,
1838        doc: crate::get_doc(&[], None, ctx)?,
1839    };
1840
1841    let getter = crate::pymethod::impl_py_getter_def(variant_cls_type, property_type, ctx)?;
1842    Ok(getter)
1843}
1844
1845fn descriptors_to_items(
1846    cls: &syn::Ident,
1847    rename_all: Option<&RenameAllAttribute>,
1848    frozen: Option<frozen>,
1849    field_options: Vec<(&syn::Field, FieldPyO3Options)>,
1850    ctx: &Ctx,
1851) -> syn::Result<Vec<MethodAndMethodDef>> {
1852    let ty = syn::parse_quote!(#cls);
1853    let mut items = Vec::new();
1854    for (field_index, (field, options)) in field_options.into_iter().enumerate() {
1855        if let FieldPyO3Options {
1856            name: Some(name),
1857            get: None,
1858            set: None,
1859        } = options
1860        {
1861            return Err(syn::Error::new_spanned(name, USELESS_NAME));
1862        }
1863
1864        if options.get.is_some() {
1865            let getter = impl_py_getter_def(
1866                &ty,
1867                PropertyType::Descriptor {
1868                    field_index,
1869                    field,
1870                    python_name: options.name.as_ref(),
1871                    renaming_rule: rename_all.map(|rename_all| rename_all.value.rule),
1872                },
1873                ctx,
1874            )?;
1875            items.push(getter);
1876        }
1877
1878        if let Some(set) = options.set {
1879            ensure_spanned!(frozen.is_none(), set.span() => "cannot use `#[pyo3(set)]` on a `frozen` class");
1880            let setter = impl_py_setter_def(
1881                &ty,
1882                PropertyType::Descriptor {
1883                    field_index,
1884                    field,
1885                    python_name: options.name.as_ref(),
1886                    renaming_rule: rename_all.map(|rename_all| rename_all.value.rule),
1887                },
1888                ctx,
1889            )?;
1890            items.push(setter);
1891        };
1892    }
1893    Ok(items)
1894}
1895
1896fn impl_pytypeinfo(cls: &syn::Ident, attr: &PyClassArgs, ctx: &Ctx) -> TokenStream {
1897    let Ctx { pyo3_path, .. } = ctx;
1898    let cls_name = get_class_python_name(cls, attr).to_string();
1899
1900    let module = if let Some(ModuleAttribute { value, .. }) = &attr.options.module {
1901        quote! { ::core::option::Option::Some(#value) }
1902    } else {
1903        quote! { ::core::option::Option::None }
1904    };
1905
1906    quote! {
1907        unsafe impl #pyo3_path::type_object::PyTypeInfo for #cls {
1908            const NAME: &'static str = #cls_name;
1909            const MODULE: ::std::option::Option<&'static str> = #module;
1910
1911            #[inline]
1912            fn type_object_raw(py: #pyo3_path::Python<'_>) -> *mut #pyo3_path::ffi::PyTypeObject {
1913                use #pyo3_path::prelude::PyTypeMethods;
1914                <#cls as #pyo3_path::impl_::pyclass::PyClassImpl>::lazy_type_object()
1915                    .get_or_try_init(py)
1916                    .unwrap_or_else(|e| #pyo3_path::impl_::pyclass::type_object_init_failed(
1917                        py,
1918                        e,
1919                        <Self as #pyo3_path::type_object::PyTypeInfo>::NAME
1920                    ))
1921                    .as_type_ptr()
1922            }
1923        }
1924    }
1925}
1926
1927fn pyclass_richcmp_arms(
1928    options: &PyClassPyO3Options,
1929    ctx: &Ctx,
1930) -> std::result::Result<TokenStream, syn::Error> {
1931    let Ctx { pyo3_path, .. } = ctx;
1932
1933    let eq_arms = options
1934        .eq
1935        .map(|eq| eq.span)
1936        .or(options.eq_int.map(|eq_int| eq_int.span))
1937        .map(|span| {
1938            quote_spanned! { span =>
1939                #pyo3_path::pyclass::CompareOp::Eq => {
1940                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val == other, py)
1941                },
1942                #pyo3_path::pyclass::CompareOp::Ne => {
1943                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val != other, py)
1944                },
1945            }
1946        })
1947        .unwrap_or_default();
1948
1949    if let Some(ord) = options.ord {
1950        ensure_spanned!(options.eq.is_some(), ord.span() => "The `ord` option requires the `eq` option.");
1951    }
1952
1953    let ord_arms = options
1954        .ord
1955        .map(|ord| {
1956            quote_spanned! { ord.span() =>
1957                #pyo3_path::pyclass::CompareOp::Gt => {
1958                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val > other, py)
1959                },
1960                #pyo3_path::pyclass::CompareOp::Lt => {
1961                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val < other, py)
1962                 },
1963                #pyo3_path::pyclass::CompareOp::Le => {
1964                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val <= other, py)
1965                 },
1966                #pyo3_path::pyclass::CompareOp::Ge => {
1967                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val >= other, py)
1968                 },
1969            }
1970        })
1971        .unwrap_or_else(|| quote! { _ => ::std::result::Result::Ok(py.NotImplemented()) });
1972
1973    Ok(quote! {
1974        #eq_arms
1975        #ord_arms
1976    })
1977}
1978
1979fn pyclass_richcmp_simple_enum(
1980    options: &PyClassPyO3Options,
1981    cls: &syn::Type,
1982    repr_type: &syn::Ident,
1983    #[cfg(feature = "experimental-inspect")] class_name: &str,
1984    ctx: &Ctx,
1985) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
1986    let Ctx { pyo3_path, .. } = ctx;
1987    if let Some(eq_int) = options.eq_int {
1988        ensure_spanned!(options.eq.is_some(), eq_int.span() => "The `eq_int` option requires the `eq` option.");
1989    }
1990
1991    if options.eq.is_none() && options.eq_int.is_none() {
1992        return Ok((None, None));
1993    }
1994
1995    let arms = pyclass_richcmp_arms(options, ctx)?;
1996
1997    let eq = options.eq.map(|eq| {
1998        quote_spanned! { eq.span() =>
1999            let self_val = self;
2000            if let ::std::result::Result::Ok(other) = other.cast::<Self>() {
2001                let other = &*other.borrow();
2002                return match op {
2003                    #arms
2004                }
2005            }
2006        }
2007    });
2008
2009    let eq_int = options.eq_int.map(|eq_int| {
2010        quote_spanned! { eq_int.span() =>
2011            let self_val = self.__pyo3__int__();
2012            if let ::std::result::Result::Ok(other) = #pyo3_path::types::PyAnyMethods::extract::<#repr_type>(other).or_else(|_| {
2013                other.cast::<Self>().map(|o| o.borrow().__pyo3__int__())
2014            }) {
2015                return match op {
2016                    #arms
2017                }
2018            }
2019        }
2020    });
2021
2022    let mut richcmp_impl = parse_quote! {
2023        fn __pyo3__generated____richcmp__(
2024            &self,
2025            py: #pyo3_path::Python,
2026            other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>,
2027            op: #pyo3_path::pyclass::CompareOp
2028        ) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> {
2029            #eq
2030
2031            #eq_int
2032
2033            ::std::result::Result::Ok(py.NotImplemented())
2034        }
2035    };
2036    let richcmp_slot = if options.eq.is_some() {
2037        generate_protocol_slot(
2038            cls,
2039            &mut richcmp_impl,
2040            &__RICHCMP__,
2041            "__richcmp__",
2042            #[cfg(feature = "experimental-inspect")]
2043            FunctionIntrospectionData {
2044                names: &["__eq__", "__ne__"],
2045                arguments: vec![FnArg::Regular(RegularArg {
2046                    name: Cow::Owned(format_ident!("other")),
2047                    // we need to set a type, let's pick something small, it is overridden by annotation anyway
2048                    ty: &parse_quote!(!),
2049                    from_py_with: None,
2050                    default_value: None,
2051                    option_wrapped_type: None,
2052                    annotation: Some(match (options.eq.is_some(), options.eq_int.is_some()) {
2053                        (true, true) => {
2054                            format!("{class_name} | int")
2055                        }
2056                        (true, false) => class_name.into(),
2057                        (false, true) => "int".into(),
2058                        (false, false) => unreachable!(),
2059                    }),
2060                })],
2061                returns: parse_quote! { ::std::primitive::bool },
2062            },
2063            ctx,
2064        )?
2065    } else {
2066        generate_default_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, ctx)?
2067    };
2068    Ok((Some(richcmp_impl), Some(richcmp_slot)))
2069}
2070
2071fn pyclass_richcmp(
2072    options: &PyClassPyO3Options,
2073    cls: &syn::Type,
2074    ctx: &Ctx,
2075) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
2076    let Ctx { pyo3_path, .. } = ctx;
2077    if let Some(eq_int) = options.eq_int {
2078        bail_spanned!(eq_int.span() => "`eq_int` can only be used on simple enums.")
2079    }
2080
2081    let arms = pyclass_richcmp_arms(options, ctx)?;
2082    if options.eq.is_some() {
2083        let mut richcmp_impl = parse_quote! {
2084            fn __pyo3__generated____richcmp__(
2085                &self,
2086                py: #pyo3_path::Python,
2087                other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>,
2088                op: #pyo3_path::pyclass::CompareOp
2089            ) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> {
2090                let self_val = self;
2091                if let ::std::result::Result::Ok(other) = other.cast::<Self>() {
2092                    let other = &*other.borrow();
2093                    match op {
2094                        #arms
2095                    }
2096                } else {
2097                    ::std::result::Result::Ok(py.NotImplemented())
2098                }
2099            }
2100        };
2101        let richcmp_slot = generate_protocol_slot(
2102            cls,
2103            &mut richcmp_impl,
2104            &__RICHCMP__,
2105            "__richcmp__",
2106            #[cfg(feature = "experimental-inspect")]
2107            FunctionIntrospectionData {
2108                names: if options.ord.is_some() {
2109                    &["__eq__", "__ne__", "__lt__", "__le__", "__gt__", "__ge__"]
2110                } else {
2111                    &["__eq__", "__ne__"]
2112                },
2113                arguments: vec![FnArg::Regular(RegularArg {
2114                    name: Cow::Owned(format_ident!("other")),
2115                    ty: &parse_quote!(&#cls),
2116                    from_py_with: None,
2117                    default_value: None,
2118                    option_wrapped_type: None,
2119                    annotation: None,
2120                })],
2121                returns: parse_quote! { ::std::primitive::bool },
2122            },
2123            ctx,
2124        )?;
2125        Ok((Some(richcmp_impl), Some(richcmp_slot)))
2126    } else {
2127        Ok((None, None))
2128    }
2129}
2130
2131fn pyclass_hash(
2132    options: &PyClassPyO3Options,
2133    cls: &syn::Type,
2134    ctx: &Ctx,
2135) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
2136    if options.hash.is_some() {
2137        ensure_spanned!(
2138            options.frozen.is_some(), options.hash.span() => "The `hash` option requires the `frozen` option.";
2139            options.eq.is_some(), options.hash.span() => "The `hash` option requires the `eq` option.";
2140        );
2141    }
2142    match options.hash {
2143        Some(opt) => {
2144            let mut hash_impl = parse_quote_spanned! { opt.span() =>
2145                fn __pyo3__generated____hash__(&self) -> u64 {
2146                    let mut s = ::std::collections::hash_map::DefaultHasher::new();
2147                    ::std::hash::Hash::hash(self, &mut s);
2148                    ::std::hash::Hasher::finish(&s)
2149                }
2150            };
2151            let hash_slot = generate_protocol_slot(
2152                cls,
2153                &mut hash_impl,
2154                &__HASH__,
2155                "__hash__",
2156                #[cfg(feature = "experimental-inspect")]
2157                FunctionIntrospectionData {
2158                    names: &["__hash__"],
2159                    arguments: Vec::new(),
2160                    returns: parse_quote! { ::std::primitive::u64 },
2161                },
2162                ctx,
2163            )?;
2164            Ok((Some(hash_impl), Some(hash_slot)))
2165        }
2166        None => Ok((None, None)),
2167    }
2168}
2169
2170fn pyclass_class_geitem(
2171    options: &PyClassPyO3Options,
2172    cls: &syn::Type,
2173    ctx: &Ctx,
2174) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndMethodDef>)> {
2175    let Ctx { pyo3_path, .. } = ctx;
2176    match options.generic {
2177        Some(_) => {
2178            let ident = format_ident!("__class_getitem__");
2179            let mut class_geitem_impl: syn::ImplItemFn = {
2180                parse_quote! {
2181                    #[classmethod]
2182                    fn #ident<'py>(
2183                        cls: &#pyo3_path::Bound<'py, #pyo3_path::types::PyType>,
2184                        key: &#pyo3_path::Bound<'py, #pyo3_path::types::PyAny>
2185                    ) -> #pyo3_path::PyResult<#pyo3_path::Bound<'py, #pyo3_path::types::PyGenericAlias>> {
2186                        #pyo3_path::types::PyGenericAlias::new(cls.py(), cls.as_any(), key)
2187                    }
2188                }
2189            };
2190
2191            let spec = FnSpec::parse(
2192                &mut class_geitem_impl.sig,
2193                &mut class_geitem_impl.attrs,
2194                Default::default(),
2195            )?;
2196
2197            let class_geitem_method = crate::pymethod::impl_py_method_def(
2198                cls,
2199                &spec,
2200                &spec.get_doc(&class_geitem_impl.attrs, ctx)?,
2201                Some(quote!(#pyo3_path::ffi::METH_CLASS)),
2202                ctx,
2203            )?;
2204            Ok((Some(class_geitem_impl), Some(class_geitem_method)))
2205        }
2206        None => Ok((None, None)),
2207    }
2208}
2209
2210/// Implements most traits used by `#[pyclass]`.
2211///
2212/// Specifically, it implements traits that only depend on class name,
2213/// and attributes of `#[pyclass]`, and docstrings.
2214/// Therefore it doesn't implement traits that depends on struct fields and enum variants.
2215struct PyClassImplsBuilder<'a> {
2216    cls: &'a syn::Ident,
2217    attr: &'a PyClassArgs,
2218    methods_type: PyClassMethodsType,
2219    default_methods: Vec<MethodAndMethodDef>,
2220    default_slots: Vec<MethodAndSlotDef>,
2221    doc: Option<PythonDoc>,
2222}
2223
2224impl<'a> PyClassImplsBuilder<'a> {
2225    fn new(
2226        cls: &'a syn::Ident,
2227        attr: &'a PyClassArgs,
2228        methods_type: PyClassMethodsType,
2229        default_methods: Vec<MethodAndMethodDef>,
2230        default_slots: Vec<MethodAndSlotDef>,
2231    ) -> Self {
2232        Self {
2233            cls,
2234            attr,
2235            methods_type,
2236            default_methods,
2237            default_slots,
2238            doc: None,
2239        }
2240    }
2241
2242    fn doc(self, doc: PythonDoc) -> Self {
2243        Self {
2244            doc: Some(doc),
2245            ..self
2246        }
2247    }
2248
2249    fn impl_all(&self, ctx: &Ctx) -> Result<TokenStream> {
2250        Ok([
2251            self.impl_pyclass(ctx),
2252            self.impl_extractext(ctx),
2253            self.impl_into_py(ctx),
2254            self.impl_pyclassimpl(ctx)?,
2255            self.impl_add_to_module(ctx),
2256            self.impl_freelist(ctx),
2257            self.impl_introspection(ctx),
2258        ]
2259        .into_iter()
2260        .collect())
2261    }
2262
2263    fn impl_pyclass(&self, ctx: &Ctx) -> TokenStream {
2264        let Ctx { pyo3_path, .. } = ctx;
2265        let cls = self.cls;
2266
2267        let frozen = if self.attr.options.frozen.is_some() {
2268            quote! { #pyo3_path::pyclass::boolean_struct::True }
2269        } else {
2270            quote! { #pyo3_path::pyclass::boolean_struct::False }
2271        };
2272
2273        quote! {
2274            impl #pyo3_path::PyClass for #cls {
2275                type Frozen = #frozen;
2276            }
2277        }
2278    }
2279    fn impl_extractext(&self, ctx: &Ctx) -> TokenStream {
2280        let Ctx { pyo3_path, .. } = ctx;
2281        let cls = self.cls;
2282
2283        let input_type = if cfg!(feature = "experimental-inspect") {
2284            let full_name = get_class_python_module_and_name(cls, self.attr);
2285            quote! { const INPUT_TYPE: &'static str = #full_name; }
2286        } else {
2287            quote! {}
2288        };
2289        if self.attr.options.frozen.is_some() {
2290            quote! {
2291                impl<'a, 'holder, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'holder, 'py, false> for &'holder #cls
2292                {
2293                    type Holder = ::std::option::Option<#pyo3_path::PyClassGuard<'a, #cls>>;
2294
2295                    #input_type
2296
2297                    #[inline]
2298                    fn extract(obj: &'a #pyo3_path::Bound<'py, #pyo3_path::PyAny>, holder: &'holder mut Self::Holder) -> #pyo3_path::PyResult<Self> {
2299                        #pyo3_path::impl_::extract_argument::extract_pyclass_ref(obj, holder)
2300                    }
2301                }
2302            }
2303        } else {
2304            quote! {
2305                impl<'a, 'holder, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'holder, 'py, false> for &'holder #cls
2306                {
2307                    type Holder = ::std::option::Option<#pyo3_path::PyClassGuard<'a, #cls>>;
2308
2309                    #input_type
2310
2311                    #[inline]
2312                    fn extract(obj: &'a #pyo3_path::Bound<'py, #pyo3_path::PyAny>, holder: &'holder mut Self::Holder) -> #pyo3_path::PyResult<Self> {
2313                        #pyo3_path::impl_::extract_argument::extract_pyclass_ref(obj, holder)
2314                    }
2315                }
2316
2317                impl<'a, 'holder, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'holder, 'py, false> for &'holder mut #cls
2318                {
2319                    type Holder = ::std::option::Option<#pyo3_path::PyClassGuardMut<'a, #cls>>;
2320
2321                    #input_type
2322
2323                    #[inline]
2324                    fn extract(obj: &'a #pyo3_path::Bound<'py, #pyo3_path::PyAny>, holder: &'holder mut Self::Holder) -> #pyo3_path::PyResult<Self> {
2325                        #pyo3_path::impl_::extract_argument::extract_pyclass_ref_mut(obj, holder)
2326                    }
2327                }
2328            }
2329        }
2330    }
2331
2332    fn impl_into_py(&self, ctx: &Ctx) -> TokenStream {
2333        let Ctx { pyo3_path, .. } = ctx;
2334        let cls = self.cls;
2335        let attr = self.attr;
2336        // If #cls is not extended type, we allow Self->PyObject conversion
2337        if attr.options.extends.is_none() {
2338            let output_type = if cfg!(feature = "experimental-inspect") {
2339                let full_name = get_class_python_module_and_name(cls, self.attr);
2340                quote! { const OUTPUT_TYPE: &'static str = #full_name; }
2341            } else {
2342                quote! {}
2343            };
2344            quote! {
2345                impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls {
2346                    type Target = Self;
2347                    type Output = #pyo3_path::Bound<'py, <Self as #pyo3_path::conversion::IntoPyObject<'py>>::Target>;
2348                    type Error = #pyo3_path::PyErr;
2349                    #output_type
2350
2351                    fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result<
2352                        <Self as #pyo3_path::conversion::IntoPyObject>::Output,
2353                        <Self as #pyo3_path::conversion::IntoPyObject>::Error,
2354                    > {
2355                        #pyo3_path::Bound::new(py, self)
2356                    }
2357                }
2358            }
2359        } else {
2360            quote! {}
2361        }
2362    }
2363    fn impl_pyclassimpl(&self, ctx: &Ctx) -> Result<TokenStream> {
2364        let Ctx { pyo3_path, .. } = ctx;
2365        let cls = self.cls;
2366        let doc = self.doc.as_ref().map_or(
2367            LitCStr::empty(ctx).to_token_stream(),
2368            PythonDoc::to_token_stream,
2369        );
2370        let is_basetype = self.attr.options.subclass.is_some();
2371        let base = match &self.attr.options.extends {
2372            Some(extends_attr) => extends_attr.value.clone(),
2373            None => parse_quote! { #pyo3_path::PyAny },
2374        };
2375        let is_subclass = self.attr.options.extends.is_some();
2376        let is_mapping: bool = self.attr.options.mapping.is_some();
2377        let is_sequence: bool = self.attr.options.sequence.is_some();
2378        let is_immutable_type = self.attr.options.immutable_type.is_some();
2379
2380        ensure_spanned!(
2381            !(is_mapping && is_sequence),
2382            self.cls.span() => "a `#[pyclass]` cannot be both a `mapping` and a `sequence`"
2383        );
2384
2385        let dict_offset = if self.attr.options.dict.is_some() {
2386            quote! {
2387                fn dict_offset() -> ::std::option::Option<#pyo3_path::ffi::Py_ssize_t> {
2388                    ::std::option::Option::Some(#pyo3_path::impl_::pyclass::dict_offset::<Self>())
2389                }
2390            }
2391        } else {
2392            TokenStream::new()
2393        };
2394
2395        // insert space for weak ref
2396        let weaklist_offset = if self.attr.options.weakref.is_some() {
2397            quote! {
2398                fn weaklist_offset() -> ::std::option::Option<#pyo3_path::ffi::Py_ssize_t> {
2399                    ::std::option::Option::Some(#pyo3_path::impl_::pyclass::weaklist_offset::<Self>())
2400                }
2401            }
2402        } else {
2403            TokenStream::new()
2404        };
2405
2406        let thread_checker = if self.attr.options.unsendable.is_some() {
2407            quote! { #pyo3_path::impl_::pyclass::ThreadCheckerImpl }
2408        } else {
2409            quote! { #pyo3_path::impl_::pyclass::SendablePyClass<#cls> }
2410        };
2411
2412        let (pymethods_items, inventory, inventory_class) = match self.methods_type {
2413            PyClassMethodsType::Specialization => (quote! { collector.py_methods() }, None, None),
2414            PyClassMethodsType::Inventory => {
2415                // To allow multiple #[pymethods] block, we define inventory types.
2416                let inventory_class_name = syn::Ident::new(
2417                    &format!("Pyo3MethodsInventoryFor{}", cls.unraw()),
2418                    Span::call_site(),
2419                );
2420                (
2421                    quote! {
2422                        ::std::boxed::Box::new(
2423                            ::std::iter::Iterator::map(
2424                                #pyo3_path::inventory::iter::<<Self as #pyo3_path::impl_::pyclass::PyClassImpl>::Inventory>(),
2425                                #pyo3_path::impl_::pyclass::PyClassInventory::items
2426                            )
2427                        )
2428                    },
2429                    Some(quote! { type Inventory = #inventory_class_name; }),
2430                    Some(define_inventory_class(&inventory_class_name, ctx)),
2431                )
2432            }
2433        };
2434
2435        let default_methods = self
2436            .default_methods
2437            .iter()
2438            .map(|meth| &meth.associated_method)
2439            .chain(
2440                self.default_slots
2441                    .iter()
2442                    .map(|meth| &meth.associated_method),
2443            );
2444
2445        let default_method_defs = self.default_methods.iter().map(|meth| &meth.method_def);
2446        let default_slot_defs = self.default_slots.iter().map(|slot| &slot.slot_def);
2447        let freelist_slots = self.freelist_slots(ctx);
2448
2449        let class_mutability = if self.attr.options.frozen.is_some() {
2450            quote! {
2451                ImmutableChild
2452            }
2453        } else {
2454            quote! {
2455                MutableChild
2456            }
2457        };
2458
2459        let cls = self.cls;
2460        let attr = self.attr;
2461        let dict = if attr.options.dict.is_some() {
2462            quote! { #pyo3_path::impl_::pyclass::PyClassDictSlot }
2463        } else {
2464            quote! { #pyo3_path::impl_::pyclass::PyClassDummySlot }
2465        };
2466
2467        // insert space for weak ref
2468        let weakref = if attr.options.weakref.is_some() {
2469            quote! { #pyo3_path::impl_::pyclass::PyClassWeakRefSlot }
2470        } else {
2471            quote! { #pyo3_path::impl_::pyclass::PyClassDummySlot }
2472        };
2473
2474        let base_nativetype = if attr.options.extends.is_some() {
2475            quote! { <Self::BaseType as #pyo3_path::impl_::pyclass::PyClassBaseType>::BaseNativeType }
2476        } else {
2477            quote! { #pyo3_path::PyAny }
2478        };
2479
2480        let pyclass_base_type_impl = attr.options.subclass.map(|subclass| {
2481            quote_spanned! { subclass.span() =>
2482                impl #pyo3_path::impl_::pyclass::PyClassBaseType for #cls {
2483                    type LayoutAsBase = #pyo3_path::impl_::pycell::PyClassObject<Self>;
2484                    type BaseNativeType = <Self as #pyo3_path::impl_::pyclass::PyClassImpl>::BaseNativeType;
2485                    type Initializer = #pyo3_path::pyclass_init::PyClassInitializer<Self>;
2486                    type PyClassMutability = <Self as #pyo3_path::impl_::pyclass::PyClassImpl>::PyClassMutability;
2487                }
2488            }
2489        });
2490
2491        let assertions = if attr.options.unsendable.is_some() {
2492            TokenStream::new()
2493        } else {
2494            let assert = quote_spanned! { cls.span() => #pyo3_path::impl_::pyclass::assert_pyclass_sync::<#cls>(); };
2495            quote! {
2496                const _: () = {
2497                    #assert
2498                };
2499            }
2500        };
2501
2502        let type_name = if cfg!(feature = "experimental-inspect") {
2503            let full_name = get_class_python_module_and_name(cls, self.attr);
2504            quote! { const TYPE_NAME: &'static str = #full_name; }
2505        } else {
2506            quote! {}
2507        };
2508
2509        Ok(quote! {
2510            #assertions
2511
2512            #pyclass_base_type_impl
2513
2514            impl #pyo3_path::impl_::pyclass::PyClassImpl for #cls {
2515                const IS_BASETYPE: bool = #is_basetype;
2516                const IS_SUBCLASS: bool = #is_subclass;
2517                const IS_MAPPING: bool = #is_mapping;
2518                const IS_SEQUENCE: bool = #is_sequence;
2519                const IS_IMMUTABLE_TYPE: bool = #is_immutable_type;
2520
2521                type BaseType = #base;
2522                type ThreadChecker = #thread_checker;
2523                #inventory
2524                type PyClassMutability = <<#base as #pyo3_path::impl_::pyclass::PyClassBaseType>::PyClassMutability as #pyo3_path::impl_::pycell::PyClassMutability>::#class_mutability;
2525                type Dict = #dict;
2526                type WeakRef = #weakref;
2527                type BaseNativeType = #base_nativetype;
2528
2529                #type_name
2530
2531                fn items_iter() -> #pyo3_path::impl_::pyclass::PyClassItemsIter {
2532                    use #pyo3_path::impl_::pyclass::*;
2533                    let collector = PyClassImplCollector::<Self>::new();
2534                    static INTRINSIC_ITEMS: PyClassItems = PyClassItems {
2535                        methods: &[#(#default_method_defs),*],
2536                        slots: &[#(#default_slot_defs),* #(#freelist_slots),*],
2537                    };
2538                    PyClassItemsIter::new(&INTRINSIC_ITEMS, #pymethods_items)
2539                }
2540
2541                const RAW_DOC: &'static ::std::ffi::CStr = #doc;
2542
2543                const DOC: &'static ::std::ffi::CStr = {
2544                    use #pyo3_path::impl_ as impl_;
2545                    use impl_::pyclass::Probe as _;
2546                    const DOC_PIECES: &'static [&'static [u8]] = impl_::pyclass::doc::PyClassDocGenerator::<
2547                        #cls,
2548                        { impl_::pyclass::HasNewTextSignature::<#cls>::VALUE }
2549                    >::DOC_PIECES;
2550                    const LEN: usize = impl_::concat::combined_len(DOC_PIECES);
2551                    const DOC: &'static [u8] = &impl_::concat::combine_to_array::<LEN>(DOC_PIECES);
2552                    impl_::pyclass::doc::doc_bytes_as_cstr(DOC)
2553                };
2554
2555                #dict_offset
2556
2557                #weaklist_offset
2558
2559                fn lazy_type_object() -> &'static #pyo3_path::impl_::pyclass::LazyTypeObject<Self> {
2560                    use #pyo3_path::impl_::pyclass::LazyTypeObject;
2561                    static TYPE_OBJECT: LazyTypeObject<#cls> = LazyTypeObject::new();
2562                    &TYPE_OBJECT
2563                }
2564            }
2565
2566            #[doc(hidden)]
2567            #[allow(non_snake_case)]
2568            impl #cls {
2569                #(#default_methods)*
2570            }
2571
2572            #inventory_class
2573        })
2574    }
2575
2576    fn impl_add_to_module(&self, ctx: &Ctx) -> TokenStream {
2577        let Ctx { pyo3_path, .. } = ctx;
2578        let cls = self.cls;
2579        quote! {
2580            impl #cls {
2581                #[doc(hidden)]
2582                pub const _PYO3_DEF: #pyo3_path::impl_::pymodule::AddClassToModule<Self> = #pyo3_path::impl_::pymodule::AddClassToModule::new();
2583            }
2584        }
2585    }
2586
2587    fn impl_freelist(&self, ctx: &Ctx) -> TokenStream {
2588        let cls = self.cls;
2589        let Ctx { pyo3_path, .. } = ctx;
2590
2591        self.attr.options.freelist.as_ref().map_or(quote! {}, |freelist| {
2592            let freelist = &freelist.value;
2593            quote! {
2594                impl #pyo3_path::impl_::pyclass::PyClassWithFreeList for #cls {
2595                    #[inline]
2596                    fn get_free_list(py: #pyo3_path::Python<'_>) -> &'static ::std::sync::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList> {
2597                        static FREELIST: #pyo3_path::sync::PyOnceLock<::std::sync::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList>> = #pyo3_path::sync::PyOnceLock::new();
2598                        &FREELIST.get_or_init(py, || ::std::sync::Mutex::new(#pyo3_path::impl_::freelist::PyObjectFreeList::with_capacity(#freelist)))
2599                    }
2600                }
2601            }
2602        })
2603    }
2604
2605    fn freelist_slots(&self, ctx: &Ctx) -> Vec<TokenStream> {
2606        let Ctx { pyo3_path, .. } = ctx;
2607        let cls = self.cls;
2608
2609        if self.attr.options.freelist.is_some() {
2610            vec![
2611                quote! {
2612                    #pyo3_path::ffi::PyType_Slot {
2613                        slot: #pyo3_path::ffi::Py_tp_alloc,
2614                        pfunc: #pyo3_path::impl_::pyclass::alloc_with_freelist::<#cls> as *mut _,
2615                    }
2616                },
2617                quote! {
2618                    #pyo3_path::ffi::PyType_Slot {
2619                        slot: #pyo3_path::ffi::Py_tp_free,
2620                        pfunc: #pyo3_path::impl_::pyclass::free_with_freelist::<#cls> as *mut _,
2621                    }
2622                },
2623            ]
2624        } else {
2625            Vec::new()
2626        }
2627    }
2628
2629    #[cfg(feature = "experimental-inspect")]
2630    fn impl_introspection(&self, ctx: &Ctx) -> TokenStream {
2631        let Ctx { pyo3_path, .. } = ctx;
2632        let name = get_class_python_name(self.cls, self.attr).to_string();
2633        let ident = self.cls;
2634        let static_introspection = class_introspection_code(pyo3_path, ident, &name);
2635        let introspection_id = introspection_id_const();
2636        quote! {
2637            #static_introspection
2638            impl #ident {
2639                #introspection_id
2640            }
2641        }
2642    }
2643
2644    #[cfg(not(feature = "experimental-inspect"))]
2645    fn impl_introspection(&self, _ctx: &Ctx) -> TokenStream {
2646        quote! {}
2647    }
2648}
2649
2650fn define_inventory_class(inventory_class_name: &syn::Ident, ctx: &Ctx) -> TokenStream {
2651    let Ctx { pyo3_path, .. } = ctx;
2652    quote! {
2653        #[doc(hidden)]
2654        pub struct #inventory_class_name {
2655            items: #pyo3_path::impl_::pyclass::PyClassItems,
2656        }
2657        impl #inventory_class_name {
2658            pub const fn new(items: #pyo3_path::impl_::pyclass::PyClassItems) -> Self {
2659                Self { items }
2660            }
2661        }
2662
2663        impl #pyo3_path::impl_::pyclass::PyClassInventory for #inventory_class_name {
2664            fn items(&self) -> &#pyo3_path::impl_::pyclass::PyClassItems {
2665                &self.items
2666            }
2667        }
2668
2669        #pyo3_path::inventory::collect!(#inventory_class_name);
2670    }
2671}
2672
2673fn generate_cfg_check(variants: &[PyClassEnumUnitVariant<'_>], cls: &syn::Ident) -> TokenStream {
2674    if variants.is_empty() {
2675        return quote! {};
2676    }
2677
2678    let mut conditions = Vec::new();
2679
2680    for variant in variants {
2681        let cfg_attrs = &variant.cfg_attrs;
2682
2683        if cfg_attrs.is_empty() {
2684            // There's at least one variant of the enum without cfg attributes,
2685            // so the check is not necessary
2686            return quote! {};
2687        }
2688
2689        for attr in cfg_attrs {
2690            if let syn::Meta::List(meta) = &attr.meta {
2691                let cfg_tokens = &meta.tokens;
2692                conditions.push(quote! { not(#cfg_tokens) });
2693            }
2694        }
2695    }
2696
2697    quote_spanned! {
2698        cls.span() =>
2699        #[cfg(all(#(#conditions),*))]
2700        ::core::compile_error!(concat!("#[pyclass] can't be used on enums without any variants - all variants of enum `", stringify!(#cls), "` have been configured out by cfg attributes"));
2701    }
2702}
2703
2704const UNIQUE_GET: &str = "`get` may only be specified once";
2705const UNIQUE_SET: &str = "`set` may only be specified once";
2706const UNIQUE_NAME: &str = "`name` may only be specified once";
2707
2708const DUPE_SET: &str = "useless `set` - the struct is already annotated with `set_all`";
2709const DUPE_GET: &str = "useless `get` - the struct is already annotated with `get_all`";
2710const UNIT_GET: &str =
2711    "`get_all` on an unit struct does nothing, because unit structs have no fields";
2712const UNIT_SET: &str =
2713    "`set_all` on an unit struct does nothing, because unit structs have no fields";
2714
2715const USELESS_NAME: &str = "`name` is useless without `get` or `set`";