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