pyo3_macros_backend/
pyclass.rs

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