1 | use crate::attributes::{self, get_pyo3_options, CrateAttribute, IntoPyWithAttribute}; |
2 | use crate::utils::Ctx; |
3 | use proc_macro2::{Span, TokenStream}; |
4 | use quote::{format_ident, quote, quote_spanned}; |
5 | use syn::ext::IdentExt; |
6 | use syn::parse::{Parse, ParseStream}; |
7 | use syn::spanned::Spanned as _; |
8 | use syn::{ |
9 | parenthesized, parse_quote, Attribute, DataEnum, DeriveInput, Fields, Ident, Index, Result, |
10 | Token, |
11 | }; |
12 | |
13 | /// Attributes for deriving `IntoPyObject` scoped on containers. |
14 | enum ContainerPyO3Attribute { |
15 | /// Treat the Container as a Wrapper, directly convert its field into the output object. |
16 | Transparent(attributes::kw::transparent), |
17 | /// Change the path for the pyo3 crate |
18 | Crate(CrateAttribute), |
19 | } |
20 | |
21 | impl Parse for ContainerPyO3Attribute { |
22 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
23 | let lookahead: Lookahead1<'_> = input.lookahead1(); |
24 | if lookahead.peek(token:attributes::kw::transparent) { |
25 | let kw: attributes::kw::transparent = input.parse()?; |
26 | Ok(ContainerPyO3Attribute::Transparent(kw)) |
27 | } else if lookahead.peek(Token![crate]) { |
28 | input.parse().map(op:ContainerPyO3Attribute::Crate) |
29 | } else { |
30 | Err(lookahead.error()) |
31 | } |
32 | } |
33 | } |
34 | |
35 | #[derive (Default)] |
36 | struct ContainerOptions { |
37 | /// Treat the Container as a Wrapper, directly convert its field into the output object. |
38 | transparent: Option<attributes::kw::transparent>, |
39 | /// Change the path for the pyo3 crate |
40 | krate: Option<CrateAttribute>, |
41 | } |
42 | |
43 | impl ContainerOptions { |
44 | fn from_attrs(attrs: &[Attribute]) -> Result<Self> { |
45 | let mut options = ContainerOptions::default(); |
46 | |
47 | for attr in attrs { |
48 | if let Some(pyo3_attrs) = get_pyo3_options(attr)? { |
49 | pyo3_attrs |
50 | .into_iter() |
51 | .try_for_each(|opt| options.set_option(opt))?; |
52 | } |
53 | } |
54 | Ok(options) |
55 | } |
56 | |
57 | fn set_option(&mut self, option: ContainerPyO3Attribute) -> syn::Result<()> { |
58 | macro_rules! set_option { |
59 | ($key:ident) => { |
60 | { |
61 | ensure_spanned!( |
62 | self.$key.is_none(), |
63 | $key.span() => concat!("`" , stringify!($key), "` may only be specified once" ) |
64 | ); |
65 | self.$key = Some($key); |
66 | } |
67 | }; |
68 | } |
69 | |
70 | match option { |
71 | ContainerPyO3Attribute::Transparent(transparent) => set_option!(transparent), |
72 | ContainerPyO3Attribute::Crate(krate) => set_option!(krate), |
73 | } |
74 | Ok(()) |
75 | } |
76 | } |
77 | |
78 | #[derive (Debug, Clone)] |
79 | struct ItemOption { |
80 | field: Option<syn::LitStr>, |
81 | span: Span, |
82 | } |
83 | |
84 | impl ItemOption { |
85 | fn span(&self) -> Span { |
86 | self.span |
87 | } |
88 | } |
89 | |
90 | enum FieldAttribute { |
91 | Item(ItemOption), |
92 | IntoPyWith(IntoPyWithAttribute), |
93 | } |
94 | |
95 | impl Parse for FieldAttribute { |
96 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
97 | let lookahead = input.lookahead1(); |
98 | if lookahead.peek(attributes::kw::attribute) { |
99 | let attr: attributes::kw::attribute = input.parse()?; |
100 | bail_spanned!(attr.span => "`attribute` is not supported by `IntoPyObject`" ); |
101 | } else if lookahead.peek(attributes::kw::item) { |
102 | let attr: attributes::kw::item = input.parse()?; |
103 | if input.peek(syn::token::Paren) { |
104 | let content; |
105 | let _ = parenthesized!(content in input); |
106 | let key = content.parse()?; |
107 | if !content.is_empty() { |
108 | return Err( |
109 | content.error("expected at most one argument: `item` or `item(key)`" ) |
110 | ); |
111 | } |
112 | Ok(FieldAttribute::Item(ItemOption { |
113 | field: Some(key), |
114 | span: attr.span, |
115 | })) |
116 | } else { |
117 | Ok(FieldAttribute::Item(ItemOption { |
118 | field: None, |
119 | span: attr.span, |
120 | })) |
121 | } |
122 | } else if lookahead.peek(attributes::kw::into_py_with) { |
123 | input.parse().map(FieldAttribute::IntoPyWith) |
124 | } else { |
125 | Err(lookahead.error()) |
126 | } |
127 | } |
128 | } |
129 | |
130 | #[derive (Clone, Debug, Default)] |
131 | struct FieldAttributes { |
132 | item: Option<ItemOption>, |
133 | into_py_with: Option<IntoPyWithAttribute>, |
134 | } |
135 | |
136 | impl FieldAttributes { |
137 | /// Extract the field attributes. |
138 | fn from_attrs(attrs: &[Attribute]) -> Result<Self> { |
139 | let mut options = FieldAttributes::default(); |
140 | |
141 | for attr in attrs { |
142 | if let Some(pyo3_attrs) = get_pyo3_options(attr)? { |
143 | pyo3_attrs |
144 | .into_iter() |
145 | .try_for_each(|opt| options.set_option(opt))?; |
146 | } |
147 | } |
148 | Ok(options) |
149 | } |
150 | |
151 | fn set_option(&mut self, option: FieldAttribute) -> syn::Result<()> { |
152 | macro_rules! set_option { |
153 | ($key:ident) => { |
154 | { |
155 | ensure_spanned!( |
156 | self.$key.is_none(), |
157 | $key.span() => concat!("`" , stringify!($key), "` may only be specified once" ) |
158 | ); |
159 | self.$key = Some($key); |
160 | } |
161 | }; |
162 | } |
163 | |
164 | match option { |
165 | FieldAttribute::Item(item) => set_option!(item), |
166 | FieldAttribute::IntoPyWith(into_py_with) => set_option!(into_py_with), |
167 | } |
168 | Ok(()) |
169 | } |
170 | } |
171 | |
172 | enum IntoPyObjectTypes { |
173 | Transparent(syn::Type), |
174 | Opaque { |
175 | target: TokenStream, |
176 | output: TokenStream, |
177 | error: TokenStream, |
178 | }, |
179 | } |
180 | |
181 | struct IntoPyObjectImpl { |
182 | types: IntoPyObjectTypes, |
183 | body: TokenStream, |
184 | } |
185 | |
186 | struct NamedStructField<'a> { |
187 | ident: &'a syn::Ident, |
188 | field: &'a syn::Field, |
189 | item: Option<ItemOption>, |
190 | into_py_with: Option<IntoPyWithAttribute>, |
191 | } |
192 | |
193 | struct TupleStructField<'a> { |
194 | field: &'a syn::Field, |
195 | into_py_with: Option<IntoPyWithAttribute>, |
196 | } |
197 | |
198 | /// Container Style |
199 | /// |
200 | /// Covers Structs, Tuplestructs and corresponding Newtypes. |
201 | enum ContainerType<'a> { |
202 | /// Struct Container, e.g. `struct Foo { a: String }` |
203 | /// |
204 | /// Variant contains the list of field identifiers and the corresponding extraction call. |
205 | Struct(Vec<NamedStructField<'a>>), |
206 | /// Newtype struct container, e.g. `#[transparent] struct Foo { a: String }` |
207 | /// |
208 | /// The field specified by the identifier is extracted directly from the object. |
209 | StructNewtype(&'a syn::Field), |
210 | /// Tuple struct, e.g. `struct Foo(String)`. |
211 | /// |
212 | /// Variant contains a list of conversion methods for each of the fields that are directly |
213 | /// extracted from the tuple. |
214 | Tuple(Vec<TupleStructField<'a>>), |
215 | /// Tuple newtype, e.g. `#[transparent] struct Foo(String)` |
216 | /// |
217 | /// The wrapped field is directly extracted from the object. |
218 | TupleNewtype(&'a syn::Field), |
219 | } |
220 | |
221 | /// Data container |
222 | /// |
223 | /// Either describes a struct or an enum variant. |
224 | struct Container<'a, const REF: bool> { |
225 | path: syn::Path, |
226 | receiver: Option<Ident>, |
227 | ty: ContainerType<'a>, |
228 | } |
229 | |
230 | /// Construct a container based on fields, identifier and attributes. |
231 | impl<'a, const REF: bool> Container<'a, REF> { |
232 | /// |
233 | /// Fails if the variant has no fields or incompatible attributes. |
234 | fn new( |
235 | receiver: Option<Ident>, |
236 | fields: &'a Fields, |
237 | path: syn::Path, |
238 | options: ContainerOptions, |
239 | ) -> Result<Self> { |
240 | let style = match fields { |
241 | Fields::Unnamed(unnamed) if !unnamed.unnamed.is_empty() => { |
242 | let mut tuple_fields = unnamed |
243 | .unnamed |
244 | .iter() |
245 | .map(|field| { |
246 | let attrs = FieldAttributes::from_attrs(&field.attrs)?; |
247 | ensure_spanned!( |
248 | attrs.item.is_none(), |
249 | attrs.item.unwrap().span() => "`item` is not permitted on tuple struct elements." |
250 | ); |
251 | Ok(TupleStructField { |
252 | field, |
253 | into_py_with: attrs.into_py_with, |
254 | }) |
255 | }) |
256 | .collect::<Result<Vec<_>>>()?; |
257 | if tuple_fields.len() == 1 { |
258 | // Always treat a 1-length tuple struct as "transparent", even without the |
259 | // explicit annotation. |
260 | let TupleStructField { |
261 | field, |
262 | into_py_with, |
263 | } = tuple_fields.pop().unwrap(); |
264 | ensure_spanned!( |
265 | into_py_with.is_none(), |
266 | into_py_with.span() => "`into_py_with` is not permitted on `transparent` structs" |
267 | ); |
268 | ContainerType::TupleNewtype(field) |
269 | } else if options.transparent.is_some() { |
270 | bail_spanned!( |
271 | fields.span() => "transparent structs and variants can only have 1 field" |
272 | ); |
273 | } else { |
274 | ContainerType::Tuple(tuple_fields) |
275 | } |
276 | } |
277 | Fields::Named(named) if !named.named.is_empty() => { |
278 | if options.transparent.is_some() { |
279 | ensure_spanned!( |
280 | named.named.iter().count() == 1, |
281 | fields.span() => "transparent structs and variants can only have 1 field" |
282 | ); |
283 | |
284 | let field = named.named.iter().next().unwrap(); |
285 | let attrs = FieldAttributes::from_attrs(&field.attrs)?; |
286 | ensure_spanned!( |
287 | attrs.item.is_none(), |
288 | attrs.item.unwrap().span() => "`transparent` structs may not have `item` for the inner field" |
289 | ); |
290 | ensure_spanned!( |
291 | attrs.into_py_with.is_none(), |
292 | attrs.into_py_with.span() => "`into_py_with` is not permitted on `transparent` structs or variants" |
293 | ); |
294 | ContainerType::StructNewtype(field) |
295 | } else { |
296 | let struct_fields = named |
297 | .named |
298 | .iter() |
299 | .map(|field| { |
300 | let ident = field |
301 | .ident |
302 | .as_ref() |
303 | .expect("Named fields should have identifiers" ); |
304 | |
305 | let attrs = FieldAttributes::from_attrs(&field.attrs)?; |
306 | |
307 | Ok(NamedStructField { |
308 | ident, |
309 | field, |
310 | item: attrs.item, |
311 | into_py_with: attrs.into_py_with, |
312 | }) |
313 | }) |
314 | .collect::<Result<Vec<_>>>()?; |
315 | ContainerType::Struct(struct_fields) |
316 | } |
317 | } |
318 | _ => bail_spanned!( |
319 | fields.span() => "cannot derive `IntoPyObject` for empty structs" |
320 | ), |
321 | }; |
322 | |
323 | let v = Container { |
324 | path, |
325 | receiver, |
326 | ty: style, |
327 | }; |
328 | Ok(v) |
329 | } |
330 | |
331 | fn match_pattern(&self) -> TokenStream { |
332 | let path = &self.path; |
333 | let pattern = match &self.ty { |
334 | ContainerType::Struct(fields) => fields |
335 | .iter() |
336 | .enumerate() |
337 | .map(|(i, f)| { |
338 | let ident = f.ident; |
339 | let new_ident = format_ident!("arg {i}" ); |
340 | quote! {#ident: #new_ident,} |
341 | }) |
342 | .collect::<TokenStream>(), |
343 | ContainerType::StructNewtype(field) => { |
344 | let ident = field.ident.as_ref().unwrap(); |
345 | quote!(#ident: arg0) |
346 | } |
347 | ContainerType::Tuple(fields) => { |
348 | let i = (0..fields.len()).map(Index::from); |
349 | let idents = (0..fields.len()).map(|i| format_ident!("arg {i}" )); |
350 | quote! { #(#i: #idents,)* } |
351 | } |
352 | ContainerType::TupleNewtype(_) => quote!(0: arg0), |
353 | }; |
354 | |
355 | quote! { #path{ #pattern } } |
356 | } |
357 | |
358 | /// Build derivation body for a struct. |
359 | fn build(&self, ctx: &Ctx) -> IntoPyObjectImpl { |
360 | match &self.ty { |
361 | ContainerType::StructNewtype(field) | ContainerType::TupleNewtype(field) => { |
362 | self.build_newtype_struct(field, ctx) |
363 | } |
364 | ContainerType::Tuple(fields) => self.build_tuple_struct(fields, ctx), |
365 | ContainerType::Struct(fields) => self.build_struct(fields, ctx), |
366 | } |
367 | } |
368 | |
369 | fn build_newtype_struct(&self, field: &syn::Field, ctx: &Ctx) -> IntoPyObjectImpl { |
370 | let Ctx { pyo3_path, .. } = ctx; |
371 | let ty = &field.ty; |
372 | |
373 | let unpack = self |
374 | .receiver |
375 | .as_ref() |
376 | .map(|i| { |
377 | let pattern = self.match_pattern(); |
378 | quote! { let #pattern = #i;} |
379 | }) |
380 | .unwrap_or_default(); |
381 | |
382 | IntoPyObjectImpl { |
383 | types: IntoPyObjectTypes::Transparent(ty.clone()), |
384 | body: quote_spanned! { ty.span() => |
385 | #unpack |
386 | #pyo3_path::conversion::IntoPyObject::into_pyobject(arg0, py) |
387 | }, |
388 | } |
389 | } |
390 | |
391 | fn build_struct(&self, fields: &[NamedStructField<'_>], ctx: &Ctx) -> IntoPyObjectImpl { |
392 | let Ctx { pyo3_path, .. } = ctx; |
393 | |
394 | let unpack = self |
395 | .receiver |
396 | .as_ref() |
397 | .map(|i| { |
398 | let pattern = self.match_pattern(); |
399 | quote! { let #pattern = #i;} |
400 | }) |
401 | .unwrap_or_default(); |
402 | |
403 | let setter = fields |
404 | .iter() |
405 | .enumerate() |
406 | .map(|(i, f)| { |
407 | let key = f |
408 | .item |
409 | .as_ref() |
410 | .and_then(|item| item.field.as_ref()) |
411 | .map(|item| item.value()) |
412 | .unwrap_or_else(|| f.ident.unraw().to_string()); |
413 | let value = Ident::new(&format!("arg {i}" ), f.field.ty.span()); |
414 | |
415 | if let Some(expr_path) = f.into_py_with.as_ref().map(|i|&i.value) { |
416 | let cow = if REF { |
417 | quote!(::std::borrow::Cow::Borrowed(#value)) |
418 | } else { |
419 | quote!(::std::borrow::Cow::Owned(#value)) |
420 | }; |
421 | quote! { |
422 | let into_py_with: fn(::std::borrow::Cow<'_, _>, #pyo3_path::Python<'py>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'py, #pyo3_path::PyAny>> = #expr_path; |
423 | #pyo3_path::types::PyDictMethods::set_item(&dict, #key, into_py_with(#cow, py)?)?; |
424 | } |
425 | } else { |
426 | quote! { |
427 | #pyo3_path::types::PyDictMethods::set_item(&dict, #key, #value)?; |
428 | } |
429 | } |
430 | }) |
431 | .collect::<TokenStream>(); |
432 | |
433 | IntoPyObjectImpl { |
434 | types: IntoPyObjectTypes::Opaque { |
435 | target: quote!(#pyo3_path::types::PyDict), |
436 | output: quote!(#pyo3_path::Bound<'py, Self::Target>), |
437 | error: quote!(#pyo3_path::PyErr), |
438 | }, |
439 | body: quote! { |
440 | #unpack |
441 | let dict = #pyo3_path::types::PyDict::new(py); |
442 | #setter |
443 | ::std::result::Result::Ok::<_, Self::Error>(dict) |
444 | }, |
445 | } |
446 | } |
447 | |
448 | fn build_tuple_struct(&self, fields: &[TupleStructField<'_>], ctx: &Ctx) -> IntoPyObjectImpl { |
449 | let Ctx { pyo3_path, .. } = ctx; |
450 | |
451 | let unpack = self |
452 | .receiver |
453 | .as_ref() |
454 | .map(|i| { |
455 | let pattern = self.match_pattern(); |
456 | quote! { let #pattern = #i;} |
457 | }) |
458 | .unwrap_or_default(); |
459 | |
460 | let setter = fields |
461 | .iter() |
462 | .enumerate() |
463 | .map(|(i, f)| { |
464 | let ty = &f.field.ty; |
465 | let value = Ident::new(&format!("arg {i}" ), f.field.ty.span()); |
466 | |
467 | if let Some(expr_path) = f.into_py_with.as_ref().map(|i|&i.value) { |
468 | let cow = if REF { |
469 | quote!(::std::borrow::Cow::Borrowed(#value)) |
470 | } else { |
471 | quote!(::std::borrow::Cow::Owned(#value)) |
472 | }; |
473 | quote_spanned! { ty.span() => |
474 | { |
475 | let into_py_with: fn(::std::borrow::Cow<'_, _>, #pyo3_path::Python<'py>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'py, #pyo3_path::PyAny>> = #expr_path; |
476 | into_py_with(#cow, py)? |
477 | }, |
478 | } |
479 | } else { |
480 | quote_spanned! { ty.span() => |
481 | #pyo3_path::conversion::IntoPyObject::into_pyobject(#value, py) |
482 | .map(#pyo3_path::BoundObject::into_any) |
483 | .map(#pyo3_path::BoundObject::into_bound)?, |
484 | } |
485 | } |
486 | }) |
487 | .collect::<TokenStream>(); |
488 | |
489 | IntoPyObjectImpl { |
490 | types: IntoPyObjectTypes::Opaque { |
491 | target: quote!(#pyo3_path::types::PyTuple), |
492 | output: quote!(#pyo3_path::Bound<'py, Self::Target>), |
493 | error: quote!(#pyo3_path::PyErr), |
494 | }, |
495 | body: quote! { |
496 | #unpack |
497 | #pyo3_path::types::PyTuple::new(py, [#setter]) |
498 | }, |
499 | } |
500 | } |
501 | } |
502 | |
503 | /// Describes derivation input of an enum. |
504 | struct Enum<'a, const REF: bool> { |
505 | variants: Vec<Container<'a, REF>>, |
506 | } |
507 | |
508 | impl<'a, const REF: bool> Enum<'a, REF> { |
509 | /// Construct a new enum representation. |
510 | /// |
511 | /// `data_enum` is the `syn` representation of the input enum, `ident` is the |
512 | /// `Identifier` of the enum. |
513 | fn new(data_enum: &'a DataEnum, ident: &'a Ident) -> Result<Self> { |
514 | ensure_spanned!( |
515 | !data_enum.variants.is_empty(), |
516 | ident.span() => "cannot derive `IntoPyObject` for empty enum" |
517 | ); |
518 | let variants = data_enum |
519 | .variants |
520 | .iter() |
521 | .map(|variant| { |
522 | let attrs = ContainerOptions::from_attrs(&variant.attrs)?; |
523 | let var_ident = &variant.ident; |
524 | |
525 | ensure_spanned!( |
526 | !variant.fields.is_empty(), |
527 | variant.ident.span() => "cannot derive `IntoPyObject` for empty variants" |
528 | ); |
529 | |
530 | Container::new( |
531 | None, |
532 | &variant.fields, |
533 | parse_quote!(#ident::#var_ident), |
534 | attrs, |
535 | ) |
536 | }) |
537 | .collect::<Result<Vec<_>>>()?; |
538 | |
539 | Ok(Enum { variants }) |
540 | } |
541 | |
542 | /// Build derivation body for enums. |
543 | fn build(&self, ctx: &Ctx) -> IntoPyObjectImpl { |
544 | let Ctx { pyo3_path, .. } = ctx; |
545 | |
546 | let variants = self |
547 | .variants |
548 | .iter() |
549 | .map(|v| { |
550 | let IntoPyObjectImpl { body, .. } = v.build(ctx); |
551 | let pattern = v.match_pattern(); |
552 | quote! { |
553 | #pattern => { |
554 | {#body} |
555 | .map(#pyo3_path::BoundObject::into_any) |
556 | .map(#pyo3_path::BoundObject::into_bound) |
557 | .map_err(::std::convert::Into::<#pyo3_path::PyErr>::into) |
558 | } |
559 | } |
560 | }) |
561 | .collect::<TokenStream>(); |
562 | |
563 | IntoPyObjectImpl { |
564 | types: IntoPyObjectTypes::Opaque { |
565 | target: quote!(#pyo3_path::types::PyAny), |
566 | output: quote!(#pyo3_path::Bound<'py, <Self as #pyo3_path::conversion::IntoPyObject<'py>>::Target>), |
567 | error: quote!(#pyo3_path::PyErr), |
568 | }, |
569 | body: quote! { |
570 | match self { |
571 | #variants |
572 | } |
573 | }, |
574 | } |
575 | } |
576 | } |
577 | |
578 | // if there is a `'py` lifetime, we treat it as the `Python<'py>` lifetime |
579 | fn verify_and_get_lifetime(generics: &syn::Generics) -> Option<&syn::LifetimeParam> { |
580 | let mut lifetimes: Lifetimes<'_> = generics.lifetimes(); |
581 | lifetimes.find(|l: &&LifetimeParam| l.lifetime.ident == "py" ) |
582 | } |
583 | |
584 | pub fn build_derive_into_pyobject<const REF: bool>(tokens: &DeriveInput) -> Result<TokenStream> { |
585 | let options = ContainerOptions::from_attrs(&tokens.attrs)?; |
586 | let ctx = &Ctx::new(&options.krate, None); |
587 | let Ctx { pyo3_path, .. } = &ctx; |
588 | |
589 | let (_, ty_generics, _) = tokens.generics.split_for_impl(); |
590 | let mut trait_generics = tokens.generics.clone(); |
591 | if REF { |
592 | trait_generics.params.push(parse_quote!('_a)); |
593 | } |
594 | let lt_param = if let Some(lt) = verify_and_get_lifetime(&trait_generics) { |
595 | lt.clone() |
596 | } else { |
597 | trait_generics.params.push(parse_quote!('py)); |
598 | parse_quote!('py) |
599 | }; |
600 | let (impl_generics, _, where_clause) = trait_generics.split_for_impl(); |
601 | |
602 | let mut where_clause = where_clause.cloned().unwrap_or_else(|| parse_quote!(where)); |
603 | for param in trait_generics.type_params() { |
604 | let gen_ident = ¶m.ident; |
605 | where_clause.predicates.push(if REF { |
606 | parse_quote!(&'_a #gen_ident: #pyo3_path::conversion::IntoPyObject<'py>) |
607 | } else { |
608 | parse_quote!(#gen_ident: #pyo3_path::conversion::IntoPyObject<'py>) |
609 | }) |
610 | } |
611 | |
612 | let IntoPyObjectImpl { types, body } = match &tokens.data { |
613 | syn::Data::Enum(en) => { |
614 | if options.transparent.is_some() { |
615 | bail_spanned!(tokens.span() => "`transparent` is not supported at top level for enums" ); |
616 | } |
617 | let en = Enum::<REF>::new(en, &tokens.ident)?; |
618 | en.build(ctx) |
619 | } |
620 | syn::Data::Struct(st) => { |
621 | let ident = &tokens.ident; |
622 | let st = Container::<REF>::new( |
623 | Some(Ident::new("self" , Span::call_site())), |
624 | &st.fields, |
625 | parse_quote!(#ident), |
626 | options, |
627 | )?; |
628 | st.build(ctx) |
629 | } |
630 | syn::Data::Union(_) => bail_spanned!( |
631 | tokens.span() => "#[derive(`IntoPyObject`)] is not supported for unions" |
632 | ), |
633 | }; |
634 | |
635 | let (target, output, error) = match types { |
636 | IntoPyObjectTypes::Transparent(ty) => { |
637 | if REF { |
638 | ( |
639 | quote! { <&'_a #ty as #pyo3_path::IntoPyObject<'py>>::Target }, |
640 | quote! { <&'_a #ty as #pyo3_path::IntoPyObject<'py>>::Output }, |
641 | quote! { <&'_a #ty as #pyo3_path::IntoPyObject<'py>>::Error }, |
642 | ) |
643 | } else { |
644 | ( |
645 | quote! { <#ty as #pyo3_path::IntoPyObject<'py>>::Target }, |
646 | quote! { <#ty as #pyo3_path::IntoPyObject<'py>>::Output }, |
647 | quote! { <#ty as #pyo3_path::IntoPyObject<'py>>::Error }, |
648 | ) |
649 | } |
650 | } |
651 | IntoPyObjectTypes::Opaque { |
652 | target, |
653 | output, |
654 | error, |
655 | } => (target, output, error), |
656 | }; |
657 | |
658 | let ident = &tokens.ident; |
659 | let ident = if REF { |
660 | quote! { &'_a #ident} |
661 | } else { |
662 | quote! { #ident } |
663 | }; |
664 | Ok(quote!( |
665 | #[automatically_derived] |
666 | impl #impl_generics #pyo3_path::conversion::IntoPyObject<#lt_param> for #ident #ty_generics #where_clause { |
667 | type Target = #target; |
668 | type Output = #output; |
669 | type Error = #error; |
670 | |
671 | fn into_pyobject(self, py: #pyo3_path::Python<#lt_param>) -> ::std::result::Result< |
672 | <Self as #pyo3_path::conversion::IntoPyObject>::Output, |
673 | <Self as #pyo3_path::conversion::IntoPyObject>::Error, |
674 | > { |
675 | #body |
676 | } |
677 | } |
678 | )) |
679 | } |
680 | |