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