Skip to main content

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