1use std::borrow::Cow;
2
3use crate::attributes::kw::frozen;
4use crate::attributes::{
5 self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute,
6 ModuleAttribute, NameAttribute, NameLitStr, RenameAllAttribute, TextSignatureAttribute,
7 TextSignatureAttributeValue,
8};
9use crate::deprecations::{Deprecation, Deprecations};
10use crate::konst::{ConstAttributes, ConstSpec};
11use crate::method::FnSpec;
12use crate::pyimpl::{gen_py_const, PyClassMethodsType};
13use crate::pymethod::{
14 impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType,
15 SlotDef, __INT__, __REPR__, __RICHCMP__,
16};
17use crate::utils::{self, apply_renaming_rule, get_pyo3_crate, PythonDoc};
18use crate::PyFunctionOptions;
19use proc_macro2::{Ident, Span, TokenStream};
20use quote::quote;
21use syn::ext::IdentExt;
22use syn::parse::{Parse, ParseStream};
23use syn::punctuated::Punctuated;
24use syn::{parse_quote, spanned::Spanned, Result, Token};
25
26/// If the class is derived from a Rust `struct` or `enum`.
27#[derive(Copy, Clone, Debug, PartialEq, Eq)]
28pub enum PyClassKind {
29 Struct,
30 Enum,
31}
32
33/// The parsed arguments of the pyclass macro
34pub struct PyClassArgs {
35 pub class_kind: PyClassKind,
36 pub options: PyClassPyO3Options,
37 pub deprecations: Deprecations,
38}
39
40impl PyClassArgs {
41 fn parse(input: ParseStream<'_>, kind: PyClassKind) -> Result<Self> {
42 Ok(PyClassArgs {
43 class_kind: kind,
44 options: PyClassPyO3Options::parse(input)?,
45 deprecations: Deprecations::new(),
46 })
47 }
48
49 pub fn parse_stuct_args(input: ParseStream<'_>) -> syn::Result<Self> {
50 Self::parse(input, kind:PyClassKind::Struct)
51 }
52
53 pub fn parse_enum_args(input: ParseStream<'_>) -> syn::Result<Self> {
54 Self::parse(input, kind:PyClassKind::Enum)
55 }
56}
57
58#[derive(Default)]
59pub struct PyClassPyO3Options {
60 pub krate: Option<CrateAttribute>,
61 pub dict: Option<kw::dict>,
62 pub extends: Option<ExtendsAttribute>,
63 pub get_all: Option<kw::get_all>,
64 pub freelist: Option<FreelistAttribute>,
65 pub frozen: Option<kw::frozen>,
66 pub mapping: Option<kw::mapping>,
67 pub module: Option<ModuleAttribute>,
68 pub name: Option<NameAttribute>,
69 pub rename_all: Option<RenameAllAttribute>,
70 pub sequence: Option<kw::sequence>,
71 pub set_all: Option<kw::set_all>,
72 pub subclass: Option<kw::subclass>,
73 pub text_signature: Option<TextSignatureAttribute>,
74 pub unsendable: Option<kw::unsendable>,
75 pub weakref: Option<kw::weakref>,
76
77 pub deprecations: Deprecations,
78}
79
80enum PyClassPyO3Option {
81 Crate(CrateAttribute),
82 Dict(kw::dict),
83 Extends(ExtendsAttribute),
84 Freelist(FreelistAttribute),
85 Frozen(kw::frozen),
86 GetAll(kw::get_all),
87 Mapping(kw::mapping),
88 Module(ModuleAttribute),
89 Name(NameAttribute),
90 RenameAll(RenameAllAttribute),
91 Sequence(kw::sequence),
92 SetAll(kw::set_all),
93 Subclass(kw::subclass),
94 TextSignature(TextSignatureAttribute),
95 Unsendable(kw::unsendable),
96 Weakref(kw::weakref),
97}
98
99impl Parse for PyClassPyO3Option {
100 fn parse(input: ParseStream<'_>) -> Result<Self> {
101 let lookahead = input.lookahead1();
102 if lookahead.peek(Token![crate]) {
103 input.parse().map(PyClassPyO3Option::Crate)
104 } else if lookahead.peek(kw::dict) {
105 input.parse().map(PyClassPyO3Option::Dict)
106 } else if lookahead.peek(kw::extends) {
107 input.parse().map(PyClassPyO3Option::Extends)
108 } else if lookahead.peek(attributes::kw::freelist) {
109 input.parse().map(PyClassPyO3Option::Freelist)
110 } else if lookahead.peek(attributes::kw::frozen) {
111 input.parse().map(PyClassPyO3Option::Frozen)
112 } else if lookahead.peek(attributes::kw::get_all) {
113 input.parse().map(PyClassPyO3Option::GetAll)
114 } else if lookahead.peek(attributes::kw::mapping) {
115 input.parse().map(PyClassPyO3Option::Mapping)
116 } else if lookahead.peek(attributes::kw::module) {
117 input.parse().map(PyClassPyO3Option::Module)
118 } else if lookahead.peek(kw::name) {
119 input.parse().map(PyClassPyO3Option::Name)
120 } else if lookahead.peek(kw::rename_all) {
121 input.parse().map(PyClassPyO3Option::RenameAll)
122 } else if lookahead.peek(attributes::kw::sequence) {
123 input.parse().map(PyClassPyO3Option::Sequence)
124 } else if lookahead.peek(attributes::kw::set_all) {
125 input.parse().map(PyClassPyO3Option::SetAll)
126 } else if lookahead.peek(attributes::kw::subclass) {
127 input.parse().map(PyClassPyO3Option::Subclass)
128 } else if lookahead.peek(attributes::kw::text_signature) {
129 input.parse().map(PyClassPyO3Option::TextSignature)
130 } else if lookahead.peek(attributes::kw::unsendable) {
131 input.parse().map(PyClassPyO3Option::Unsendable)
132 } else if lookahead.peek(attributes::kw::weakref) {
133 input.parse().map(PyClassPyO3Option::Weakref)
134 } else {
135 Err(lookahead.error())
136 }
137 }
138}
139
140impl PyClassPyO3Options {
141 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
142 let mut options: PyClassPyO3Options = Default::default();
143
144 for option in Punctuated::<PyClassPyO3Option, syn::Token![,]>::parse_terminated(input)? {
145 options.set_option(option)?;
146 }
147
148 Ok(options)
149 }
150
151 pub fn take_pyo3_options(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
152 take_pyo3_options(attrs)?
153 .into_iter()
154 .try_for_each(|option| self.set_option(option))
155 }
156
157 fn set_option(&mut self, option: PyClassPyO3Option) -> syn::Result<()> {
158 macro_rules! set_option {
159 ($key:ident) => {
160 {
161 ensure_spanned!(
162 self.$key.is_none(),
163 $key.span() => concat!("`", stringify!($key), "` may only be specified once")
164 );
165 self.$key = Some($key);
166 }
167 };
168 }
169
170 match option {
171 PyClassPyO3Option::Crate(krate) => set_option!(krate),
172 PyClassPyO3Option::Dict(dict) => set_option!(dict),
173 PyClassPyO3Option::Extends(extends) => set_option!(extends),
174 PyClassPyO3Option::Freelist(freelist) => set_option!(freelist),
175 PyClassPyO3Option::Frozen(frozen) => set_option!(frozen),
176 PyClassPyO3Option::GetAll(get_all) => set_option!(get_all),
177 PyClassPyO3Option::Mapping(mapping) => set_option!(mapping),
178 PyClassPyO3Option::Module(module) => set_option!(module),
179 PyClassPyO3Option::Name(name) => set_option!(name),
180 PyClassPyO3Option::RenameAll(rename_all) => set_option!(rename_all),
181 PyClassPyO3Option::Sequence(sequence) => set_option!(sequence),
182 PyClassPyO3Option::SetAll(set_all) => set_option!(set_all),
183 PyClassPyO3Option::Subclass(subclass) => set_option!(subclass),
184 PyClassPyO3Option::TextSignature(text_signature) => {
185 self.deprecations
186 .push(Deprecation::PyClassTextSignature, text_signature.span());
187 set_option!(text_signature)
188 }
189 PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable),
190 PyClassPyO3Option::Weakref(weakref) => set_option!(weakref),
191 }
192 Ok(())
193 }
194}
195
196pub fn build_py_class(
197 class: &mut syn::ItemStruct,
198 mut args: PyClassArgs,
199 methods_type: PyClassMethodsType,
200) -> syn::Result<TokenStream> {
201 args.options.take_pyo3_options(&mut class.attrs)?;
202 let doc = utils::get_doc(&class.attrs, None);
203 let krate = get_pyo3_crate(&args.options.krate);
204
205 if let Some(lt) = class.generics.lifetimes().next() {
206 bail_spanned!(
207 lt.span() =>
208 "#[pyclass] cannot have lifetime parameters. \
209 For an explanation, see https://pyo3.rs/latest/class.html#no-lifetime-parameters"
210 );
211 }
212
213 ensure_spanned!(
214 class.generics.params.is_empty(),
215 class.generics.span() =>
216 "#[pyclass] cannot have generic parameters. \
217 For an explanation, see https://pyo3.rs/latest/class.html#no-generic-parameters"
218 );
219
220 let mut field_options: Vec<(&syn::Field, FieldPyO3Options)> = match &mut class.fields {
221 syn::Fields::Named(fields) => fields
222 .named
223 .iter_mut()
224 .map(|field| {
225 FieldPyO3Options::take_pyo3_options(&mut field.attrs)
226 .map(move |options| (&*field, options))
227 })
228 .collect::<Result<_>>()?,
229 syn::Fields::Unnamed(fields) => fields
230 .unnamed
231 .iter_mut()
232 .map(|field| {
233 FieldPyO3Options::take_pyo3_options(&mut field.attrs)
234 .map(move |options| (&*field, options))
235 })
236 .collect::<Result<_>>()?,
237 syn::Fields::Unit => {
238 if let Some(attr) = args.options.set_all {
239 return Err(syn::Error::new_spanned(attr, UNIT_SET));
240 };
241 if let Some(attr) = args.options.get_all {
242 return Err(syn::Error::new_spanned(attr, UNIT_GET));
243 };
244 // No fields for unit struct
245 Vec::new()
246 }
247 };
248
249 if let Some(attr) = args.options.get_all {
250 for (_, FieldPyO3Options { get, .. }) in &mut field_options {
251 if let Some(old_get) = get.replace(Annotated::Struct(attr)) {
252 return Err(syn::Error::new(old_get.span(), DUPE_GET));
253 }
254 }
255 }
256
257 if let Some(attr) = args.options.set_all {
258 for (_, FieldPyO3Options { set, .. }) in &mut field_options {
259 if let Some(old_set) = set.replace(Annotated::Struct(attr)) {
260 return Err(syn::Error::new(old_set.span(), DUPE_SET));
261 }
262 }
263 }
264
265 impl_class(&class.ident, &args, doc, field_options, methods_type, krate)
266}
267
268enum Annotated<X, Y> {
269 Field(X),
270 Struct(Y),
271}
272
273impl<X: Spanned, Y: Spanned> Annotated<X, Y> {
274 fn span(&self) -> Span {
275 match self {
276 Self::Field(x: &X) => x.span(),
277 Self::Struct(y: &Y) => y.span(),
278 }
279 }
280}
281
282/// `#[pyo3()]` options for pyclass fields
283struct FieldPyO3Options {
284 get: Option<Annotated<kw::get, kw::get_all>>,
285 set: Option<Annotated<kw::set, kw::set_all>>,
286 name: Option<NameAttribute>,
287}
288
289enum FieldPyO3Option {
290 Get(attributes::kw::get),
291 Set(attributes::kw::set),
292 Name(NameAttribute),
293}
294
295impl Parse for FieldPyO3Option {
296 fn parse(input: ParseStream<'_>) -> Result<Self> {
297 let lookahead: Lookahead1<'_> = input.lookahead1();
298 if lookahead.peek(token:attributes::kw::get) {
299 input.parse().map(op:FieldPyO3Option::Get)
300 } else if lookahead.peek(token:attributes::kw::set) {
301 input.parse().map(op:FieldPyO3Option::Set)
302 } else if lookahead.peek(token:attributes::kw::name) {
303 input.parse().map(op:FieldPyO3Option::Name)
304 } else {
305 Err(lookahead.error())
306 }
307 }
308}
309
310impl FieldPyO3Options {
311 fn take_pyo3_options(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
312 let mut options = FieldPyO3Options {
313 get: None,
314 set: None,
315 name: None,
316 };
317
318 for option in take_pyo3_options(attrs)? {
319 match option {
320 FieldPyO3Option::Get(kw) => {
321 if options.get.replace(Annotated::Field(kw)).is_some() {
322 return Err(syn::Error::new(kw.span(), UNIQUE_GET));
323 }
324 }
325 FieldPyO3Option::Set(kw) => {
326 if options.set.replace(Annotated::Field(kw)).is_some() {
327 return Err(syn::Error::new(kw.span(), UNIQUE_SET));
328 }
329 }
330 FieldPyO3Option::Name(name) => {
331 if options.name.replace(name).is_some() {
332 return Err(syn::Error::new(options.name.span(), UNIQUE_NAME));
333 }
334 }
335 }
336 }
337
338 Ok(options)
339 }
340}
341
342fn get_class_python_name<'a>(cls: &'a syn::Ident, args: &'a PyClassArgs) -> Cow<'a, syn::Ident> {
343 argsOption>.options
344 .name
345 .as_ref()
346 .map(|name_attr: &KeywordAttribute| Cow::Borrowed(&name_attr.value.0))
347 .unwrap_or_else(|| Cow::Owned(cls.unraw()))
348}
349
350fn impl_class(
351 cls: &syn::Ident,
352 args: &PyClassArgs,
353 doc: PythonDoc,
354 field_options: Vec<(&syn::Field, FieldPyO3Options)>,
355 methods_type: PyClassMethodsType,
356 krate: syn::Path,
357) -> syn::Result<TokenStream> {
358 let pytypeinfo_impl = impl_pytypeinfo(cls, args, Some(&args.options.deprecations));
359
360 let py_class_impl = PyClassImplsBuilder::new(
361 cls,
362 args,
363 methods_type,
364 descriptors_to_items(
365 cls,
366 args.options.rename_all.as_ref(),
367 args.options.frozen,
368 field_options,
369 )?,
370 vec![],
371 )
372 .doc(doc)
373 .impl_all()?;
374
375 Ok(quote! {
376 const _: () = {
377 use #krate as _pyo3;
378
379 #pytypeinfo_impl
380
381 #py_class_impl
382 };
383 })
384}
385
386struct PyClassEnumVariant<'a> {
387 ident: &'a syn::Ident,
388 options: EnumVariantPyO3Options,
389}
390
391impl<'a> PyClassEnumVariant<'a> {
392 fn python_name(&self, args: &PyClassArgs) -> Cow<'_, syn::Ident> {
393 self.options
394 .name
395 .as_ref()
396 .map(|name_attr: &KeywordAttribute| Cow::Borrowed(&name_attr.value.0))
397 .unwrap_or_else(|| {
398 let name: Ident = self.ident.unraw();
399 if let Some(attr: &KeywordAttribute) = &args.options.rename_all {
400 let new_name: String = apply_renaming_rule(attr.value.rule, &name.to_string());
401 Cow::Owned(Ident::new(&new_name, Span::call_site()))
402 } else {
403 Cow::Owned(name)
404 }
405 })
406 }
407}
408
409struct PyClassEnum<'a> {
410 ident: &'a syn::Ident,
411 // The underlying #[repr] of the enum, used to implement __int__ and __richcmp__.
412 // This matters when the underlying representation may not fit in `isize`.
413 repr_type: syn::Ident,
414 variants: Vec<PyClassEnumVariant<'a>>,
415}
416
417impl<'a> PyClassEnum<'a> {
418 fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result<Self> {
419 fn is_numeric_type(t: &syn::Ident) -> bool {
420 [
421 "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "u128", "i128", "usize",
422 "isize",
423 ]
424 .iter()
425 .any(|&s| t == s)
426 }
427 let ident = &enum_.ident;
428 // According to the [reference](https://doc.rust-lang.org/reference/items/enumerations.html),
429 // "Under the default representation, the specified discriminant is interpreted as an isize
430 // value", so `isize` should be enough by default.
431 let mut repr_type = syn::Ident::new("isize", proc_macro2::Span::call_site());
432 if let Some(attr) = enum_.attrs.iter().find(|attr| attr.path().is_ident("repr")) {
433 let args =
434 attr.parse_args_with(Punctuated::<TokenStream, Token![!]>::parse_terminated)?;
435 if let Some(ident) = args
436 .into_iter()
437 .filter_map(|ts| syn::parse2::<syn::Ident>(ts).ok())
438 .find(is_numeric_type)
439 {
440 repr_type = ident;
441 }
442 }
443
444 let variants = enum_
445 .variants
446 .iter_mut()
447 .map(extract_variant_data)
448 .collect::<syn::Result<_>>()?;
449 Ok(Self {
450 ident,
451 repr_type,
452 variants,
453 })
454 }
455}
456
457pub fn build_py_enum(
458 enum_: &mut syn::ItemEnum,
459 mut args: PyClassArgs,
460 method_type: PyClassMethodsType,
461) -> syn::Result<TokenStream> {
462 args.options.take_pyo3_options(&mut enum_.attrs)?;
463
464 if let Some(extends: &KeywordAttribute) = &args.options.extends {
465 bail_spanned!(extends.span() => "enums can't extend from other classes");
466 } else if let Some(subclass: &subclass) = &args.options.subclass {
467 bail_spanned!(subclass.span() => "enums can't be inherited by other classes");
468 } else if enum_.variants.is_empty() {
469 bail_spanned!(enum_.brace_token.span.join() => "#[pyclass] can't be used on enums without any variants");
470 }
471
472 let doc: PythonDoc = utils::get_doc(&enum_.attrs, text_signature:None);
473 let enum_: PyClassEnum<'_> = PyClassEnum::new(enum_)?;
474 impl_enum(enum_, &args, doc, methods_type:method_type)
475}
476
477/// `#[pyo3()]` options for pyclass enum variants
478struct EnumVariantPyO3Options {
479 name: Option<NameAttribute>,
480}
481
482enum EnumVariantPyO3Option {
483 Name(NameAttribute),
484}
485
486impl Parse for EnumVariantPyO3Option {
487 fn parse(input: ParseStream<'_>) -> Result<Self> {
488 let lookahead: Lookahead1<'_> = input.lookahead1();
489 if lookahead.peek(token:attributes::kw::name) {
490 input.parse().map(op:EnumVariantPyO3Option::Name)
491 } else {
492 Err(lookahead.error())
493 }
494 }
495}
496
497impl EnumVariantPyO3Options {
498 fn take_pyo3_options(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
499 let mut options: EnumVariantPyO3Options = EnumVariantPyO3Options { name: None };
500
501 for option: EnumVariantPyO3Option in take_pyo3_options(attrs)? {
502 match option {
503 EnumVariantPyO3Option::Name(name: KeywordAttribute) => {
504 ensure_spanned!(
505 options.name.is_none(),
506 name.span() => "`name` may only be specified once"
507 );
508 options.name = Some(name);
509 }
510 }
511 }
512
513 Ok(options)
514 }
515}
516
517fn impl_enum(
518 enum_: PyClassEnum<'_>,
519 args: &PyClassArgs,
520 doc: PythonDoc,
521 methods_type: PyClassMethodsType,
522) -> Result<TokenStream> {
523 let krate = get_pyo3_crate(&args.options.krate);
524 let cls = enum_.ident;
525 let ty: syn::Type = syn::parse_quote!(#cls);
526 let variants = enum_.variants;
527 let pytypeinfo = impl_pytypeinfo(cls, args, None);
528
529 let (default_repr, default_repr_slot) = {
530 let variants_repr = variants.iter().map(|variant| {
531 let variant_name = variant.ident;
532 // Assuming all variants are unit variants because they are the only type we support.
533 let repr = format!(
534 "{}.{}",
535 get_class_python_name(cls, args),
536 variant.python_name(args),
537 );
538 quote! { #cls::#variant_name => #repr, }
539 });
540 let mut repr_impl: syn::ImplItemFn = syn::parse_quote! {
541 fn __pyo3__repr__(&self) -> &'static str {
542 match self {
543 #(#variants_repr)*
544 }
545 }
546 };
547 let repr_slot = generate_default_protocol_slot(&ty, &mut repr_impl, &__REPR__).unwrap();
548 (repr_impl, repr_slot)
549 };
550
551 let repr_type = &enum_.repr_type;
552
553 let (default_int, default_int_slot) = {
554 // This implementation allows us to convert &T to #repr_type without implementing `Copy`
555 let variants_to_int = variants.iter().map(|variant| {
556 let variant_name = variant.ident;
557 quote! { #cls::#variant_name => #cls::#variant_name as #repr_type, }
558 });
559 let mut int_impl: syn::ImplItemFn = syn::parse_quote! {
560 fn __pyo3__int__(&self) -> #repr_type {
561 match self {
562 #(#variants_to_int)*
563 }
564 }
565 };
566 let int_slot = generate_default_protocol_slot(&ty, &mut int_impl, &__INT__).unwrap();
567 (int_impl, int_slot)
568 };
569
570 let (default_richcmp, default_richcmp_slot) = {
571 let mut richcmp_impl: syn::ImplItemFn = syn::parse_quote! {
572 fn __pyo3__richcmp__(
573 &self,
574 py: _pyo3::Python,
575 other: &_pyo3::PyAny,
576 op: _pyo3::basic::CompareOp
577 ) -> _pyo3::PyResult<_pyo3::PyObject> {
578 use _pyo3::conversion::ToPyObject;
579 use ::core::result::Result::*;
580 match op {
581 _pyo3::basic::CompareOp::Eq => {
582 let self_val = self.__pyo3__int__();
583 if let Ok(i) = other.extract::<#repr_type>() {
584 return Ok((self_val == i).to_object(py));
585 }
586 if let Ok(other) = other.extract::<_pyo3::PyRef<Self>>() {
587 return Ok((self_val == other.__pyo3__int__()).to_object(py));
588 }
589
590 return Ok(py.NotImplemented());
591 }
592 _pyo3::basic::CompareOp::Ne => {
593 let self_val = self.__pyo3__int__();
594 if let Ok(i) = other.extract::<#repr_type>() {
595 return Ok((self_val != i).to_object(py));
596 }
597 if let Ok(other) = other.extract::<_pyo3::PyRef<Self>>() {
598 return Ok((self_val != other.__pyo3__int__()).to_object(py));
599 }
600
601 return Ok(py.NotImplemented());
602 }
603 _ => Ok(py.NotImplemented()),
604 }
605 }
606 };
607 let richcmp_slot =
608 generate_default_protocol_slot(&ty, &mut richcmp_impl, &__RICHCMP__).unwrap();
609 (richcmp_impl, richcmp_slot)
610 };
611
612 let default_slots = vec![default_repr_slot, default_int_slot, default_richcmp_slot];
613
614 let pyclass_impls = PyClassImplsBuilder::new(
615 cls,
616 args,
617 methods_type,
618 enum_default_methods(cls, variants.iter().map(|v| (v.ident, v.python_name(args)))),
619 default_slots,
620 )
621 .doc(doc)
622 .impl_all()?;
623
624 Ok(quote! {
625 const _: () = {
626 use #krate as _pyo3;
627
628 #pytypeinfo
629
630 #pyclass_impls
631
632 #[doc(hidden)]
633 #[allow(non_snake_case)]
634 impl #cls {
635 #default_repr
636 #default_int
637 #default_richcmp
638 }
639 };
640 })
641}
642
643fn generate_default_protocol_slot(
644 cls: &syn::Type,
645 method: &mut syn::ImplItemFn,
646 slot: &SlotDef,
647) -> syn::Result<MethodAndSlotDef> {
648 let spec: FnSpec<'_> = FnSpecResult, Error>::parse(
649 &mut method.sig,
650 &mut Vec::new(),
651 options:PyFunctionOptions::default(),
652 )
653 .unwrap();
654 let name: String = spec.name.to_string();
655 slot.generate_type_slot(
656 &syn::parse_quote!(#cls),
657 &spec,
658 &format!("__default_{}__", name),
659 )
660}
661
662fn enum_default_methods<'a>(
663 cls: &'a syn::Ident,
664 unit_variant_names: impl IntoIterator<Item = (&'a syn::Ident, Cow<'a, syn::Ident>)>,
665) -> Vec<MethodAndMethodDef> {
666 let cls_type: Type = syn::parse_quote!(#cls);
667 let variant_to_attribute: impl Fn(&Ident, &Ident) -> … = |var_ident: &syn::Ident, py_ident: &syn::Ident| ConstSpec {
668 rust_ident: var_ident.clone(),
669 attributes: ConstAttributes {
670 is_class_attr: true,
671 name: Some(NameAttribute {
672 kw: syn::parse_quote! { name },
673 value: NameLitStr(py_ident.clone()),
674 }),
675 deprecations: Default::default(),
676 },
677 };
678 unit_variant_namesimpl Iterator
679 .into_iter()
680 .map(|(var: &Ident, py_name: Cow<'_, Ident>)| gen_py_const(&cls_type, &variant_to_attribute(var_ident:var, &py_name)))
681 .collect()
682}
683
684fn extract_variant_data(variant: &mut syn::Variant) -> syn::Result<PyClassEnumVariant<'_>> {
685 use syn::Fields;
686 let ident: &Ident = match variant.fields {
687 Fields::Unit => &variant.ident,
688 _ => bail_spanned!(variant.span() => "Currently only support unit variants."),
689 };
690 let options: EnumVariantPyO3Options = EnumVariantPyO3Options::take_pyo3_options(&mut variant.attrs)?;
691 Ok(PyClassEnumVariant { ident, options })
692}
693
694fn descriptors_to_items(
695 cls: &syn::Ident,
696 rename_all: Option<&RenameAllAttribute>,
697 frozen: Option<frozen>,
698 field_options: Vec<(&syn::Field, FieldPyO3Options)>,
699) -> syn::Result<Vec<MethodAndMethodDef>> {
700 let ty = syn::parse_quote!(#cls);
701 let mut items = Vec::new();
702 for (field_index, (field, options)) in field_options.into_iter().enumerate() {
703 if let FieldPyO3Options {
704 name: Some(name),
705 get: None,
706 set: None,
707 } = options
708 {
709 return Err(syn::Error::new_spanned(name, USELESS_NAME));
710 }
711
712 if options.get.is_some() {
713 let getter = impl_py_getter_def(
714 &ty,
715 PropertyType::Descriptor {
716 field_index,
717 field,
718 python_name: options.name.as_ref(),
719 renaming_rule: rename_all.map(|rename_all| rename_all.value.rule),
720 },
721 )?;
722 items.push(getter);
723 }
724
725 if let Some(set) = options.set {
726 ensure_spanned!(frozen.is_none(), set.span() => "cannot use `#[pyo3(set)]` on a `frozen` class");
727 let setter = impl_py_setter_def(
728 &ty,
729 PropertyType::Descriptor {
730 field_index,
731 field,
732 python_name: options.name.as_ref(),
733 renaming_rule: rename_all.map(|rename_all| rename_all.value.rule),
734 },
735 )?;
736 items.push(setter);
737 };
738 }
739 Ok(items)
740}
741
742fn impl_pytypeinfo(
743 cls: &syn::Ident,
744 attr: &PyClassArgs,
745 deprecations: Option<&Deprecations>,
746) -> TokenStream {
747 let cls_name = get_class_python_name(cls, attr).to_string();
748
749 let module = if let Some(ModuleAttribute { value, .. }) = &attr.options.module {
750 quote! { ::core::option::Option::Some(#value) }
751 } else {
752 quote! { ::core::option::Option::None }
753 };
754
755 quote! {
756 unsafe impl _pyo3::type_object::PyTypeInfo for #cls {
757 type AsRefTarget = _pyo3::PyCell<Self>;
758
759 const NAME: &'static str = #cls_name;
760 const MODULE: ::std::option::Option<&'static str> = #module;
761
762 #[inline]
763 fn type_object_raw(py: _pyo3::Python<'_>) -> *mut _pyo3::ffi::PyTypeObject {
764 #deprecations
765
766 <#cls as _pyo3::impl_::pyclass::PyClassImpl>::lazy_type_object()
767 .get_or_init(py)
768 .as_type_ptr()
769 }
770 }
771 }
772}
773
774/// Implements most traits used by `#[pyclass]`.
775///
776/// Specifically, it implements traits that only depend on class name,
777/// and attributes of `#[pyclass]`, and docstrings.
778/// Therefore it doesn't implement traits that depends on struct fields and enum variants.
779struct PyClassImplsBuilder<'a> {
780 cls: &'a syn::Ident,
781 attr: &'a PyClassArgs,
782 methods_type: PyClassMethodsType,
783 default_methods: Vec<MethodAndMethodDef>,
784 default_slots: Vec<MethodAndSlotDef>,
785 doc: Option<PythonDoc>,
786}
787
788impl<'a> PyClassImplsBuilder<'a> {
789 fn new(
790 cls: &'a syn::Ident,
791 attr: &'a PyClassArgs,
792 methods_type: PyClassMethodsType,
793 default_methods: Vec<MethodAndMethodDef>,
794 default_slots: Vec<MethodAndSlotDef>,
795 ) -> Self {
796 Self {
797 cls,
798 attr,
799 methods_type,
800 default_methods,
801 default_slots,
802 doc: None,
803 }
804 }
805
806 fn doc(self, doc: PythonDoc) -> Self {
807 Self {
808 doc: Some(doc),
809 ..self
810 }
811 }
812
813 fn impl_all(&self) -> Result<TokenStream> {
814 let tokens = vec![
815 self.impl_pyclass(),
816 self.impl_extractext(),
817 self.impl_into_py(),
818 self.impl_pyclassimpl()?,
819 self.impl_freelist(),
820 ]
821 .into_iter()
822 .collect();
823 Ok(tokens)
824 }
825
826 fn impl_pyclass(&self) -> TokenStream {
827 let cls = self.cls;
828
829 let frozen = if self.attr.options.frozen.is_some() {
830 quote! { _pyo3::pyclass::boolean_struct::True }
831 } else {
832 quote! { _pyo3::pyclass::boolean_struct::False }
833 };
834
835 quote! {
836 impl _pyo3::PyClass for #cls {
837 type Frozen = #frozen;
838 }
839 }
840 }
841 fn impl_extractext(&self) -> TokenStream {
842 let cls = self.cls;
843 if self.attr.options.frozen.is_some() {
844 quote! {
845 impl<'a, 'py> _pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a #cls
846 {
847 type Holder = ::std::option::Option<_pyo3::PyRef<'py, #cls>>;
848
849 #[inline]
850 fn extract(obj: &'py _pyo3::PyAny, holder: &'a mut Self::Holder) -> _pyo3::PyResult<Self> {
851 _pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder)
852 }
853 }
854 }
855 } else {
856 quote! {
857 impl<'a, 'py> _pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a #cls
858 {
859 type Holder = ::std::option::Option<_pyo3::PyRef<'py, #cls>>;
860
861 #[inline]
862 fn extract(obj: &'py _pyo3::PyAny, holder: &'a mut Self::Holder) -> _pyo3::PyResult<Self> {
863 _pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder)
864 }
865 }
866
867 impl<'a, 'py> _pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a mut #cls
868 {
869 type Holder = ::std::option::Option<_pyo3::PyRefMut<'py, #cls>>;
870
871 #[inline]
872 fn extract(obj: &'py _pyo3::PyAny, holder: &'a mut Self::Holder) -> _pyo3::PyResult<Self> {
873 _pyo3::impl_::extract_argument::extract_pyclass_ref_mut(obj, holder)
874 }
875 }
876 }
877 }
878 }
879
880 fn impl_into_py(&self) -> TokenStream {
881 let cls = self.cls;
882 let attr = self.attr;
883 // If #cls is not extended type, we allow Self->PyObject conversion
884 if attr.options.extends.is_none() {
885 quote! {
886 impl _pyo3::IntoPy<_pyo3::PyObject> for #cls {
887 fn into_py(self, py: _pyo3::Python) -> _pyo3::PyObject {
888 _pyo3::IntoPy::into_py(_pyo3::Py::new(py, self).unwrap(), py)
889 }
890 }
891 }
892 } else {
893 quote! {}
894 }
895 }
896 fn impl_pyclassimpl(&self) -> Result<TokenStream> {
897 let cls = self.cls;
898 let doc = self.doc.as_ref().map_or(quote! {"\0"}, |doc| quote! {#doc});
899 let deprecated_text_signature = match self
900 .attr
901 .options
902 .text_signature
903 .as_ref()
904 .map(|attr| &attr.value)
905 {
906 Some(TextSignatureAttributeValue::Str(s)) => quote!(::std::option::Option::Some(#s)),
907 Some(TextSignatureAttributeValue::Disabled(_)) | None => {
908 quote!(::std::option::Option::None)
909 }
910 };
911 let is_basetype = self.attr.options.subclass.is_some();
912 let base = self
913 .attr
914 .options
915 .extends
916 .as_ref()
917 .map(|extends_attr| extends_attr.value.clone())
918 .unwrap_or_else(|| parse_quote! { _pyo3::PyAny });
919 let is_subclass = self.attr.options.extends.is_some();
920 let is_mapping: bool = self.attr.options.mapping.is_some();
921 let is_sequence: bool = self.attr.options.sequence.is_some();
922
923 ensure_spanned!(
924 !(is_mapping && is_sequence),
925 self.cls.span() => "a `#[pyclass]` cannot be both a `mapping` and a `sequence`"
926 );
927
928 let dict_offset = if self.attr.options.dict.is_some() {
929 quote! {
930 fn dict_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> {
931 ::std::option::Option::Some(_pyo3::impl_::pyclass::dict_offset::<Self>())
932 }
933 }
934 } else {
935 TokenStream::new()
936 };
937
938 // insert space for weak ref
939 let weaklist_offset = if self.attr.options.weakref.is_some() {
940 quote! {
941 fn weaklist_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> {
942 ::std::option::Option::Some(_pyo3::impl_::pyclass::weaklist_offset::<Self>())
943 }
944 }
945 } else {
946 TokenStream::new()
947 };
948
949 let thread_checker = if self.attr.options.unsendable.is_some() {
950 quote! { _pyo3::impl_::pyclass::ThreadCheckerImpl }
951 } else {
952 quote! { _pyo3::impl_::pyclass::SendablePyClass<#cls> }
953 };
954
955 let (pymethods_items, inventory, inventory_class) = match self.methods_type {
956 PyClassMethodsType::Specialization => (quote! { collector.py_methods() }, None, None),
957 PyClassMethodsType::Inventory => {
958 // To allow multiple #[pymethods] block, we define inventory types.
959 let inventory_class_name = syn::Ident::new(
960 &format!("Pyo3MethodsInventoryFor{}", cls.unraw()),
961 Span::call_site(),
962 );
963 (
964 quote! {
965 ::std::boxed::Box::new(
966 ::std::iter::Iterator::map(
967 _pyo3::inventory::iter::<<Self as _pyo3::impl_::pyclass::PyClassImpl>::Inventory>(),
968 _pyo3::impl_::pyclass::PyClassInventory::items
969 )
970 )
971 },
972 Some(quote! { type Inventory = #inventory_class_name; }),
973 Some(define_inventory_class(&inventory_class_name)),
974 )
975 }
976 };
977
978 let default_methods = self
979 .default_methods
980 .iter()
981 .map(|meth| &meth.associated_method)
982 .chain(
983 self.default_slots
984 .iter()
985 .map(|meth| &meth.associated_method),
986 );
987
988 let default_method_defs = self.default_methods.iter().map(|meth| &meth.method_def);
989 let default_slot_defs = self.default_slots.iter().map(|slot| &slot.slot_def);
990 let freelist_slots = self.freelist_slots();
991
992 let deprecations = &self.attr.deprecations;
993
994 let class_mutability = if self.attr.options.frozen.is_some() {
995 quote! {
996 ImmutableChild
997 }
998 } else {
999 quote! {
1000 MutableChild
1001 }
1002 };
1003
1004 let cls = self.cls;
1005 let attr = self.attr;
1006 let dict = if attr.options.dict.is_some() {
1007 quote! { _pyo3::impl_::pyclass::PyClassDictSlot }
1008 } else {
1009 quote! { _pyo3::impl_::pyclass::PyClassDummySlot }
1010 };
1011
1012 // insert space for weak ref
1013 let weakref = if attr.options.weakref.is_some() {
1014 quote! { _pyo3::impl_::pyclass::PyClassWeakRefSlot }
1015 } else {
1016 quote! { _pyo3::impl_::pyclass::PyClassDummySlot }
1017 };
1018
1019 let base_nativetype = if attr.options.extends.is_some() {
1020 quote! { <Self::BaseType as _pyo3::impl_::pyclass::PyClassBaseType>::BaseNativeType }
1021 } else {
1022 quote! { _pyo3::PyAny }
1023 };
1024
1025 Ok(quote! {
1026 impl _pyo3::impl_::pyclass::PyClassImpl for #cls {
1027 const IS_BASETYPE: bool = #is_basetype;
1028 const IS_SUBCLASS: bool = #is_subclass;
1029 const IS_MAPPING: bool = #is_mapping;
1030 const IS_SEQUENCE: bool = #is_sequence;
1031
1032 type BaseType = #base;
1033 type ThreadChecker = #thread_checker;
1034 #inventory
1035 type PyClassMutability = <<#base as _pyo3::impl_::pyclass::PyClassBaseType>::PyClassMutability as _pyo3::impl_::pycell::PyClassMutability>::#class_mutability;
1036 type Dict = #dict;
1037 type WeakRef = #weakref;
1038 type BaseNativeType = #base_nativetype;
1039
1040 fn items_iter() -> _pyo3::impl_::pyclass::PyClassItemsIter {
1041 use _pyo3::impl_::pyclass::*;
1042 let collector = PyClassImplCollector::<Self>::new();
1043 #deprecations;
1044 static INTRINSIC_ITEMS: PyClassItems = PyClassItems {
1045 methods: &[#(#default_method_defs),*],
1046 slots: &[#(#default_slot_defs),* #(#freelist_slots),*],
1047 };
1048 PyClassItemsIter::new(&INTRINSIC_ITEMS, #pymethods_items)
1049 }
1050
1051 fn doc(py: _pyo3::Python<'_>) -> _pyo3::PyResult<&'static ::std::ffi::CStr> {
1052 use _pyo3::impl_::pyclass::*;
1053 static DOC: _pyo3::once_cell::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = _pyo3::once_cell::GILOnceCell::new();
1054 DOC.get_or_try_init(py, || {
1055 let collector = PyClassImplCollector::<Self>::new();
1056 build_pyclass_doc(<#cls as _pyo3::PyTypeInfo>::NAME, #doc, #deprecated_text_signature.or_else(|| collector.new_text_signature()))
1057 }).map(::std::ops::Deref::deref)
1058 }
1059
1060 #dict_offset
1061
1062 #weaklist_offset
1063
1064 fn lazy_type_object() -> &'static _pyo3::impl_::pyclass::LazyTypeObject<Self> {
1065 use _pyo3::impl_::pyclass::LazyTypeObject;
1066 static TYPE_OBJECT: LazyTypeObject<#cls> = LazyTypeObject::new();
1067 &TYPE_OBJECT
1068 }
1069 }
1070
1071 #[doc(hidden)]
1072 #[allow(non_snake_case)]
1073 impl #cls {
1074 #(#default_methods)*
1075 }
1076
1077 #inventory_class
1078 })
1079 }
1080
1081 fn impl_freelist(&self) -> TokenStream {
1082 let cls = self.cls;
1083
1084 self.attr.options.freelist.as_ref().map_or(quote!{}, |freelist| {
1085 let freelist = &freelist.value;
1086 quote! {
1087 impl _pyo3::impl_::pyclass::PyClassWithFreeList for #cls {
1088 #[inline]
1089 fn get_free_list(py: _pyo3::Python<'_>) -> &mut _pyo3::impl_::freelist::FreeList<*mut _pyo3::ffi::PyObject> {
1090 static mut FREELIST: *mut _pyo3::impl_::freelist::FreeList<*mut _pyo3::ffi::PyObject> = 0 as *mut _;
1091 unsafe {
1092 if FREELIST.is_null() {
1093 FREELIST = ::std::boxed::Box::into_raw(::std::boxed::Box::new(
1094 _pyo3::impl_::freelist::FreeList::with_capacity(#freelist)));
1095 }
1096 &mut *FREELIST
1097 }
1098 }
1099 }
1100 }
1101 })
1102 }
1103
1104 fn freelist_slots(&self) -> Vec<TokenStream> {
1105 let cls = self.cls;
1106
1107 if self.attr.options.freelist.is_some() {
1108 vec![
1109 quote! {
1110 _pyo3::ffi::PyType_Slot {
1111 slot: _pyo3::ffi::Py_tp_alloc,
1112 pfunc: _pyo3::impl_::pyclass::alloc_with_freelist::<#cls> as *mut _,
1113 }
1114 },
1115 quote! {
1116 _pyo3::ffi::PyType_Slot {
1117 slot: _pyo3::ffi::Py_tp_free,
1118 pfunc: _pyo3::impl_::pyclass::free_with_freelist::<#cls> as *mut _,
1119 }
1120 },
1121 ]
1122 } else {
1123 Vec::new()
1124 }
1125 }
1126}
1127
1128fn define_inventory_class(inventory_class_name: &syn::Ident) -> TokenStream {
1129 quote! {
1130 #[doc(hidden)]
1131 pub struct #inventory_class_name {
1132 items: _pyo3::impl_::pyclass::PyClassItems,
1133 }
1134 impl #inventory_class_name {
1135 pub const fn new(items: _pyo3::impl_::pyclass::PyClassItems) -> Self {
1136 Self { items }
1137 }
1138 }
1139
1140 impl _pyo3::impl_::pyclass::PyClassInventory for #inventory_class_name {
1141 fn items(&self) -> &_pyo3::impl_::pyclass::PyClassItems {
1142 &self.items
1143 }
1144 }
1145
1146 _pyo3::inventory::collect!(#inventory_class_name);
1147 }
1148}
1149
1150const UNIQUE_GET: &str = "`get` may only be specified once";
1151const UNIQUE_SET: &str = "`set` may only be specified once";
1152const UNIQUE_NAME: &str = "`name` may only be specified once";
1153
1154const DUPE_SET: &str = "useless `set` - the struct is already annotated with `set_all`";
1155const DUPE_GET: &str = "useless `get` - the struct is already annotated with `get_all`";
1156const UNIT_GET: &str =
1157 "`get_all` on an unit struct does nothing, because unit structs have no fields";
1158const UNIT_SET: &str =
1159 "`set_all` on an unit struct does nothing, because unit structs have no fields";
1160
1161const USELESS_NAME: &str = "`name` is useless without `get` or `set`";
1162