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