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