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: &mut Vec<Ident>,
1165) -> syn::Result<(MethodAndMethodDef, syn::ImplItemFn)> {
1166    let ident = format_ident!("__match_args__");
1167    let mut match_args_impl: syn::ImplItemFn = {
1168        parse_quote! {
1169            #[classattr]
1170            fn #ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'_, #pyo3_path::types::PyTuple>> {
1171                #pyo3_path::types::PyTuple::new::<&str, _>(py, [
1172                    #(stringify!(#field_names),)*
1173                ])
1174            }
1175        }
1176    };
1177
1178    let spec = FnSpec::parse(
1179        &mut match_args_impl.sig,
1180        &mut match_args_impl.attrs,
1181        Default::default(),
1182    )?;
1183    let variant_match_args = impl_py_class_attribute(variant_cls_type, &spec, ctx)?;
1184
1185    Ok((variant_match_args, match_args_impl))
1186}
1187
1188fn impl_complex_enum_struct_variant_cls(
1189    enum_name: &syn::Ident,
1190    variant: &PyClassEnumStructVariant<'_>,
1191    ctx: &Ctx,
1192) -> Result<(TokenStream, Vec<MethodAndMethodDef>, Vec<MethodAndSlotDef>)> {
1193    let Ctx { pyo3_path, .. } = ctx;
1194    let variant_ident = &variant.ident;
1195    let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident);
1196    let variant_cls_type = parse_quote!(#variant_cls);
1197
1198    let mut field_names: Vec<Ident> = vec![];
1199    let mut fields_with_types: Vec<TokenStream> = vec![];
1200    let mut field_getters = vec![];
1201    let mut field_getter_impls: Vec<TokenStream> = vec![];
1202    for field in &variant.fields {
1203        let field_name = field.ident;
1204        let field_type = field.ty;
1205        let field_with_type = quote! { #field_name: #field_type };
1206
1207        let field_getter =
1208            complex_enum_variant_field_getter(&variant_cls_type, field_name, field.span, ctx)?;
1209
1210        let field_getter_impl = quote! {
1211            fn #field_name(slf: #pyo3_path::PyRef<Self>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> {
1212                #[allow(unused_imports)]
1213                use #pyo3_path::impl_::pyclass::Probe;
1214                let py = slf.py();
1215                match &*slf.into_super() {
1216                    #enum_name::#variant_ident { #field_name, .. } =>
1217                        #pyo3_path::impl_::pyclass::ConvertField::<
1218                            { #pyo3_path::impl_::pyclass::IsIntoPyObjectRef::<#field_type>::VALUE },
1219                            { #pyo3_path::impl_::pyclass::IsIntoPyObject::<#field_type>::VALUE },
1220                        >::convert_field::<#field_type>(#field_name, py),
1221                    _ => ::core::unreachable!("Wrong complex enum variant found in variant wrapper PyClass"),
1222                }
1223            }
1224        };
1225
1226        field_names.push(field_name.clone());
1227        fields_with_types.push(field_with_type);
1228        field_getters.push(field_getter);
1229        field_getter_impls.push(field_getter_impl);
1230    }
1231
1232    let (variant_match_args, match_args_const_impl) =
1233        impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &mut field_names)?;
1234
1235    field_getters.push(variant_match_args);
1236
1237    let cls_impl = quote! {
1238        #[doc(hidden)]
1239        #[allow(non_snake_case)]
1240        impl #variant_cls {
1241            #[allow(clippy::too_many_arguments)]
1242            fn __pymethod_constructor__(py: #pyo3_path::Python<'_>, #(#fields_with_types,)*) -> #pyo3_path::PyClassInitializer<#variant_cls> {
1243                let base_value = #enum_name::#variant_ident { #(#field_names,)* };
1244                <#pyo3_path::PyClassInitializer<#enum_name> as ::std::convert::From<#enum_name>>::from(base_value).add_subclass(#variant_cls)
1245            }
1246
1247            #match_args_const_impl
1248
1249            #(#field_getter_impls)*
1250        }
1251    };
1252
1253    Ok((cls_impl, field_getters, Vec::new()))
1254}
1255
1256fn impl_complex_enum_tuple_variant_field_getters(
1257    ctx: &Ctx,
1258    variant: &PyClassEnumTupleVariant<'_>,
1259    enum_name: &syn::Ident,
1260    variant_cls_type: &syn::Type,
1261    variant_ident: &&Ident,
1262    field_names: &mut Vec<Ident>,
1263    fields_types: &mut Vec<syn::Type>,
1264) -> Result<(Vec<MethodAndMethodDef>, Vec<syn::ImplItemFn>)> {
1265    let Ctx { pyo3_path, .. } = ctx;
1266
1267    let mut field_getters = vec![];
1268    let mut field_getter_impls = vec![];
1269
1270    for (index, field) in variant.fields.iter().enumerate() {
1271        let field_name = format_ident!("_{}", index);
1272        let field_type = field.ty;
1273
1274        let field_getter =
1275            complex_enum_variant_field_getter(variant_cls_type, &field_name, field.span, ctx)?;
1276
1277        // Generate the match arms needed to destructure the tuple and access the specific field
1278        let field_access_tokens: Vec<_> = (0..variant.fields.len())
1279            .map(|i| {
1280                if i == index {
1281                    quote! { val }
1282                } else {
1283                    quote! { _ }
1284                }
1285            })
1286            .collect();
1287        let field_getter_impl: syn::ImplItemFn = parse_quote! {
1288            fn #field_name(slf: #pyo3_path::PyRef<Self>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> {
1289                #[allow(unused_imports)]
1290                use #pyo3_path::impl_::pyclass::Probe;
1291                let py = slf.py();
1292                match &*slf.into_super() {
1293                    #enum_name::#variant_ident ( #(#field_access_tokens), *) =>
1294                        #pyo3_path::impl_::pyclass::ConvertField::<
1295                            { #pyo3_path::impl_::pyclass::IsIntoPyObjectRef::<#field_type>::VALUE },
1296                            { #pyo3_path::impl_::pyclass::IsIntoPyObject::<#field_type>::VALUE },
1297                        >::convert_field::<#field_type>(val, py),
1298                    _ => ::core::unreachable!("Wrong complex enum variant found in variant wrapper PyClass"),
1299                }
1300            }
1301        };
1302
1303        field_names.push(field_name);
1304        fields_types.push(field_type.clone());
1305        field_getters.push(field_getter);
1306        field_getter_impls.push(field_getter_impl);
1307    }
1308
1309    Ok((field_getters, field_getter_impls))
1310}
1311
1312fn impl_complex_enum_tuple_variant_len(
1313    ctx: &Ctx,
1314
1315    variant_cls_type: &syn::Type,
1316    num_fields: usize,
1317) -> Result<(MethodAndSlotDef, syn::ImplItemFn)> {
1318    let Ctx { pyo3_path, .. } = ctx;
1319
1320    let mut len_method_impl: syn::ImplItemFn = parse_quote! {
1321        fn __len__(slf: #pyo3_path::PyRef<Self>) -> #pyo3_path::PyResult<usize> {
1322            ::std::result::Result::Ok(#num_fields)
1323        }
1324    };
1325
1326    let variant_len =
1327        generate_default_protocol_slot(variant_cls_type, &mut len_method_impl, &__LEN__, ctx)?;
1328
1329    Ok((variant_len, len_method_impl))
1330}
1331
1332fn impl_complex_enum_tuple_variant_getitem(
1333    ctx: &Ctx,
1334    variant_cls: &syn::Ident,
1335    variant_cls_type: &syn::Type,
1336    num_fields: usize,
1337) -> Result<(MethodAndSlotDef, syn::ImplItemFn)> {
1338    let Ctx { pyo3_path, .. } = ctx;
1339
1340    let match_arms: Vec<_> = (0..num_fields)
1341        .map(|i| {
1342            let field_access = format_ident!("_{}", i);
1343            quote! { #i =>
1344                #pyo3_path::IntoPyObjectExt::into_py_any(#variant_cls::#field_access(slf)?, py)
1345            }
1346        })
1347        .collect();
1348
1349    let mut get_item_method_impl: syn::ImplItemFn = parse_quote! {
1350        fn __getitem__(slf: #pyo3_path::PyRef<Self>, idx: usize) -> #pyo3_path::PyResult< #pyo3_path::PyObject> {
1351            let py = slf.py();
1352            match idx {
1353                #( #match_arms, )*
1354                _ => ::std::result::Result::Err(#pyo3_path::exceptions::PyIndexError::new_err("tuple index out of range")),
1355            }
1356        }
1357    };
1358
1359    let variant_getitem = generate_default_protocol_slot(
1360        variant_cls_type,
1361        &mut get_item_method_impl,
1362        &__GETITEM__,
1363        ctx,
1364    )?;
1365
1366    Ok((variant_getitem, get_item_method_impl))
1367}
1368
1369fn impl_complex_enum_tuple_variant_cls(
1370    enum_name: &syn::Ident,
1371    variant: &PyClassEnumTupleVariant<'_>,
1372    ctx: &Ctx,
1373) -> Result<(TokenStream, Vec<MethodAndMethodDef>, Vec<MethodAndSlotDef>)> {
1374    let Ctx { pyo3_path, .. } = ctx;
1375    let variant_ident = &variant.ident;
1376    let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident);
1377    let variant_cls_type = parse_quote!(#variant_cls);
1378
1379    let mut slots = vec![];
1380
1381    // represents the index of the field
1382    let mut field_names: Vec<Ident> = vec![];
1383    let mut field_types: Vec<syn::Type> = vec![];
1384
1385    let (mut field_getters, field_getter_impls) = impl_complex_enum_tuple_variant_field_getters(
1386        ctx,
1387        variant,
1388        enum_name,
1389        &variant_cls_type,
1390        variant_ident,
1391        &mut field_names,
1392        &mut field_types,
1393    )?;
1394
1395    let num_fields = variant.fields.len();
1396
1397    let (variant_len, len_method_impl) =
1398        impl_complex_enum_tuple_variant_len(ctx, &variant_cls_type, num_fields)?;
1399
1400    slots.push(variant_len);
1401
1402    let (variant_getitem, getitem_method_impl) =
1403        impl_complex_enum_tuple_variant_getitem(ctx, &variant_cls, &variant_cls_type, num_fields)?;
1404
1405    slots.push(variant_getitem);
1406
1407    let (variant_match_args, match_args_method_impl) =
1408        impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &mut field_names)?;
1409
1410    field_getters.push(variant_match_args);
1411
1412    let cls_impl = quote! {
1413        #[doc(hidden)]
1414        #[allow(non_snake_case)]
1415        impl #variant_cls {
1416            #[allow(clippy::too_many_arguments)]
1417            fn __pymethod_constructor__(py: #pyo3_path::Python<'_>, #(#field_names : #field_types,)*) -> #pyo3_path::PyClassInitializer<#variant_cls> {
1418                let base_value = #enum_name::#variant_ident ( #(#field_names,)* );
1419                <#pyo3_path::PyClassInitializer<#enum_name> as ::std::convert::From<#enum_name>>::from(base_value).add_subclass(#variant_cls)
1420            }
1421
1422            #len_method_impl
1423
1424            #getitem_method_impl
1425
1426            #match_args_method_impl
1427
1428            #(#field_getter_impls)*
1429        }
1430    };
1431
1432    Ok((cls_impl, field_getters, slots))
1433}
1434
1435fn gen_complex_enum_variant_class_ident(enum_: &syn::Ident, variant: &syn::Ident) -> syn::Ident {
1436    format_ident!("{}_{}", enum_, variant)
1437}
1438
1439fn generate_protocol_slot(
1440    cls: &syn::Type,
1441    method: &mut syn::ImplItemFn,
1442    slot: &SlotDef,
1443    name: &str,
1444    ctx: &Ctx,
1445) -> syn::Result<MethodAndSlotDef> {
1446    let spec = FnSpec::parse(
1447        &mut method.sig,
1448        &mut Vec::new(),
1449        PyFunctionOptions::default(),
1450    )
1451    .unwrap();
1452    slot.generate_type_slot(&syn::parse_quote!(#cls), &spec, name, ctx)
1453}
1454
1455fn generate_default_protocol_slot(
1456    cls: &syn::Type,
1457    method: &mut syn::ImplItemFn,
1458    slot: &SlotDef,
1459    ctx: &Ctx,
1460) -> syn::Result<MethodAndSlotDef> {
1461    let spec = FnSpec::parse(
1462        &mut method.sig,
1463        &mut Vec::new(),
1464        PyFunctionOptions::default(),
1465    )
1466    .unwrap();
1467    let name = spec.name.to_string();
1468    slot.generate_type_slot(
1469        &syn::parse_quote!(#cls),
1470        &spec,
1471        &format!("__default_{}__", name),
1472        ctx,
1473    )
1474}
1475
1476fn simple_enum_default_methods<'a>(
1477    cls: &'a syn::Ident,
1478    unit_variant_names: impl IntoIterator<
1479        Item = (
1480            &'a syn::Ident,
1481            Cow<'a, syn::Ident>,
1482            &'a Vec<&'a syn::Attribute>,
1483        ),
1484    >,
1485    ctx: &Ctx,
1486) -> Vec<MethodAndMethodDef> {
1487    let cls_type = syn::parse_quote!(#cls);
1488    let variant_to_attribute = |var_ident: &syn::Ident, py_ident: &syn::Ident| ConstSpec {
1489        rust_ident: var_ident.clone(),
1490        attributes: ConstAttributes {
1491            is_class_attr: true,
1492            name: Some(NameAttribute {
1493                kw: syn::parse_quote! { name },
1494                value: NameLitStr(py_ident.clone()),
1495            }),
1496        },
1497    };
1498    unit_variant_names
1499        .into_iter()
1500        .map(|(var, py_name, attrs)| {
1501            let method = gen_py_const(&cls_type, &variant_to_attribute(var, &py_name), ctx);
1502            let associated_method_tokens = method.associated_method;
1503            let method_def_tokens = method.method_def;
1504
1505            let associated_method = quote! {
1506                #(#attrs)*
1507                #associated_method_tokens
1508            };
1509            let method_def = quote! {
1510                #(#attrs)*
1511                #method_def_tokens
1512            };
1513
1514            MethodAndMethodDef {
1515                associated_method,
1516                method_def,
1517            }
1518        })
1519        .collect()
1520}
1521
1522fn complex_enum_default_methods<'a>(
1523    cls: &'a syn::Ident,
1524    variant_names: impl IntoIterator<Item = (&'a syn::Ident, Cow<'a, syn::Ident>)>,
1525    ctx: &Ctx,
1526) -> Vec<MethodAndMethodDef> {
1527    let cls_type = syn::parse_quote!(#cls);
1528    let variant_to_attribute = |var_ident: &syn::Ident, py_ident: &syn::Ident| ConstSpec {
1529        rust_ident: var_ident.clone(),
1530        attributes: ConstAttributes {
1531            is_class_attr: true,
1532            name: Some(NameAttribute {
1533                kw: syn::parse_quote! { name },
1534                value: NameLitStr(py_ident.clone()),
1535            }),
1536        },
1537    };
1538    variant_names
1539        .into_iter()
1540        .map(|(var, py_name)| {
1541            gen_complex_enum_variant_attr(cls, &cls_type, &variant_to_attribute(var, &py_name), ctx)
1542        })
1543        .collect()
1544}
1545
1546pub fn gen_complex_enum_variant_attr(
1547    cls: &syn::Ident,
1548    cls_type: &syn::Type,
1549    spec: &ConstSpec,
1550    ctx: &Ctx,
1551) -> MethodAndMethodDef {
1552    let Ctx { pyo3_path, .. } = ctx;
1553    let member = &spec.rust_ident;
1554    let wrapper_ident = format_ident!("__pymethod_variant_cls_{}__", member);
1555    let python_name = spec.null_terminated_python_name(ctx);
1556
1557    let variant_cls = format_ident!("{}_{}", cls, member);
1558    let associated_method = quote! {
1559        fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> {
1560            ::std::result::Result::Ok(py.get_type::<#variant_cls>().into_any().unbind())
1561        }
1562    };
1563
1564    let method_def = quote! {
1565        #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
1566            #pyo3_path::impl_::pymethods::PyMethodDefType::ClassAttribute({
1567                #pyo3_path::impl_::pymethods::PyClassAttributeDef::new(
1568                    #python_name,
1569                    #cls_type::#wrapper_ident
1570                )
1571            })
1572        )
1573    };
1574
1575    MethodAndMethodDef {
1576        associated_method,
1577        method_def,
1578    }
1579}
1580
1581fn complex_enum_variant_new<'a>(
1582    cls: &'a syn::Ident,
1583    variant: PyClassEnumVariant<'a>,
1584    ctx: &Ctx,
1585) -> Result<MethodAndSlotDef> {
1586    match variant {
1587        PyClassEnumVariant::Struct(struct_variant) => {
1588            complex_enum_struct_variant_new(cls, struct_variant, ctx)
1589        }
1590        PyClassEnumVariant::Tuple(tuple_variant) => {
1591            complex_enum_tuple_variant_new(cls, tuple_variant, ctx)
1592        }
1593    }
1594}
1595
1596fn complex_enum_struct_variant_new<'a>(
1597    cls: &'a syn::Ident,
1598    variant: PyClassEnumStructVariant<'a>,
1599    ctx: &Ctx,
1600) -> Result<MethodAndSlotDef> {
1601    let Ctx { pyo3_path, .. } = ctx;
1602    let variant_cls = format_ident!("{}_{}", cls, variant.ident);
1603    let variant_cls_type: syn::Type = parse_quote!(#variant_cls);
1604
1605    let arg_py_ident: syn::Ident = parse_quote!(py);
1606    let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>);
1607
1608    let args = {
1609        let mut args = vec![
1610            // py: Python<'_>
1611            FnArg::Py(PyArg {
1612                name: &arg_py_ident,
1613                ty: &arg_py_type,
1614            }),
1615        ];
1616
1617        for field in &variant.fields {
1618            args.push(FnArg::Regular(RegularArg {
1619                name: Cow::Borrowed(field.ident),
1620                ty: field.ty,
1621                from_py_with: None,
1622                default_value: None,
1623                option_wrapped_type: None,
1624            }));
1625        }
1626        args
1627    };
1628
1629    let signature = if let Some(constructor) = variant.options.constructor {
1630        crate::pyfunction::FunctionSignature::from_arguments_and_attribute(
1631            args,
1632            constructor.into_signature(),
1633        )?
1634    } else {
1635        crate::pyfunction::FunctionSignature::from_arguments(args)
1636    };
1637
1638    let spec = FnSpec {
1639        tp: crate::method::FnType::FnNew,
1640        name: &format_ident!("__pymethod_constructor__"),
1641        python_name: format_ident!("__new__"),
1642        signature,
1643        convention: crate::method::CallingConvention::TpNew,
1644        text_signature: None,
1645        asyncness: None,
1646        unsafety: None,
1647    };
1648
1649    crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx)
1650}
1651
1652fn complex_enum_tuple_variant_new<'a>(
1653    cls: &'a syn::Ident,
1654    variant: PyClassEnumTupleVariant<'a>,
1655    ctx: &Ctx,
1656) -> Result<MethodAndSlotDef> {
1657    let Ctx { pyo3_path, .. } = ctx;
1658
1659    let variant_cls: Ident = format_ident!("{}_{}", cls, variant.ident);
1660    let variant_cls_type: syn::Type = parse_quote!(#variant_cls);
1661
1662    let arg_py_ident: syn::Ident = parse_quote!(py);
1663    let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>);
1664
1665    let args = {
1666        let mut args = vec![FnArg::Py(PyArg {
1667            name: &arg_py_ident,
1668            ty: &arg_py_type,
1669        })];
1670
1671        for (i, field) in variant.fields.iter().enumerate() {
1672            args.push(FnArg::Regular(RegularArg {
1673                name: std::borrow::Cow::Owned(format_ident!("_{}", i)),
1674                ty: field.ty,
1675                from_py_with: None,
1676                default_value: None,
1677                option_wrapped_type: None,
1678            }));
1679        }
1680        args
1681    };
1682
1683    let signature = if let Some(constructor) = variant.options.constructor {
1684        crate::pyfunction::FunctionSignature::from_arguments_and_attribute(
1685            args,
1686            constructor.into_signature(),
1687        )?
1688    } else {
1689        crate::pyfunction::FunctionSignature::from_arguments(args)
1690    };
1691
1692    let spec = FnSpec {
1693        tp: crate::method::FnType::FnNew,
1694        name: &format_ident!("__pymethod_constructor__"),
1695        python_name: format_ident!("__new__"),
1696        signature,
1697        convention: crate::method::CallingConvention::TpNew,
1698        text_signature: None,
1699        asyncness: None,
1700        unsafety: None,
1701    };
1702
1703    crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx)
1704}
1705
1706fn complex_enum_variant_field_getter<'a>(
1707    variant_cls_type: &'a syn::Type,
1708    field_name: &'a syn::Ident,
1709    field_span: Span,
1710    ctx: &Ctx,
1711) -> Result<MethodAndMethodDef> {
1712    let signature = crate::pyfunction::FunctionSignature::from_arguments(vec![]);
1713
1714    let self_type = crate::method::SelfType::TryFromBoundRef(field_span);
1715
1716    let spec = FnSpec {
1717        tp: crate::method::FnType::Getter(self_type.clone()),
1718        name: field_name,
1719        python_name: field_name.clone(),
1720        signature,
1721        convention: crate::method::CallingConvention::Noargs,
1722        text_signature: None,
1723        asyncness: None,
1724        unsafety: None,
1725    };
1726
1727    let property_type = crate::pymethod::PropertyType::Function {
1728        self_type: &self_type,
1729        spec: &spec,
1730        doc: crate::get_doc(&[], None, ctx),
1731    };
1732
1733    let getter = crate::pymethod::impl_py_getter_def(variant_cls_type, property_type, ctx)?;
1734    Ok(getter)
1735}
1736
1737fn descriptors_to_items(
1738    cls: &syn::Ident,
1739    rename_all: Option<&RenameAllAttribute>,
1740    frozen: Option<frozen>,
1741    field_options: Vec<(&syn::Field, FieldPyO3Options)>,
1742    ctx: &Ctx,
1743) -> syn::Result<Vec<MethodAndMethodDef>> {
1744    let ty = syn::parse_quote!(#cls);
1745    let mut items = Vec::new();
1746    for (field_index, (field, options)) in field_options.into_iter().enumerate() {
1747        if let FieldPyO3Options {
1748            name: Some(name),
1749            get: None,
1750            set: None,
1751        } = options
1752        {
1753            return Err(syn::Error::new_spanned(name, USELESS_NAME));
1754        }
1755
1756        if options.get.is_some() {
1757            let getter = impl_py_getter_def(
1758                &ty,
1759                PropertyType::Descriptor {
1760                    field_index,
1761                    field,
1762                    python_name: options.name.as_ref(),
1763                    renaming_rule: rename_all.map(|rename_all| rename_all.value.rule),
1764                },
1765                ctx,
1766            )?;
1767            items.push(getter);
1768        }
1769
1770        if let Some(set) = options.set {
1771            ensure_spanned!(frozen.is_none(), set.span() => "cannot use `#[pyo3(set)]` on a `frozen` class");
1772            let setter = impl_py_setter_def(
1773                &ty,
1774                PropertyType::Descriptor {
1775                    field_index,
1776                    field,
1777                    python_name: options.name.as_ref(),
1778                    renaming_rule: rename_all.map(|rename_all| rename_all.value.rule),
1779                },
1780                ctx,
1781            )?;
1782            items.push(setter);
1783        };
1784    }
1785    Ok(items)
1786}
1787
1788fn impl_pytypeinfo(cls: &syn::Ident, attr: &PyClassArgs, ctx: &Ctx) -> TokenStream {
1789    let Ctx { pyo3_path, .. } = ctx;
1790    let cls_name = get_class_python_name(cls, attr).to_string();
1791
1792    let module = if let Some(ModuleAttribute { value, .. }) = &attr.options.module {
1793        quote! { ::core::option::Option::Some(#value) }
1794    } else {
1795        quote! { ::core::option::Option::None }
1796    };
1797
1798    quote! {
1799        unsafe impl #pyo3_path::type_object::PyTypeInfo for #cls {
1800            const NAME: &'static str = #cls_name;
1801            const MODULE: ::std::option::Option<&'static str> = #module;
1802
1803            #[inline]
1804            fn type_object_raw(py: #pyo3_path::Python<'_>) -> *mut #pyo3_path::ffi::PyTypeObject {
1805                use #pyo3_path::prelude::PyTypeMethods;
1806                <#cls as #pyo3_path::impl_::pyclass::PyClassImpl>::lazy_type_object()
1807                    .get_or_init(py)
1808                    .as_type_ptr()
1809            }
1810        }
1811    }
1812}
1813
1814fn pyclass_richcmp_arms(
1815    options: &PyClassPyO3Options,
1816    ctx: &Ctx,
1817) -> std::result::Result<TokenStream, syn::Error> {
1818    let Ctx { pyo3_path, .. } = ctx;
1819
1820    let eq_arms = options
1821        .eq
1822        .map(|eq| eq.span)
1823        .or(options.eq_int.map(|eq_int| eq_int.span))
1824        .map(|span| {
1825            quote_spanned! { span =>
1826                #pyo3_path::pyclass::CompareOp::Eq => {
1827                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val == other, py)
1828                },
1829                #pyo3_path::pyclass::CompareOp::Ne => {
1830                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val != other, py)
1831                },
1832            }
1833        })
1834        .unwrap_or_default();
1835
1836    if let Some(ord) = options.ord {
1837        ensure_spanned!(options.eq.is_some(), ord.span() => "The `ord` option requires the `eq` option.");
1838    }
1839
1840    let ord_arms = options
1841        .ord
1842        .map(|ord| {
1843            quote_spanned! { ord.span() =>
1844                #pyo3_path::pyclass::CompareOp::Gt => {
1845                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val > other, py)
1846                },
1847                #pyo3_path::pyclass::CompareOp::Lt => {
1848                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val < other, py)
1849                 },
1850                #pyo3_path::pyclass::CompareOp::Le => {
1851                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val <= other, py)
1852                 },
1853                #pyo3_path::pyclass::CompareOp::Ge => {
1854                    #pyo3_path::IntoPyObjectExt::into_py_any(self_val >= other, py)
1855                 },
1856            }
1857        })
1858        .unwrap_or_else(|| quote! { _ => ::std::result::Result::Ok(py.NotImplemented()) });
1859
1860    Ok(quote! {
1861        #eq_arms
1862        #ord_arms
1863    })
1864}
1865
1866fn pyclass_richcmp_simple_enum(
1867    options: &PyClassPyO3Options,
1868    cls: &syn::Type,
1869    repr_type: &syn::Ident,
1870    ctx: &Ctx,
1871) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
1872    let Ctx { pyo3_path, .. } = ctx;
1873
1874    if let Some(eq_int) = options.eq_int {
1875        ensure_spanned!(options.eq.is_some(), eq_int.span() => "The `eq_int` option requires the `eq` option.");
1876    }
1877
1878    if options.eq.is_none() && options.eq_int.is_none() {
1879        return Ok((None, None));
1880    }
1881
1882    let arms = pyclass_richcmp_arms(options, ctx)?;
1883
1884    let eq = options.eq.map(|eq| {
1885        quote_spanned! { eq.span() =>
1886            let self_val = self;
1887            if let ::std::result::Result::Ok(other) = #pyo3_path::types::PyAnyMethods::downcast::<Self>(other) {
1888                let other = &*other.borrow();
1889                return match op {
1890                    #arms
1891                }
1892            }
1893        }
1894    });
1895
1896    let eq_int = options.eq_int.map(|eq_int| {
1897        quote_spanned! { eq_int.span() =>
1898            let self_val = self.__pyo3__int__();
1899            if let ::std::result::Result::Ok(other) = #pyo3_path::types::PyAnyMethods::extract::<#repr_type>(other).or_else(|_| {
1900                #pyo3_path::types::PyAnyMethods::downcast::<Self>(other).map(|o| o.borrow().__pyo3__int__())
1901            }) {
1902                return match op {
1903                    #arms
1904                }
1905            }
1906        }
1907    });
1908
1909    let mut richcmp_impl = parse_quote! {
1910        fn __pyo3__generated____richcmp__(
1911            &self,
1912            py: #pyo3_path::Python,
1913            other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>,
1914            op: #pyo3_path::pyclass::CompareOp
1915        ) -> #pyo3_path::PyResult<#pyo3_path::PyObject> {
1916            #eq
1917
1918            #eq_int
1919
1920            ::std::result::Result::Ok(py.NotImplemented())
1921        }
1922    };
1923    let richcmp_slot = if options.eq.is_some() {
1924        generate_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, "__richcmp__", ctx).unwrap()
1925    } else {
1926        generate_default_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, ctx).unwrap()
1927    };
1928    Ok((Some(richcmp_impl), Some(richcmp_slot)))
1929}
1930
1931fn pyclass_richcmp(
1932    options: &PyClassPyO3Options,
1933    cls: &syn::Type,
1934    ctx: &Ctx,
1935) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
1936    let Ctx { pyo3_path, .. } = ctx;
1937    if let Some(eq_int) = options.eq_int {
1938        bail_spanned!(eq_int.span() => "`eq_int` can only be used on simple enums.")
1939    }
1940
1941    let arms = pyclass_richcmp_arms(options, ctx)?;
1942    if options.eq.is_some() {
1943        let mut richcmp_impl = parse_quote! {
1944            fn __pyo3__generated____richcmp__(
1945                &self,
1946                py: #pyo3_path::Python,
1947                other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>,
1948                op: #pyo3_path::pyclass::CompareOp
1949            ) -> #pyo3_path::PyResult<#pyo3_path::PyObject> {
1950                let self_val = self;
1951                if let ::std::result::Result::Ok(other) = #pyo3_path::types::PyAnyMethods::downcast::<Self>(other) {
1952                    let other = &*other.borrow();
1953                    match op {
1954                        #arms
1955                    }
1956                } else {
1957                    ::std::result::Result::Ok(py.NotImplemented())
1958                }
1959            }
1960        };
1961        let richcmp_slot =
1962            generate_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, "__richcmp__", ctx)
1963                .unwrap();
1964        Ok((Some(richcmp_impl), Some(richcmp_slot)))
1965    } else {
1966        Ok((None, None))
1967    }
1968}
1969
1970fn pyclass_hash(
1971    options: &PyClassPyO3Options,
1972    cls: &syn::Type,
1973    ctx: &Ctx,
1974) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
1975    if options.hash.is_some() {
1976        ensure_spanned!(
1977            options.frozen.is_some(), options.hash.span() => "The `hash` option requires the `frozen` option.";
1978            options.eq.is_some(), options.hash.span() => "The `hash` option requires the `eq` option.";
1979        );
1980    }
1981    // FIXME: Use hash.map(...).unzip() on MSRV >= 1.66
1982    match options.hash {
1983        Some(opt) => {
1984            let mut hash_impl = parse_quote_spanned! { opt.span() =>
1985                fn __pyo3__generated____hash__(&self) -> u64 {
1986                    let mut s = ::std::collections::hash_map::DefaultHasher::new();
1987                    ::std::hash::Hash::hash(self, &mut s);
1988                    ::std::hash::Hasher::finish(&s)
1989                }
1990            };
1991            let hash_slot =
1992                generate_protocol_slot(cls, &mut hash_impl, &__HASH__, "__hash__", ctx).unwrap();
1993            Ok((Some(hash_impl), Some(hash_slot)))
1994        }
1995        None => Ok((None, None)),
1996    }
1997}
1998
1999/// Implements most traits used by `#[pyclass]`.
2000///
2001/// Specifically, it implements traits that only depend on class name,
2002/// and attributes of `#[pyclass]`, and docstrings.
2003/// Therefore it doesn't implement traits that depends on struct fields and enum variants.
2004struct PyClassImplsBuilder<'a> {
2005    cls: &'a syn::Ident,
2006    attr: &'a PyClassArgs,
2007    methods_type: PyClassMethodsType,
2008    default_methods: Vec<MethodAndMethodDef>,
2009    default_slots: Vec<MethodAndSlotDef>,
2010    doc: Option<PythonDoc>,
2011}
2012
2013impl<'a> PyClassImplsBuilder<'a> {
2014    fn new(
2015        cls: &'a syn::Ident,
2016        attr: &'a PyClassArgs,
2017        methods_type: PyClassMethodsType,
2018        default_methods: Vec<MethodAndMethodDef>,
2019        default_slots: Vec<MethodAndSlotDef>,
2020    ) -> Self {
2021        Self {
2022            cls,
2023            attr,
2024            methods_type,
2025            default_methods,
2026            default_slots,
2027            doc: None,
2028        }
2029    }
2030
2031    fn doc(self, doc: PythonDoc) -> Self {
2032        Self {
2033            doc: Some(doc),
2034            ..self
2035        }
2036    }
2037
2038    fn impl_all(&self, ctx: &Ctx) -> Result<TokenStream> {
2039        Ok([
2040            self.impl_pyclass(ctx),
2041            self.impl_extractext(ctx),
2042            self.impl_into_py(ctx),
2043            self.impl_pyclassimpl(ctx)?,
2044            self.impl_add_to_module(ctx),
2045            self.impl_freelist(ctx),
2046            self.impl_introspection(ctx),
2047        ]
2048        .into_iter()
2049        .collect())
2050    }
2051
2052    fn impl_pyclass(&self, ctx: &Ctx) -> TokenStream {
2053        let Ctx { pyo3_path, .. } = ctx;
2054        let cls = self.cls;
2055
2056        let frozen = if self.attr.options.frozen.is_some() {
2057            quote! { #pyo3_path::pyclass::boolean_struct::True }
2058        } else {
2059            quote! { #pyo3_path::pyclass::boolean_struct::False }
2060        };
2061
2062        quote! {
2063            impl #pyo3_path::PyClass for #cls {
2064                type Frozen = #frozen;
2065            }
2066        }
2067    }
2068    fn impl_extractext(&self, ctx: &Ctx) -> TokenStream {
2069        let Ctx { pyo3_path, .. } = ctx;
2070        let cls = self.cls;
2071        if self.attr.options.frozen.is_some() {
2072            quote! {
2073                impl<'a, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'py, false> for &'a #cls
2074                {
2075                    type Holder = ::std::option::Option<#pyo3_path::PyRef<'py, #cls>>;
2076
2077                    #[inline]
2078                    fn extract(obj: &'a #pyo3_path::Bound<'py, #pyo3_path::PyAny>, holder: &'a mut Self::Holder) -> #pyo3_path::PyResult<Self> {
2079                        #pyo3_path::impl_::extract_argument::extract_pyclass_ref(obj, holder)
2080                    }
2081                }
2082            }
2083        } else {
2084            quote! {
2085                impl<'a, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'py, false> for &'a #cls
2086                {
2087                    type Holder = ::std::option::Option<#pyo3_path::PyRef<'py, #cls>>;
2088
2089                    #[inline]
2090                    fn extract(obj: &'a #pyo3_path::Bound<'py, #pyo3_path::PyAny>, holder: &'a mut Self::Holder) -> #pyo3_path::PyResult<Self> {
2091                        #pyo3_path::impl_::extract_argument::extract_pyclass_ref(obj, holder)
2092                    }
2093                }
2094
2095                impl<'a, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'py, false> for &'a mut #cls
2096                {
2097                    type Holder = ::std::option::Option<#pyo3_path::PyRefMut<'py, #cls>>;
2098
2099                    #[inline]
2100                    fn extract(obj: &'a #pyo3_path::Bound<'py, #pyo3_path::PyAny>, holder: &'a mut Self::Holder) -> #pyo3_path::PyResult<Self> {
2101                        #pyo3_path::impl_::extract_argument::extract_pyclass_ref_mut(obj, holder)
2102                    }
2103                }
2104            }
2105        }
2106    }
2107
2108    fn impl_into_py(&self, ctx: &Ctx) -> TokenStream {
2109        let Ctx { pyo3_path, .. } = ctx;
2110        let cls = self.cls;
2111        let attr = self.attr;
2112        // If #cls is not extended type, we allow Self->PyObject conversion
2113        if attr.options.extends.is_none() {
2114            quote! {
2115                impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls {
2116                    type Target = Self;
2117                    type Output = #pyo3_path::Bound<'py, <Self as #pyo3_path::conversion::IntoPyObject<'py>>::Target>;
2118                    type Error = #pyo3_path::PyErr;
2119
2120                    fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result<
2121                        <Self as #pyo3_path::conversion::IntoPyObject>::Output,
2122                        <Self as #pyo3_path::conversion::IntoPyObject>::Error,
2123                    > {
2124                        #pyo3_path::Bound::new(py, self)
2125                    }
2126                }
2127            }
2128        } else {
2129            quote! {}
2130        }
2131    }
2132    fn impl_pyclassimpl(&self, ctx: &Ctx) -> Result<TokenStream> {
2133        let Ctx { pyo3_path, .. } = ctx;
2134        let cls = self.cls;
2135        let doc = self.doc.as_ref().map_or(
2136            LitCStr::empty(ctx).to_token_stream(),
2137            PythonDoc::to_token_stream,
2138        );
2139        let is_basetype = self.attr.options.subclass.is_some();
2140        let base = match &self.attr.options.extends {
2141            Some(extends_attr) => extends_attr.value.clone(),
2142            None => parse_quote! { #pyo3_path::PyAny },
2143        };
2144        let is_subclass = self.attr.options.extends.is_some();
2145        let is_mapping: bool = self.attr.options.mapping.is_some();
2146        let is_sequence: bool = self.attr.options.sequence.is_some();
2147
2148        ensure_spanned!(
2149            !(is_mapping && is_sequence),
2150            self.cls.span() => "a `#[pyclass]` cannot be both a `mapping` and a `sequence`"
2151        );
2152
2153        let dict_offset = if self.attr.options.dict.is_some() {
2154            quote! {
2155                fn dict_offset() -> ::std::option::Option<#pyo3_path::ffi::Py_ssize_t> {
2156                    ::std::option::Option::Some(#pyo3_path::impl_::pyclass::dict_offset::<Self>())
2157                }
2158            }
2159        } else {
2160            TokenStream::new()
2161        };
2162
2163        // insert space for weak ref
2164        let weaklist_offset = if self.attr.options.weakref.is_some() {
2165            quote! {
2166                fn weaklist_offset() -> ::std::option::Option<#pyo3_path::ffi::Py_ssize_t> {
2167                    ::std::option::Option::Some(#pyo3_path::impl_::pyclass::weaklist_offset::<Self>())
2168                }
2169            }
2170        } else {
2171            TokenStream::new()
2172        };
2173
2174        let thread_checker = if self.attr.options.unsendable.is_some() {
2175            quote! { #pyo3_path::impl_::pyclass::ThreadCheckerImpl }
2176        } else {
2177            quote! { #pyo3_path::impl_::pyclass::SendablePyClass<#cls> }
2178        };
2179
2180        let (pymethods_items, inventory, inventory_class) = match self.methods_type {
2181            PyClassMethodsType::Specialization => (quote! { collector.py_methods() }, None, None),
2182            PyClassMethodsType::Inventory => {
2183                // To allow multiple #[pymethods] block, we define inventory types.
2184                let inventory_class_name = syn::Ident::new(
2185                    &format!("Pyo3MethodsInventoryFor{}", cls.unraw()),
2186                    Span::call_site(),
2187                );
2188                (
2189                    quote! {
2190                        ::std::boxed::Box::new(
2191                            ::std::iter::Iterator::map(
2192                                #pyo3_path::inventory::iter::<<Self as #pyo3_path::impl_::pyclass::PyClassImpl>::Inventory>(),
2193                                #pyo3_path::impl_::pyclass::PyClassInventory::items
2194                            )
2195                        )
2196                    },
2197                    Some(quote! { type Inventory = #inventory_class_name; }),
2198                    Some(define_inventory_class(&inventory_class_name, ctx)),
2199                )
2200            }
2201        };
2202
2203        let default_methods = self
2204            .default_methods
2205            .iter()
2206            .map(|meth| &meth.associated_method)
2207            .chain(
2208                self.default_slots
2209                    .iter()
2210                    .map(|meth| &meth.associated_method),
2211            );
2212
2213        let default_method_defs = self.default_methods.iter().map(|meth| &meth.method_def);
2214        let default_slot_defs = self.default_slots.iter().map(|slot| &slot.slot_def);
2215        let freelist_slots = self.freelist_slots(ctx);
2216
2217        let class_mutability = if self.attr.options.frozen.is_some() {
2218            quote! {
2219                ImmutableChild
2220            }
2221        } else {
2222            quote! {
2223                MutableChild
2224            }
2225        };
2226
2227        let cls = self.cls;
2228        let attr = self.attr;
2229        let dict = if attr.options.dict.is_some() {
2230            quote! { #pyo3_path::impl_::pyclass::PyClassDictSlot }
2231        } else {
2232            quote! { #pyo3_path::impl_::pyclass::PyClassDummySlot }
2233        };
2234
2235        // insert space for weak ref
2236        let weakref = if attr.options.weakref.is_some() {
2237            quote! { #pyo3_path::impl_::pyclass::PyClassWeakRefSlot }
2238        } else {
2239            quote! { #pyo3_path::impl_::pyclass::PyClassDummySlot }
2240        };
2241
2242        let base_nativetype = if attr.options.extends.is_some() {
2243            quote! { <Self::BaseType as #pyo3_path::impl_::pyclass::PyClassBaseType>::BaseNativeType }
2244        } else {
2245            quote! { #pyo3_path::PyAny }
2246        };
2247
2248        let pyclass_base_type_impl = attr.options.subclass.map(|subclass| {
2249            quote_spanned! { subclass.span() =>
2250                impl #pyo3_path::impl_::pyclass::PyClassBaseType for #cls {
2251                    type LayoutAsBase = #pyo3_path::impl_::pycell::PyClassObject<Self>;
2252                    type BaseNativeType = <Self as #pyo3_path::impl_::pyclass::PyClassImpl>::BaseNativeType;
2253                    type Initializer = #pyo3_path::pyclass_init::PyClassInitializer<Self>;
2254                    type PyClassMutability = <Self as #pyo3_path::impl_::pyclass::PyClassImpl>::PyClassMutability;
2255                }
2256            }
2257        });
2258
2259        let assertions = if attr.options.unsendable.is_some() {
2260            TokenStream::new()
2261        } else {
2262            let assert = quote_spanned! { cls.span() => #pyo3_path::impl_::pyclass::assert_pyclass_sync::<#cls>(); };
2263            quote! {
2264                const _: () = {
2265                    #assert
2266                };
2267            }
2268        };
2269
2270        Ok(quote! {
2271            #assertions
2272
2273            #pyclass_base_type_impl
2274
2275            impl #pyo3_path::impl_::pyclass::PyClassImpl for #cls {
2276                const IS_BASETYPE: bool = #is_basetype;
2277                const IS_SUBCLASS: bool = #is_subclass;
2278                const IS_MAPPING: bool = #is_mapping;
2279                const IS_SEQUENCE: bool = #is_sequence;
2280
2281                type BaseType = #base;
2282                type ThreadChecker = #thread_checker;
2283                #inventory
2284                type PyClassMutability = <<#base as #pyo3_path::impl_::pyclass::PyClassBaseType>::PyClassMutability as #pyo3_path::impl_::pycell::PyClassMutability>::#class_mutability;
2285                type Dict = #dict;
2286                type WeakRef = #weakref;
2287                type BaseNativeType = #base_nativetype;
2288
2289                fn items_iter() -> #pyo3_path::impl_::pyclass::PyClassItemsIter {
2290                    use #pyo3_path::impl_::pyclass::*;
2291                    let collector = PyClassImplCollector::<Self>::new();
2292                    static INTRINSIC_ITEMS: PyClassItems = PyClassItems {
2293                        methods: &[#(#default_method_defs),*],
2294                        slots: &[#(#default_slot_defs),* #(#freelist_slots),*],
2295                    };
2296                    PyClassItemsIter::new(&INTRINSIC_ITEMS, #pymethods_items)
2297                }
2298
2299                fn doc(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<&'static ::std::ffi::CStr>  {
2300                    use #pyo3_path::impl_::pyclass::*;
2301                    static DOC: #pyo3_path::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = #pyo3_path::sync::GILOnceCell::new();
2302                    DOC.get_or_try_init(py, || {
2303                        let collector = PyClassImplCollector::<Self>::new();
2304                        build_pyclass_doc(<Self as #pyo3_path::PyTypeInfo>::NAME, #doc, collector.new_text_signature())
2305                    }).map(::std::ops::Deref::deref)
2306                }
2307
2308                #dict_offset
2309
2310                #weaklist_offset
2311
2312                fn lazy_type_object() -> &'static #pyo3_path::impl_::pyclass::LazyTypeObject<Self> {
2313                    use #pyo3_path::impl_::pyclass::LazyTypeObject;
2314                    static TYPE_OBJECT: LazyTypeObject<#cls> = LazyTypeObject::new();
2315                    &TYPE_OBJECT
2316                }
2317            }
2318
2319            #[doc(hidden)]
2320            #[allow(non_snake_case)]
2321            impl #cls {
2322                #(#default_methods)*
2323            }
2324
2325            #inventory_class
2326        })
2327    }
2328
2329    fn impl_add_to_module(&self, ctx: &Ctx) -> TokenStream {
2330        let Ctx { pyo3_path, .. } = ctx;
2331        let cls = self.cls;
2332        quote! {
2333            impl #cls {
2334                #[doc(hidden)]
2335                pub const _PYO3_DEF: #pyo3_path::impl_::pymodule::AddClassToModule<Self> = #pyo3_path::impl_::pymodule::AddClassToModule::new();
2336            }
2337        }
2338    }
2339
2340    fn impl_freelist(&self, ctx: &Ctx) -> TokenStream {
2341        let cls = self.cls;
2342        let Ctx { pyo3_path, .. } = ctx;
2343
2344        self.attr.options.freelist.as_ref().map_or(quote!{}, |freelist| {
2345            let freelist = &freelist.value;
2346            quote! {
2347                impl #pyo3_path::impl_::pyclass::PyClassWithFreeList for #cls {
2348                    #[inline]
2349                    fn get_free_list(py: #pyo3_path::Python<'_>) -> &'static ::std::sync::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList> {
2350                        static FREELIST: #pyo3_path::sync::GILOnceCell<::std::sync::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList>> = #pyo3_path::sync::GILOnceCell::new();
2351                        // If there's a race to fill the cell, the object created
2352                        // by the losing thread will be deallocated via RAII
2353                        &FREELIST.get_or_init(py, || {
2354                            ::std::sync::Mutex::new(#pyo3_path::impl_::freelist::PyObjectFreeList::with_capacity(#freelist))
2355                        })
2356                    }
2357                }
2358            }
2359        })
2360    }
2361
2362    fn freelist_slots(&self, ctx: &Ctx) -> Vec<TokenStream> {
2363        let Ctx { pyo3_path, .. } = ctx;
2364        let cls = self.cls;
2365
2366        if self.attr.options.freelist.is_some() {
2367            vec![
2368                quote! {
2369                    #pyo3_path::ffi::PyType_Slot {
2370                        slot: #pyo3_path::ffi::Py_tp_alloc,
2371                        pfunc: #pyo3_path::impl_::pyclass::alloc_with_freelist::<#cls> as *mut _,
2372                    }
2373                },
2374                quote! {
2375                    #pyo3_path::ffi::PyType_Slot {
2376                        slot: #pyo3_path::ffi::Py_tp_free,
2377                        pfunc: #pyo3_path::impl_::pyclass::free_with_freelist::<#cls> as *mut _,
2378                    }
2379                },
2380            ]
2381        } else {
2382            Vec::new()
2383        }
2384    }
2385
2386    #[cfg(feature = "experimental-inspect")]
2387    fn impl_introspection(&self, ctx: &Ctx) -> TokenStream {
2388        let Ctx { pyo3_path, .. } = ctx;
2389        let name = get_class_python_name(self.cls, self.attr).to_string();
2390        class_introspection_code(pyo3_path, self.cls, &name)
2391    }
2392
2393    #[cfg(not(feature = "experimental-inspect"))]
2394    fn impl_introspection(&self, _ctx: &Ctx) -> TokenStream {
2395        quote! {}
2396    }
2397}
2398
2399fn define_inventory_class(inventory_class_name: &syn::Ident, ctx: &Ctx) -> TokenStream {
2400    let Ctx { pyo3_path, .. } = ctx;
2401    quote! {
2402        #[doc(hidden)]
2403        pub struct #inventory_class_name {
2404            items: #pyo3_path::impl_::pyclass::PyClassItems,
2405        }
2406        impl #inventory_class_name {
2407            pub const fn new(items: #pyo3_path::impl_::pyclass::PyClassItems) -> Self {
2408                Self { items }
2409            }
2410        }
2411
2412        impl #pyo3_path::impl_::pyclass::PyClassInventory for #inventory_class_name {
2413            fn items(&self) -> &#pyo3_path::impl_::pyclass::PyClassItems {
2414                &self.items
2415            }
2416        }
2417
2418        #pyo3_path::inventory::collect!(#inventory_class_name);
2419    }
2420}
2421
2422fn generate_cfg_check(variants: &[PyClassEnumUnitVariant<'_>], cls: &syn::Ident) -> TokenStream {
2423    if variants.is_empty() {
2424        return quote! {};
2425    }
2426
2427    let mut conditions = Vec::new();
2428
2429    for variant in variants {
2430        let cfg_attrs = &variant.cfg_attrs;
2431
2432        if cfg_attrs.is_empty() {
2433            // There's at least one variant of the enum without cfg attributes,
2434            // so the check is not necessary
2435            return quote! {};
2436        }
2437
2438        for attr in cfg_attrs {
2439            if let syn::Meta::List(meta) = &attr.meta {
2440                let cfg_tokens = &meta.tokens;
2441                conditions.push(quote! { not(#cfg_tokens) });
2442            }
2443        }
2444    }
2445
2446    quote_spanned! {
2447        cls.span() =>
2448        #[cfg(all(#(#conditions),*))]
2449        ::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"));
2450    }
2451}
2452
2453const UNIQUE_GET: &str = "`get` may only be specified once";
2454const UNIQUE_SET: &str = "`set` may only be specified once";
2455const UNIQUE_NAME: &str = "`name` may only be specified once";
2456
2457const DUPE_SET: &str = "useless `set` - the struct is already annotated with `set_all`";
2458const DUPE_GET: &str = "useless `get` - the struct is already annotated with `get_all`";
2459const UNIT_GET: &str =
2460    "`get_all` on an unit struct does nothing, because unit structs have no fields";
2461const UNIT_SET: &str =
2462    "`set_all` on an unit struct does nothing, because unit structs have no fields";
2463
2464const USELESS_NAME: &str = "`name` is useless without `get` or `set`";
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here