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