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