Skip to content

Commit 0cf7893

Browse files
committed
derive intopyobject for structs and tuple structs
1 parent e789bd1 commit 0cf7893

File tree

1 file changed

+62
-29
lines changed

1 file changed

+62
-29
lines changed

pyo3-macros-backend/src/intopyobject.rs

Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use crate::attributes::{self, get_pyo3_options, CrateAttribute};
22
use crate::utils::Ctx;
3-
use proc_macro2::TokenStream;
3+
use proc_macro2::{Span, TokenStream};
44
use quote::quote;
5+
use syn::ext::IdentExt;
56
use syn::parse::{Parse, ParseStream};
67
use syn::spanned::Spanned as _;
7-
use syn::{parse_quote, Attribute, DeriveInput, Fields, Result, Token};
8+
use syn::{parse_quote, Attribute, DeriveInput, Fields, Index, Result, Token};
89

910
/// Attributes for deriving FromPyObject scoped on containers.
1011
enum ContainerPyO3Attribute {
@@ -80,7 +81,6 @@ struct IntoPyObjectImpl {
8081

8182
struct NamedStructField<'a> {
8283
ident: &'a syn::Ident,
83-
ty: &'a syn::Type,
8484
}
8585

8686
struct TupleStructField {}
@@ -112,16 +112,14 @@ enum ContainerType<'a> {
112112
///
113113
/// Either describes a struct or an enum variant.
114114
struct Container<'a> {
115-
path: syn::Path,
116115
ty: ContainerType<'a>,
117-
err_name: String,
118116
}
119117

120118
impl<'a> Container<'a> {
121119
/// Construct a container based on fields, identifier and attributes.
122120
///
123121
/// Fails if the variant has no fields or incompatible attributes.
124-
fn new(fields: &'a Fields, path: syn::Path, options: ContainerOptions) -> Result<Self> {
122+
fn new(fields: &'a Fields, options: ContainerOptions) -> Result<Self> {
125123
let style = match fields {
126124
Fields::Unnamed(unnamed) if !unnamed.unnamed.is_empty() => {
127125
if unnamed.unnamed.iter().count() == 1 {
@@ -161,9 +159,8 @@ impl<'a> Container<'a> {
161159
.ident
162160
.as_ref()
163161
.expect("Named fields should have identifiers");
164-
let ty = &field.ty;
165162

166-
Ok(NamedStructField { ident, ty })
163+
Ok(NamedStructField { ident })
167164
})
168165
.collect::<Result<Vec<_>>>()?;
169166
ContainerType::Struct(struct_fields)
@@ -173,35 +170,19 @@ impl<'a> Container<'a> {
173170
fields.span() => "cannot derive `IntoPyObject` for empty structs and variants"
174171
),
175172
};
176-
let err_name = path.segments.last().unwrap().ident.to_string();
177173

178-
let v = Container {
179-
path,
180-
ty: style,
181-
err_name,
182-
};
174+
let v = Container { ty: style };
183175
Ok(v)
184176
}
185177

186-
fn name(&self) -> String {
187-
let mut value = String::new();
188-
for segment in &self.path.segments {
189-
if !value.is_empty() {
190-
value.push_str("::");
191-
}
192-
value.push_str(&segment.ident.to_string());
193-
}
194-
value
195-
}
196-
197178
/// Build derivation body for a struct.
198179
fn build(&self, ctx: &Ctx) -> IntoPyObjectImpl {
199180
match &self.ty {
200181
ContainerType::StructNewtype(field) | ContainerType::TupleNewtype(field) => {
201182
self.build_newtype_struct(field, ctx)
202183
}
203-
ContainerType::Tuple(tups) => todo!(), // self.build_tuple_struct(tups, ctx),
204-
ContainerType::Struct(tups) => todo!(), // self.build_struct(tups, ctx),
184+
ContainerType::Tuple(fields) => self.build_tuple_struct(fields, ctx),
185+
ContainerType::Struct(fields) => self.build_struct(fields, ctx),
205186
}
206187
}
207188

@@ -221,6 +202,59 @@ impl<'a> Container<'a> {
221202
body: quote! { <#ty as #pyo3_path::conversion::IntoPyObject<'py>>::into_pyobject(#ident, py) },
222203
}
223204
}
205+
206+
fn build_struct(&self, fields: &[NamedStructField<'_>], ctx: &Ctx) -> IntoPyObjectImpl {
207+
let Ctx { pyo3_path, .. } = ctx;
208+
209+
let setter = fields
210+
.iter()
211+
.map(|f| {
212+
let key = f.ident.unraw().to_string();
213+
let ident = f.ident;
214+
quote! {
215+
dict.set_item(#key, self.#ident)?;
216+
}
217+
})
218+
.collect::<TokenStream>();
219+
220+
IntoPyObjectImpl {
221+
target: quote!(#pyo3_path::types::PyDict),
222+
output: quote!(#pyo3_path::Bound<'py, Self::Target>),
223+
error: quote!(#pyo3_path::PyErr),
224+
body: quote! {
225+
let dict = #pyo3_path::types::PyDict::new(py);
226+
#setter
227+
Ok(dict)
228+
},
229+
}
230+
}
231+
232+
fn build_tuple_struct(&self, fields: &[TupleStructField], ctx: &Ctx) -> IntoPyObjectImpl {
233+
let Ctx { pyo3_path, .. } = ctx;
234+
235+
let setter = fields
236+
.iter()
237+
.enumerate()
238+
.map(|(index, _)| {
239+
let i = Index {
240+
index: index as u32,
241+
span: Span::call_site(),
242+
};
243+
quote! {
244+
#pyo3_path::conversion::IntoPyObject::into_pyobject(self.#i, py)?,
245+
}
246+
})
247+
.collect::<TokenStream>();
248+
249+
IntoPyObjectImpl {
250+
target: quote!(#pyo3_path::types::PyTuple),
251+
output: quote!(#pyo3_path::Bound<'py, Self::Target>),
252+
error: quote!(#pyo3_path::PyErr),
253+
body: quote! {
254+
Ok(#pyo3_path::types::PyTuple::new(py, [#setter]))
255+
},
256+
}
257+
}
224258
}
225259

226260
pub fn build_derive_into_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
@@ -260,8 +294,7 @@ pub fn build_derive_into_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
260294
todo!()
261295
}
262296
syn::Data::Struct(st) => {
263-
let ident = &tokens.ident;
264-
let st = Container::new(&st.fields, parse_quote!(#ident), options)?;
297+
let st = Container::new(&st.fields, options)?;
265298
st.build(ctx)
266299
}
267300
syn::Data::Union(_) => bail_spanned!(

0 commit comments

Comments
 (0)