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