1 | use std::borrow::Cow; |
2 | |
3 | use crate::attributes::kw::frozen; |
4 | use crate::attributes::{ |
5 | self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute, |
6 | ModuleAttribute, NameAttribute, NameLitStr, RenameAllAttribute, TextSignatureAttribute, |
7 | TextSignatureAttributeValue, |
8 | }; |
9 | use crate::deprecations::{Deprecation, Deprecations}; |
10 | use crate::konst::{ConstAttributes, ConstSpec}; |
11 | use crate::method::FnSpec; |
12 | use crate::pyimpl::{gen_py_const, PyClassMethodsType}; |
13 | use crate::pymethod::{ |
14 | impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType, |
15 | SlotDef, __INT__, __REPR__, __RICHCMP__, |
16 | }; |
17 | use crate::utils::{self, apply_renaming_rule, get_pyo3_crate, PythonDoc}; |
18 | use crate::PyFunctionOptions; |
19 | use proc_macro2::{Ident, Span, TokenStream}; |
20 | use quote::quote; |
21 | use syn::ext::IdentExt; |
22 | use syn::parse::{Parse, ParseStream}; |
23 | use syn::punctuated::Punctuated; |
24 | use 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)] |
28 | pub enum PyClassKind { |
29 | Struct, |
30 | Enum, |
31 | } |
32 | |
33 | /// The parsed arguments of the pyclass macro |
34 | pub struct PyClassArgs { |
35 | pub class_kind: PyClassKind, |
36 | pub options: PyClassPyO3Options, |
37 | pub deprecations: Deprecations, |
38 | } |
39 | |
40 | impl 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)] |
59 | pub 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 | |
80 | enum 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 | |
99 | impl 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 | |
140 | impl 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 | |
196 | pub 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 | |
268 | enum Annotated<X, Y> { |
269 | Field(X), |
270 | Struct(Y), |
271 | } |
272 | |
273 | impl<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 |
283 | struct 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 | |
289 | enum FieldPyO3Option { |
290 | Get(attributes::kw::get), |
291 | Set(attributes::kw::set), |
292 | Name(NameAttribute), |
293 | } |
294 | |
295 | impl 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 | |
310 | impl 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 | |
342 | fn 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 | |
350 | fn 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 | |
386 | struct PyClassEnumVariant<'a> { |
387 | ident: &'a syn::Ident, |
388 | options: EnumVariantPyO3Options, |
389 | } |
390 | |
391 | impl<'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 | |
409 | struct 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 | |
417 | impl<'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 | |
457 | pub 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 |
478 | struct EnumVariantPyO3Options { |
479 | name: Option<NameAttribute>, |
480 | } |
481 | |
482 | enum EnumVariantPyO3Option { |
483 | Name(NameAttribute), |
484 | } |
485 | |
486 | impl 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 | |
497 | impl 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 | |
517 | fn 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 | |
643 | fn 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 | |
662 | fn 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 | |
684 | fn 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 | |
694 | fn 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 | |
742 | fn 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. |
779 | struct 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 | |
788 | impl<'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 | |
1128 | fn 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 | |
1150 | const UNIQUE_GET: &str = "`get` may only be specified once" ; |
1151 | const UNIQUE_SET: &str = "`set` may only be specified once" ; |
1152 | const UNIQUE_NAME: &str = "`name` may only be specified once" ; |
1153 | |
1154 | const DUPE_SET: &str = "useless `set` - the struct is already annotated with `set_all`" ; |
1155 | const DUPE_GET: &str = "useless `get` - the struct is already annotated with `get_all`" ; |
1156 | const UNIT_GET: &str = |
1157 | "`get_all` on an unit struct does nothing, because unit structs have no fields" ; |
1158 | const UNIT_SET: &str = |
1159 | "`set_all` on an unit struct does nothing, because unit structs have no fields" ; |
1160 | |
1161 | const USELESS_NAME: &str = "`name` is useless without `get` or `set`" ; |
1162 | |