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