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    };
1673
1674    crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx)
1675}
1676
1677fn complex_enum_tuple_variant_new<'a>(
1678    cls: &'a syn::Ident,
1679    variant: PyClassEnumTupleVariant<'a>,
1680    ctx: &Ctx,
1681) -> Result<MethodAndSlotDef> {
1682    let Ctx { pyo3_path, .. } = ctx;
1683
1684    let variant_cls: Ident = format_ident!("{}_{}", cls, variant.ident);
1685    let variant_cls_type: syn::Type = parse_quote!(#variant_cls);
1686
1687    let arg_py_ident: syn::Ident = parse_quote!(py);
1688    let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>);
1689
1690    let args = {
1691        let mut args = vec![FnArg::Py(PyArg {
1692            name: &arg_py_ident,
1693            ty: &arg_py_type,
1694        })];
1695
1696        for (i, field) in variant.fields.iter().enumerate() {
1697            args.push(FnArg::Regular(RegularArg {
1698                name: std::borrow::Cow::Owned(format_ident!("_{}", i)),
1699                ty: field.ty,
1700                from_py_with: None,
1701                default_value: None,
1702                option_wrapped_type: None,
1703            }));
1704        }
1705        args
1706    };
1707
1708    let signature = if let Some(constructor) = variant.options.constructor {
1709        crate::pyfunction::FunctionSignature::from_arguments_and_attribute(
1710            args,
1711            constructor.into_signature(),
1712        )?
1713    } else {
1714        crate::pyfunction::FunctionSignature::from_arguments(args)
1715    };
1716
1717    let spec = FnSpec {
1718        tp: crate::method::FnType::FnNew,
1719        name: &format_ident!("__pymethod_constructor__"),
1720        python_name: format_ident!("__new__"),
1721        signature,
1722        convention: crate::method::CallingConvention::TpNew,
1723        text_signature: None,
1724        asyncness: None,
1725        unsafety: None,
1726    };
1727
1728    crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx)
1729}
1730
1731fn complex_enum_variant_field_getter<'a>(
1732    variant_cls_type: &'a syn::Type,
1733    field_name: &'a syn::Ident,
1734    field_span: Span,
1735    ctx: &Ctx,
1736) -> Result<MethodAndMethodDef> {
1737    let signature = crate::pyfunction::FunctionSignature::from_arguments(vec![]);
1738
1739    let self_type = crate::method::SelfType::TryFromBoundRef(field_span);
1740
1741    let spec = FnSpec {
1742        tp: crate::method::FnType::Getter(self_type.clone()),
1743        name: field_name,
1744        python_name: field_name.unraw(),
1745        signature,
1746        convention: crate::method::CallingConvention::Noargs,
1747        text_signature: None,
1748        asyncness: None,
1749        unsafety: None,
1750    };
1751
1752    let property_type = crate::pymethod::PropertyType::Function {
1753        self_type: &self_type,
1754        spec: &spec,
1755        doc: crate::get_doc(&[], None, ctx),
1756    };
1757
1758    let getter = crate::pymethod::impl_py_getter_def(variant_cls_type, property_type, ctx)?;
1759    Ok(getter)
1760}
1761
1762fn descriptors_to_items(
1763    cls: &syn::Ident,
1764    rename_all: Option<&RenameAllAttribute>,
1765    frozen: Option<frozen>,
1766    field_options: Vec<(&syn::Field, FieldPyO3Options)>,
1767    ctx: &Ctx,
1768) -> syn::Result<Vec<MethodAndMethodDef>> {
1769    let ty = syn::parse_quote!(#cls);
1770    let mut items = Vec::new();
1771    for (field_index, (field, options)) in field_options.into_iter().enumerate() {
1772        if let FieldPyO3Options {
1773            name: Some(name),
1774            get: None,
1775            set: None,
1776        } = options
1777        {
1778            return Err(syn::Error::new_spanned(name, USELESS_NAME));
1779        }
1780
1781        if options.get.is_some() {
1782            let getter = impl_py_getter_def(
1783                &ty,
1784                PropertyType::Descriptor {
1785                    field_index,
1786                    field,
1787                    python_name: options.name.as_ref(),
1788                    renaming_rule: rename_all.map(|rename_all| rename_all.value.rule),
1789                },
1790                ctx,
1791            )?;
1792            items.push(getter);
1793        }
1794
1795        if let Some(set) = options.set {
1796            ensure_spanned!(frozen.is_none(), set.span() => "cannot use `#[pyo3(set)]` on a `frozen` class");
1797            let setter = impl_py_setter_def(
1798                &ty,
1799                PropertyType::Descriptor {
1800                    field_index,
1801                    field,
1802                    python_name: options.name.as_ref(),
1803                    renaming_rule: rename_all.map(|rename_all| rename_all.value.rule),
1804                },
1805                ctx,
1806            )?;
1807            items.push(setter);
1808        };
1809    }
1810    Ok(items)
1811}
1812
1813fn impl_pytypeinfo(cls: &syn::Ident, attr: &PyClassArgs, ctx: &Ctx) -> TokenStream {
1814    let Ctx { pyo3_path, .. } = ctx;
1815    let cls_name = get_class_python_name(cls, attr).to_string();
1816
1817    let module = if let Some(ModuleAttribute { value, .. }) = &attr.options.module {
1818        quote! { ::core::option::Option::Some(#value) }
1819    } else {
1820        quote! { ::core::option::Option::None }
1821    };
1822
1823    quote! {
1824        unsafe impl #pyo3_path::type_object::PyTypeInfo for #cls {
1825            const NAME: &'static str = #cls_name;
1826            const MODULE: ::std::option::Option<&'static str> = #module;
1827
1828            #[inline]
1829            fn type_object_raw(py: #pyo3_path::Python<'_>) -> *mut #pyo3_path::ffi::PyTypeObject {
1830                use #pyo3_path::prelude::PyTypeMethods;
1831                <#cls as #pyo3_path::impl_::pyclass::PyClassImpl>::lazy_type_object()
1832                    .get_or_init(py)
1833                    .as_type_ptr()
1834            }
1835        }
1836    }
1837}
1838
1839fn pyclass_richcmp_arms(
1840    options: &PyClassPyO3Options,
1841    ctx: &Ctx,
1842) -> std::result::Result<TokenStream, syn::Error> {
1843    let Ctx { pyo3_path, .. } = ctx;
1844
1845    let eq_arms = options
1846        .eq
1847        .map(|eq| eq.span)
1848        .or(options.eq_int.map(|eq_int| eq_int.span))
1849        .map(|span| {
1850            quote_spanned! { span =>
1851                #pyo3_path::pyclass::CompareOp::Eq => {
1852                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val == other, py)
1853                },
1854                #pyo3_path::pyclass::CompareOp::Ne => {
1855                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val != other, py)
1856                },
1857            }
1858        })
1859        .unwrap_or_default();
1860
1861    if let Some(ord) = options.ord {
1862        ensure_spanned!(options.eq.is_some(), ord.span() => "The `ord` option requires the `eq` option.");
1863    }
1864
1865    let ord_arms = options
1866        .ord
1867        .map(|ord| {
1868            quote_spanned! { ord.span() =>
1869                #pyo3_path::pyclass::CompareOp::Gt => {
1870                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val > other, py)
1871                },
1872                #pyo3_path::pyclass::CompareOp::Lt => {
1873                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val < other, py)
1874                 },
1875                #pyo3_path::pyclass::CompareOp::Le => {
1876                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val <= other, py)
1877                 },
1878                #pyo3_path::pyclass::CompareOp::Ge => {
1879                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val >= other, py)
1880                 },
1881            }
1882        })
1883        .unwrap_or_else(|| quote! { _ => ::std::result::Result::Ok(py.NotImplemented()) });
1884
1885    Ok(quote! {
1886        #eq_arms
1887        #ord_arms
1888    })
1889}
1890
1891fn pyclass_richcmp_simple_enum(
1892    options: &PyClassPyO3Options,
1893    cls: &syn::Type,
1894    repr_type: &syn::Ident,
1895    ctx: &Ctx,
1896) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
1897    let Ctx { pyo3_path, .. } = ctx;
1898
1899    if let Some(eq_int) = options.eq_int {
1900        ensure_spanned!(options.eq.is_some(), eq_int.span() => "The `eq_int` option requires the `eq` option.");
1901    }
1902
1903    if options.eq.is_none() && options.eq_int.is_none() {
1904        return Ok((None, None));
1905    }
1906
1907    let arms = pyclass_richcmp_arms(options, ctx)?;
1908
1909    let eq = options.eq.map(|eq| {
1910        quote_spanned! { eq.span() =>
1911            let self_val = self;
1912            if let ::std::result::Result::Ok(other) = #pyo3_path::types::PyAnyMethods::downcast::<Self>(other) {
1913                let other = &*other.borrow();
1914                return match op {
1915                    #arms
1916                }
1917            }
1918        }
1919    });
1920
1921    let eq_int = options.eq_int.map(|eq_int| {
1922        quote_spanned! { eq_int.span() =>
1923            let self_val = self.__pyo3__int__();
1924            if let ::std::result::Result::Ok(other) = #pyo3_path::types::PyAnyMethods::extract::<#repr_type>(other).or_else(|_| {
1925                #pyo3_path::types::PyAnyMethods::downcast::<Self>(other).map(|o| o.borrow().__pyo3__int__())
1926            }) {
1927                return match op {
1928                    #arms
1929                }
1930            }
1931        }
1932    });
1933
1934    let mut richcmp_impl = parse_quote! {
1935        fn __pyo3__generated____richcmp__(
1936            &self,
1937            py: #pyo3_path::Python,
1938            other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>,
1939            op: #pyo3_path::pyclass::CompareOp
1940        ) -> #pyo3_path::PyResult<#pyo3_path::PyObject> {
1941            #eq
1942
1943            #eq_int
1944
1945            ::std::result::Result::Ok(py.NotImplemented())
1946        }
1947    };
1948    let richcmp_slot = if options.eq.is_some() {
1949        generate_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, "__richcmp__", ctx).unwrap()
1950    } else {
1951        generate_default_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, ctx).unwrap()
1952    };
1953    Ok((Some(richcmp_impl), Some(richcmp_slot)))
1954}
1955
1956fn pyclass_richcmp(
1957    options: &PyClassPyO3Options,
1958    cls: &syn::Type,
1959    ctx: &Ctx,
1960) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
1961    let Ctx { pyo3_path, .. } = ctx;
1962    if let Some(eq_int) = options.eq_int {
1963        bail_spanned!(eq_int.span() => "`eq_int` can only be used on simple enums.")
1964    }
1965
1966    let arms = pyclass_richcmp_arms(options, ctx)?;
1967    if options.eq.is_some() {
1968        let mut richcmp_impl = parse_quote! {
1969            fn __pyo3__generated____richcmp__(
1970                &self,
1971                py: #pyo3_path::Python,
1972                other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>,
1973                op: #pyo3_path::pyclass::CompareOp
1974            ) -> #pyo3_path::PyResult<#pyo3_path::PyObject> {
1975                let self_val = self;
1976                if let ::std::result::Result::Ok(other) = #pyo3_path::types::PyAnyMethods::downcast::<Self>(other) {
1977                    let other = &*other.borrow();
1978                    match op {
1979                        #arms
1980                    }
1981                } else {
1982                    ::std::result::Result::Ok(py.NotImplemented())
1983                }
1984            }
1985        };
1986        let richcmp_slot =
1987            generate_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, "__richcmp__", ctx)
1988                .unwrap();
1989        Ok((Some(richcmp_impl), Some(richcmp_slot)))
1990    } else {
1991        Ok((None, None))
1992    }
1993}
1994
1995fn pyclass_hash(
1996    options: &PyClassPyO3Options,
1997    cls: &syn::Type,
1998    ctx: &Ctx,
1999) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
2000    if options.hash.is_some() {
2001        ensure_spanned!(
2002            options.frozen.is_some(), options.hash.span() => "The `hash` option requires the `frozen` option.";
2003            options.eq.is_some(), options.hash.span() => "The `hash` option requires the `eq` option.";
2004        );
2005    }
2006    // FIXME: Use hash.map(...).unzip() on MSRV >= 1.66
2007    match options.hash {
2008        Some(opt) => {
2009            let mut hash_impl = parse_quote_spanned! { opt.span() =>
2010                fn __pyo3__generated____hash__(&self) -> u64 {
2011                    let mut s = ::std::collections::hash_map::DefaultHasher::new();
2012                    ::std::hash::Hash::hash(self, &mut s);
2013                    ::std::hash::Hasher::finish(&s)
2014                }
2015            };
2016            let hash_slot =
2017                generate_protocol_slot(cls, &mut hash_impl, &__HASH__, "__hash__", ctx).unwrap();
2018            Ok((Some(hash_impl), Some(hash_slot)))
2019        }
2020        None => Ok((None, None)),
2021    }
2022}
2023
2024fn pyclass_class_geitem(
2025    options: &PyClassPyO3Options,
2026    cls: &syn::Type,
2027    ctx: &Ctx,
2028) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndMethodDef>)> {
2029    let Ctx { pyo3_path, .. } = ctx;
2030    match options.generic {
2031        Some(_) => {
2032            let ident = format_ident!("__class_getitem__");
2033            let mut class_geitem_impl: syn::ImplItemFn = {
2034                parse_quote! {
2035                    #[classmethod]
2036                    fn #ident<'py>(
2037                        cls: &#pyo3_path::Bound<'py, #pyo3_path::types::PyType>,
2038                        key: &#pyo3_path::Bound<'py, #pyo3_path::types::PyAny>
2039                    ) -> #pyo3_path::PyResult<#pyo3_path::Bound<'py, #pyo3_path::types::PyGenericAlias>> {
2040                        #pyo3_path::types::PyGenericAlias::new(cls.py(), cls.as_any(), key)
2041                    }
2042                }
2043            };
2044
2045            let spec = FnSpec::parse(
2046                &mut class_geitem_impl.sig,
2047                &mut class_geitem_impl.attrs,
2048                Default::default(),
2049            )?;
2050
2051            let class_geitem_method = crate::pymethod::impl_py_method_def(
2052                cls,
2053                &spec,
2054                &spec.get_doc(&class_geitem_impl.attrs, ctx),
2055                Some(quote!(#pyo3_path::ffi::METH_CLASS)),
2056                ctx,
2057            )?;
2058            Ok((Some(class_geitem_impl), Some(class_geitem_method)))
2059        }
2060        None => Ok((None, None)),
2061    }
2062}
2063
2064/// Implements most traits used by `#[pyclass]`.
2065///
2066/// Specifically, it implements traits that only depend on class name,
2067/// and attributes of `#[pyclass]`, and docstrings.
2068/// Therefore it doesn't implement traits that depends on struct fields and enum variants.
2069struct PyClassImplsBuilder<'a> {
2070    cls: &'a syn::Ident,
2071    attr: &'a PyClassArgs,
2072    methods_type: PyClassMethodsType,
2073    default_methods: Vec<MethodAndMethodDef>,
2074    default_slots: Vec<MethodAndSlotDef>,
2075    doc: Option<PythonDoc>,
2076}
2077
2078impl<'a> PyClassImplsBuilder<'a> {
2079    fn new(
2080        cls: &'a syn::Ident,
2081        attr: &'a PyClassArgs,
2082        methods_type: PyClassMethodsType,
2083        default_methods: Vec<MethodAndMethodDef>,
2084        default_slots: Vec<MethodAndSlotDef>,
2085    ) -> Self {
2086        Self {
2087            cls,
2088            attr,
2089            methods_type,
2090            default_methods,
2091            default_slots,
2092            doc: None,
2093        }
2094    }
2095
2096    fn doc(self, doc: PythonDoc) -> Self {
2097        Self {
2098            doc: Some(doc),
2099            ..self
2100        }
2101    }
2102
2103    fn impl_all(&self, ctx: &Ctx) -> Result<TokenStream> {
2104        Ok([
2105            self.impl_pyclass(ctx),
2106            self.impl_extractext(ctx),
2107            self.impl_into_py(ctx),
2108            self.impl_pyclassimpl(ctx)?,
2109            self.impl_add_to_module(ctx),
2110            self.impl_freelist(ctx),
2111            self.impl_introspection(ctx),
2112        ]
2113        .into_iter()
2114        .collect())
2115    }
2116
2117    fn impl_pyclass(&self, ctx: &Ctx) -> TokenStream {
2118        let Ctx { pyo3_path, .. } = ctx;
2119        let cls = self.cls;
2120
2121        let frozen = if self.attr.options.frozen.is_some() {
2122            quote! { #pyo3_path::pyclass::boolean_struct::True }
2123        } else {
2124            quote! { #pyo3_path::pyclass::boolean_struct::False }
2125        };
2126
2127        quote! {
2128            impl #pyo3_path::PyClass for #cls {
2129                type Frozen = #frozen;
2130            }
2131        }
2132    }
2133    fn impl_extractext(&self, ctx: &Ctx) -> TokenStream {
2134        let Ctx { pyo3_path, .. } = ctx;
2135        let cls = self.cls;
2136        if self.attr.options.frozen.is_some() {
2137            quote! {
2138                impl<'a, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'py, false> for &'a #cls
2139                {
2140                    type Holder = ::std::option::Option<#pyo3_path::PyRef<'py, #cls>>;
2141
2142                    #[inline]
2143                    fn extract(obj: &'a #pyo3_path::Bound<'py, #pyo3_path::PyAny>, holder: &'a mut Self::Holder) -> #pyo3_path::PyResult<Self> {
2144                        #pyo3_path::impl_::extract_argument::extract_pyclass_ref(obj, holder)
2145                    }
2146                }
2147            }
2148        } else {
2149            quote! {
2150                impl<'a, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'py, false> for &'a #cls
2151                {
2152                    type Holder = ::std::option::Option<#pyo3_path::PyRef<'py, #cls>>;
2153
2154                    #[inline]
2155                    fn extract(obj: &'a #pyo3_path::Bound<'py, #pyo3_path::PyAny>, holder: &'a mut Self::Holder) -> #pyo3_path::PyResult<Self> {
2156                        #pyo3_path::impl_::extract_argument::extract_pyclass_ref(obj, holder)
2157                    }
2158                }
2159
2160                impl<'a, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'py, false> for &'a mut #cls
2161                {
2162                    type Holder = ::std::option::Option<#pyo3_path::PyRefMut<'py, #cls>>;
2163
2164                    #[inline]
2165                    fn extract(obj: &'a #pyo3_path::Bound<'py, #pyo3_path::PyAny>, holder: &'a mut Self::Holder) -> #pyo3_path::PyResult<Self> {
2166                        #pyo3_path::impl_::extract_argument::extract_pyclass_ref_mut(obj, holder)
2167                    }
2168                }
2169            }
2170        }
2171    }
2172
2173    fn impl_into_py(&self, ctx: &Ctx) -> TokenStream {
2174        let Ctx { pyo3_path, .. } = ctx;
2175        let cls = self.cls;
2176        let attr = self.attr;
2177        // If #cls is not extended type, we allow Self->PyObject conversion
2178        if attr.options.extends.is_none() {
2179            quote! {
2180                impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls {
2181                    type Target = Self;
2182                    type Output = #pyo3_path::Bound<'py, <Self as #pyo3_path::conversion::IntoPyObject<'py>>::Target>;
2183                    type Error = #pyo3_path::PyErr;
2184
2185                    fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result<
2186                        <Self as #pyo3_path::conversion::IntoPyObject>::Output,
2187                        <Self as #pyo3_path::conversion::IntoPyObject>::Error,
2188                    > {
2189                        #pyo3_path::Bound::new(py, self)
2190                    }
2191                }
2192            }
2193        } else {
2194            quote! {}
2195        }
2196    }
2197    fn impl_pyclassimpl(&self, ctx: &Ctx) -> Result<TokenStream> {
2198        let Ctx { pyo3_path, .. } = ctx;
2199        let cls = self.cls;
2200        let doc = self.doc.as_ref().map_or(
2201            LitCStr::empty(ctx).to_token_stream(),
2202            PythonDoc::to_token_stream,
2203        );
2204        let is_basetype = self.attr.options.subclass.is_some();
2205        let base = match &self.attr.options.extends {
2206            Some(extends_attr) => extends_attr.value.clone(),
2207            None => parse_quote! { #pyo3_path::PyAny },
2208        };
2209        let is_subclass = self.attr.options.extends.is_some();
2210        let is_mapping: bool = self.attr.options.mapping.is_some();
2211        let is_sequence: bool = self.attr.options.sequence.is_some();
2212        let is_immutable_type = self.attr.options.immutable_type.is_some();
2213
2214        ensure_spanned!(
2215            !(is_mapping && is_sequence),
2216            self.cls.span() => "a `#[pyclass]` cannot be both a `mapping` and a `sequence`"
2217        );
2218
2219        let dict_offset = if self.attr.options.dict.is_some() {
2220            quote! {
2221                fn dict_offset() -> ::std::option::Option<#pyo3_path::ffi::Py_ssize_t> {
2222                    ::std::option::Option::Some(#pyo3_path::impl_::pyclass::dict_offset::<Self>())
2223                }
2224            }
2225        } else {
2226            TokenStream::new()
2227        };
2228
2229        // insert space for weak ref
2230        let weaklist_offset = if self.attr.options.weakref.is_some() {
2231            quote! {
2232                fn weaklist_offset() -> ::std::option::Option<#pyo3_path::ffi::Py_ssize_t> {
2233                    ::std::option::Option::Some(#pyo3_path::impl_::pyclass::weaklist_offset::<Self>())
2234                }
2235            }
2236        } else {
2237            TokenStream::new()
2238        };
2239
2240        let thread_checker = if self.attr.options.unsendable.is_some() {
2241            quote! { #pyo3_path::impl_::pyclass::ThreadCheckerImpl }
2242        } else {
2243            quote! { #pyo3_path::impl_::pyclass::SendablePyClass<#cls> }
2244        };
2245
2246        let (pymethods_items, inventory, inventory_class) = match self.methods_type {
2247            PyClassMethodsType::Specialization => (quote! { collector.py_methods() }, None, None),
2248            PyClassMethodsType::Inventory => {
2249                // To allow multiple #[pymethods] block, we define inventory types.
2250                let inventory_class_name = syn::Ident::new(
2251                    &format!("Pyo3MethodsInventoryFor{}", cls.unraw()),
2252                    Span::call_site(),
2253                );
2254                (
2255                    quote! {
2256                        ::std::boxed::Box::new(
2257                            ::std::iter::Iterator::map(
2258                                #pyo3_path::inventory::iter::<<Self as #pyo3_path::impl_::pyclass::PyClassImpl>::Inventory>(),
2259                                #pyo3_path::impl_::pyclass::PyClassInventory::items
2260                            )
2261                        )
2262                    },
2263                    Some(quote! { type Inventory = #inventory_class_name; }),
2264                    Some(define_inventory_class(&inventory_class_name, ctx)),
2265                )
2266            }
2267        };
2268
2269        let default_methods = self
2270            .default_methods
2271            .iter()
2272            .map(|meth| &meth.associated_method)
2273            .chain(
2274                self.default_slots
2275                    .iter()
2276                    .map(|meth| &meth.associated_method),
2277            );
2278
2279        let default_method_defs = self.default_methods.iter().map(|meth| &meth.method_def);
2280        let default_slot_defs = self.default_slots.iter().map(|slot| &slot.slot_def);
2281        let freelist_slots = self.freelist_slots(ctx);
2282
2283        let class_mutability = if self.attr.options.frozen.is_some() {
2284            quote! {
2285                ImmutableChild
2286            }
2287        } else {
2288            quote! {
2289                MutableChild
2290            }
2291        };
2292
2293        let cls = self.cls;
2294        let attr = self.attr;
2295        let dict = if attr.options.dict.is_some() {
2296            quote! { #pyo3_path::impl_::pyclass::PyClassDictSlot }
2297        } else {
2298            quote! { #pyo3_path::impl_::pyclass::PyClassDummySlot }
2299        };
2300
2301        // insert space for weak ref
2302        let weakref = if attr.options.weakref.is_some() {
2303            quote! { #pyo3_path::impl_::pyclass::PyClassWeakRefSlot }
2304        } else {
2305            quote! { #pyo3_path::impl_::pyclass::PyClassDummySlot }
2306        };
2307
2308        let base_nativetype = if attr.options.extends.is_some() {
2309            quote! { <Self::BaseType as #pyo3_path::impl_::pyclass::PyClassBaseType>::BaseNativeType }
2310        } else {
2311            quote! { #pyo3_path::PyAny }
2312        };
2313
2314        let pyclass_base_type_impl = attr.options.subclass.map(|subclass| {
2315            quote_spanned! { subclass.span() =>
2316                impl #pyo3_path::impl_::pyclass::PyClassBaseType for #cls {
2317                    type LayoutAsBase = #pyo3_path::impl_::pycell::PyClassObject<Self>;
2318                    type BaseNativeType = <Self as #pyo3_path::impl_::pyclass::PyClassImpl>::BaseNativeType;
2319                    type Initializer = #pyo3_path::pyclass_init::PyClassInitializer<Self>;
2320                    type PyClassMutability = <Self as #pyo3_path::impl_::pyclass::PyClassImpl>::PyClassMutability;
2321                }
2322            }
2323        });
2324
2325        let assertions = if attr.options.unsendable.is_some() {
2326            TokenStream::new()
2327        } else {
2328            let assert = quote_spanned! { cls.span() => #pyo3_path::impl_::pyclass::assert_pyclass_sync::<#cls>(); };
2329            quote! {
2330                const _: () = {
2331                    #assert
2332                };
2333            }
2334        };
2335
2336        Ok(quote! {
2337            #assertions
2338
2339            #pyclass_base_type_impl
2340
2341            impl #pyo3_path::impl_::pyclass::PyClassImpl for #cls {
2342                const IS_BASETYPE: bool = #is_basetype;
2343                const IS_SUBCLASS: bool = #is_subclass;
2344                const IS_MAPPING: bool = #is_mapping;
2345                const IS_SEQUENCE: bool = #is_sequence;
2346                const IS_IMMUTABLE_TYPE: bool = #is_immutable_type;
2347
2348                type BaseType = #base;
2349                type ThreadChecker = #thread_checker;
2350                #inventory
2351                type PyClassMutability = <<#base as #pyo3_path::impl_::pyclass::PyClassBaseType>::PyClassMutability as #pyo3_path::impl_::pycell::PyClassMutability>::#class_mutability;
2352                type Dict = #dict;
2353                type WeakRef = #weakref;
2354                type BaseNativeType = #base_nativetype;
2355
2356                fn items_iter() -> #pyo3_path::impl_::pyclass::PyClassItemsIter {
2357                    use #pyo3_path::impl_::pyclass::*;
2358                    let collector = PyClassImplCollector::<Self>::new();
2359                    static INTRINSIC_ITEMS: PyClassItems = PyClassItems {
2360                        methods: &[#(#default_method_defs),*],
2361                        slots: &[#(#default_slot_defs),* #(#freelist_slots),*],
2362                    };
2363                    PyClassItemsIter::new(&INTRINSIC_ITEMS, #pymethods_items)
2364                }
2365
2366                fn doc(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<&'static ::std::ffi::CStr>  {
2367                    use #pyo3_path::impl_::pyclass::*;
2368                    static DOC: #pyo3_path::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = #pyo3_path::sync::GILOnceCell::new();
2369                    DOC.get_or_try_init(py, || {
2370                        let collector = PyClassImplCollector::<Self>::new();
2371                        build_pyclass_doc(<Self as #pyo3_path::PyTypeInfo>::NAME, #doc, collector.new_text_signature())
2372                    }).map(::std::ops::Deref::deref)
2373                }
2374
2375                #dict_offset
2376
2377                #weaklist_offset
2378
2379                fn lazy_type_object() -> &'static #pyo3_path::impl_::pyclass::LazyTypeObject<Self> {
2380                    use #pyo3_path::impl_::pyclass::LazyTypeObject;
2381                    static TYPE_OBJECT: LazyTypeObject<#cls> = LazyTypeObject::new();
2382                    &TYPE_OBJECT
2383                }
2384            }
2385
2386            #[doc(hidden)]
2387            #[allow(non_snake_case)]
2388            impl #cls {
2389                #(#default_methods)*
2390            }
2391
2392            #inventory_class
2393        })
2394    }
2395
2396    fn impl_add_to_module(&self, ctx: &Ctx) -> TokenStream {
2397        let Ctx { pyo3_path, .. } = ctx;
2398        let cls = self.cls;
2399        quote! {
2400            impl #cls {
2401                #[doc(hidden)]
2402                pub const _PYO3_DEF: #pyo3_path::impl_::pymodule::AddClassToModule<Self> = #pyo3_path::impl_::pymodule::AddClassToModule::new();
2403            }
2404        }
2405    }
2406
2407    fn impl_freelist(&self, ctx: &Ctx) -> TokenStream {
2408        let cls = self.cls;
2409        let Ctx { pyo3_path, .. } = ctx;
2410
2411        self.attr.options.freelist.as_ref().map_or(quote!{}, |freelist| {
2412            let freelist = &freelist.value;
2413            quote! {
2414                impl #pyo3_path::impl_::pyclass::PyClassWithFreeList for #cls {
2415                    #[inline]
2416                    fn get_free_list(py: #pyo3_path::Python<'_>) -> &'static ::std::sync::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList> {
2417                        static FREELIST: #pyo3_path::sync::GILOnceCell<::std::sync::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList>> = #pyo3_path::sync::GILOnceCell::new();
2418                        // If there's a race to fill the cell, the object created
2419                        // by the losing thread will be deallocated via RAII
2420                        &FREELIST.get_or_init(py, || {
2421                            ::std::sync::Mutex::new(#pyo3_path::impl_::freelist::PyObjectFreeList::with_capacity(#freelist))
2422                        })
2423                    }
2424                }
2425            }
2426        })
2427    }
2428
2429    fn freelist_slots(&self, ctx: &Ctx) -> Vec<TokenStream> {
2430        let Ctx { pyo3_path, .. } = ctx;
2431        let cls = self.cls;
2432
2433        if self.attr.options.freelist.is_some() {
2434            vec![
2435                quote! {
2436                    #pyo3_path::ffi::PyType_Slot {
2437                        slot: #pyo3_path::ffi::Py_tp_alloc,
2438                        pfunc: #pyo3_path::impl_::pyclass::alloc_with_freelist::<#cls> as *mut _,
2439                    }
2440                },
2441                quote! {
2442                    #pyo3_path::ffi::PyType_Slot {
2443                        slot: #pyo3_path::ffi::Py_tp_free,
2444                        pfunc: #pyo3_path::impl_::pyclass::free_with_freelist::<#cls> as *mut _,
2445                    }
2446                },
2447            ]
2448        } else {
2449            Vec::new()
2450        }
2451    }
2452
2453    #[cfg(feature = "experimental-inspect")]
2454    fn impl_introspection(&self, ctx: &Ctx) -> TokenStream {
2455        let Ctx { pyo3_path, .. } = ctx;
2456        let name = get_class_python_name(self.cls, self.attr).to_string();
2457        let ident = self.cls;
2458        let static_introspection = class_introspection_code(pyo3_path, ident, &name);
2459        let introspection_id = introspection_id_const();
2460        quote! {
2461            #static_introspection
2462            impl #ident {
2463                #introspection_id
2464            }
2465        }
2466    }
2467
2468    #[cfg(not(feature = "experimental-inspect"))]
2469    fn impl_introspection(&self, _ctx: &Ctx) -> TokenStream {
2470        quote! {}
2471    }
2472}
2473
2474fn define_inventory_class(inventory_class_name: &syn::Ident, ctx: &Ctx) -> TokenStream {
2475    let Ctx { pyo3_path, .. } = ctx;
2476    quote! {
2477        #[doc(hidden)]
2478        pub struct #inventory_class_name {
2479            items: #pyo3_path::impl_::pyclass::PyClassItems,
2480        }
2481        impl #inventory_class_name {
2482            pub const fn new(items: #pyo3_path::impl_::pyclass::PyClassItems) -> Self {
2483                Self { items }
2484            }
2485        }
2486
2487        impl #pyo3_path::impl_::pyclass::PyClassInventory for #inventory_class_name {
2488            fn items(&self) -> &#pyo3_path::impl_::pyclass::PyClassItems {
2489                &self.items
2490            }
2491        }
2492
2493        #pyo3_path::inventory::collect!(#inventory_class_name);
2494    }
2495}
2496
2497fn generate_cfg_check(variants: &[PyClassEnumUnitVariant<'_>], cls: &syn::Ident) -> TokenStream {
2498    if variants.is_empty() {
2499        return quote! {};
2500    }
2501
2502    let mut conditions = Vec::new();
2503
2504    for variant in variants {
2505        let cfg_attrs = &variant.cfg_attrs;
2506
2507        if cfg_attrs.is_empty() {
2508            // There's at least one variant of the enum without cfg attributes,
2509            // so the check is not necessary
2510            return quote! {};
2511        }
2512
2513        for attr in cfg_attrs {
2514            if let syn::Meta::List(meta) = &attr.meta {
2515                let cfg_tokens = &meta.tokens;
2516                conditions.push(quote! { not(#cfg_tokens) });
2517            }
2518        }
2519    }
2520
2521    quote_spanned! {
2522        cls.span() =>
2523        #[cfg(all(#(#conditions),*))]
2524        ::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"));
2525    }
2526}
2527
2528const UNIQUE_GET: &str = "`get` may only be specified once";
2529const UNIQUE_SET: &str = "`set` may only be specified once";
2530const UNIQUE_NAME: &str = "`name` may only be specified once";
2531
2532const DUPE_SET: &str = "useless `set` - the struct is already annotated with `set_all`";
2533const DUPE_GET: &str = "useless `get` - the struct is already annotated with `get_all`";
2534const UNIT_GET: &str =
2535    "`get_all` on an unit struct does nothing, because unit structs have no fields";
2536const UNIT_SET: &str =
2537    "`set_all` on an unit struct does nothing, because unit structs have no fields";
2538
2539const USELESS_NAME: &str = "`name` is useless without `get` or `set`";