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