Skip to content

Commit 51f96a7

Browse files
committed
backup
1 parent f49c185 commit 51f96a7

File tree

11 files changed

+477
-77
lines changed

11 files changed

+477
-77
lines changed

crates/derive-impl/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ pub fn pystruct_sequence_try_from_object(input: DeriveInput) -> TokenStream {
6666
result_to_tokens(pystructseq::impl_pystruct_sequence_try_from_object(input))
6767
}
6868

69+
pub fn pystructseq(attr: PunctuatedNestedMeta, item: Item) -> TokenStream {
70+
result_to_tokens(pystructseq::impl_pystructseq(attr, item))
71+
}
72+
6973
pub fn py_compile(input: TokenStream, compiler: &dyn Compiler) -> TokenStream {
7074
result_to_tokens(compile_bytecode::impl_py_compile(input, compiler))
7175
}

crates/derive-impl/src/pystructseq.rs

Lines changed: 244 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,27 @@
1+
use crate::util::{ItemMeta, ItemMetaInner};
12
use proc_macro2::TokenStream;
2-
use quote::quote;
3-
use syn::{DeriveInput, Ident, Result};
3+
use quote::{format_ident, quote};
4+
use syn::{DeriveInput, Ident, Item, Result};
45
use syn_ext::ext::{AttributeExt, GetIdent};
5-
use syn_ext::types::Meta;
6+
use syn_ext::types::{Meta, PunctuatedNestedMeta};
67

7-
// returning a pair of not-skipped and skipped field names
8+
/// Parse field info from struct: returns (not_skipped_fields, skipped_fields)
89
fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> {
910
let syn::Data::Struct(struc) = &mut input.data else {
10-
bail_span!(
11-
input,
12-
"#[pystruct_sequence] can only be on a struct declaration"
13-
)
11+
bail_span!(input, "#[pystructseq] can only be on a struct declaration")
1412
};
1513

1614
let syn::Fields::Named(fields) = &mut struc.fields else {
1715
bail_span!(
1816
input,
19-
"#[pystruct_sequence] can only be on a struct with named fields"
17+
"#[pystructseq] can only be on a struct with named fields"
2018
);
2119
};
2220

2321
let mut not_skipped = Vec::with_capacity(fields.named.len());
2422
let mut skipped = Vec::with_capacity(fields.named.len());
2523
for field in &mut fields.named {
2624
let mut skip = false;
27-
// Collect all attributes with pystruct and their indices
2825
let mut attrs_to_remove = Vec::new();
2926

3027
for (i, attr) in field.attrs.iter().enumerate() {
@@ -47,8 +44,6 @@ fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> {
4744
.cloned()
4845
.collect();
4946

50-
// Follow #[serde(skip)] convention.
51-
// Consider to add skip_serializing and skip_deserializing if required.
5247
for ident in idents {
5348
match ident.to_string().as_str() {
5449
"skip" => {
@@ -63,27 +58,257 @@ fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> {
6358
attrs_to_remove.push(i);
6459
}
6560

66-
// Remove attributes in reverse order to maintain valid indices
67-
attrs_to_remove.sort_unstable_by(|a, b| b.cmp(a)); // Sort in descending order
61+
// Remove attributes in reverse order
62+
attrs_to_remove.sort_unstable_by(|a, b| b.cmp(a));
6863
for index in attrs_to_remove {
6964
field.attrs.remove(index);
7065
}
7166
let ident = field.ident.clone().unwrap();
7267
if skip {
73-
skipped.push(ident.clone());
68+
skipped.push(ident);
7469
} else {
75-
not_skipped.push(ident.clone());
70+
not_skipped.push(ident);
7671
}
7772
}
7873

7974
Ok((not_skipped, skipped))
8075
}
8176

77+
/// Derive Python type name from Data struct name
78+
/// e.g., StructTimeData -> PyStructTime, StatResultData -> PyStatResult
79+
fn derive_pytype_name(data_ident: &Ident) -> Ident {
80+
let name = data_ident.to_string();
81+
let base_name = name.strip_suffix("Data").unwrap_or(&name);
82+
let py_name = if base_name.starts_with("Py") {
83+
base_name.to_string()
84+
} else {
85+
format!("Py{}", base_name)
86+
};
87+
format_ident!("{}", py_name)
88+
}
89+
90+
/// Meta parser for #[pystructseq(...)]
91+
struct PyStructSeqMeta {
92+
inner: ItemMetaInner,
93+
}
94+
95+
impl ItemMeta for PyStructSeqMeta {
96+
const ALLOWED_NAMES: &'static [&'static str] = &["name", "module"];
97+
98+
fn from_inner(inner: ItemMetaInner) -> Self {
99+
Self { inner }
100+
}
101+
fn inner(&self) -> &ItemMetaInner {
102+
&self.inner
103+
}
104+
}
105+
106+
impl PyStructSeqMeta {
107+
fn class_name(&self) -> Result<String> {
108+
const KEY: &str = "name";
109+
let inner = self.inner();
110+
if let Some((_, meta)) = inner.meta_map.get(KEY) {
111+
match meta {
112+
Meta::NameValue(syn::MetaNameValue {
113+
value:
114+
syn::Expr::Lit(syn::ExprLit {
115+
lit: syn::Lit::Str(lit),
116+
..
117+
}),
118+
..
119+
}) => return Ok(lit.value()),
120+
Meta::Path(_) => return Ok(inner.item_name()),
121+
_ => {}
122+
}
123+
bail_span!(
124+
inner.meta_ident,
125+
"#[pystructseq({KEY}=value)] expects a string value"
126+
)
127+
} else {
128+
// Default: derive from struct name (e.g., StructTimeData -> struct_time)
129+
let name = inner.item_name();
130+
let base_name = name.strip_suffix("Data").unwrap_or(&name);
131+
Ok(base_name.to_lowercase())
132+
}
133+
}
134+
135+
fn module(&self) -> Result<Option<String>> {
136+
const KEY: &str = "module";
137+
let inner = self.inner();
138+
if let Some((_, meta)) = inner.meta_map.get(KEY) {
139+
match meta {
140+
Meta::NameValue(syn::MetaNameValue {
141+
value:
142+
syn::Expr::Lit(syn::ExprLit {
143+
lit: syn::Lit::Str(lit),
144+
..
145+
}),
146+
..
147+
}) => return Ok(Some(lit.value())),
148+
_ => {}
149+
}
150+
bail_span!(
151+
inner.meta_ident,
152+
"#[pystructseq({KEY}=value)] expects a string value"
153+
)
154+
} else {
155+
Ok(None)
156+
}
157+
}
158+
}
159+
160+
/// New attribute macro: #[pystructseq(name = "...", module = "...")]
161+
pub(crate) fn impl_pystructseq(attr: PunctuatedNestedMeta, item: Item) -> Result<TokenStream> {
162+
let Item::Struct(mut struct_item) = item else {
163+
bail_span!(item, "#[pystructseq] can only be applied to a struct");
164+
};
165+
166+
let data_ident = struct_item.ident.clone();
167+
let fake_ident = Ident::new("pystructseq", data_ident.span());
168+
let meta = PyStructSeqMeta::from_nested(data_ident.clone(), fake_ident, attr.into_iter())?;
169+
170+
let class_name = meta.class_name()?;
171+
let module_name = meta.module()?;
172+
let pytype_ident = derive_pytype_name(&data_ident);
173+
174+
// Parse fields from struct
175+
let mut derive_input: DeriveInput = syn::parse_quote!(#struct_item);
176+
let (not_skipped_fields, skipped_fields) = field_names(&mut derive_input)?;
177+
178+
// Rebuild struct_item from modified derive_input (attributes removed from fields)
179+
if let syn::Data::Struct(s) = derive_input.data {
180+
struct_item.fields = s.fields;
181+
}
182+
183+
// Generate field index constants
184+
let field_indices: Vec<_> = not_skipped_fields
185+
.iter()
186+
.enumerate()
187+
.map(|(i, field)| {
188+
let const_name = format_ident!("{}_INDEX", field.to_string().to_uppercase());
189+
quote! {
190+
pub const #const_name: usize = #i;
191+
}
192+
})
193+
.collect();
194+
195+
// Module name handling
196+
let module_name_tokens = match &module_name {
197+
Some(m) => quote!(Some(#m)),
198+
None => quote!(None),
199+
};
200+
201+
let module_class_name = if let Some(ref m) = module_name {
202+
format!("{}.{}", m, class_name)
203+
} else {
204+
class_name.clone()
205+
};
206+
207+
// Generate the output
208+
let output = quote! {
209+
// Original Data struct (with #[pystruct] attrs removed from fields)
210+
#struct_item
211+
212+
// Field index constants
213+
impl #data_ident {
214+
#(#field_indices)*
215+
}
216+
217+
// PyStructSequenceData trait implementation
218+
impl ::rustpython_vm::types::PyStructSequenceData for #data_ident {
219+
type PyType = #pytype_ident;
220+
221+
const REQUIRED_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#not_skipped_fields),)*];
222+
const OPTIONAL_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#skipped_fields),)*];
223+
224+
fn into_tuple(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::builtins::PyTuple {
225+
let items = vec![
226+
#(::rustpython_vm::convert::ToPyObject::to_pyobject(
227+
self.#not_skipped_fields,
228+
vm,
229+
),)*
230+
];
231+
::rustpython_vm::builtins::PyTuple::new_unchecked(items.into_boxed_slice())
232+
}
233+
}
234+
235+
// ToPyObject for Data struct
236+
impl ::rustpython_vm::convert::ToPyObject for #data_ident {
237+
fn to_pyobject(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::PyObjectRef {
238+
::rustpython_vm::types::PyStructSequenceData::into_struct_sequence(self, vm).into()
239+
}
240+
}
241+
242+
// TryFromObject for Data struct
243+
impl ::rustpython_vm::TryFromObject for #data_ident {
244+
fn try_from_object(vm: &::rustpython_vm::VirtualMachine, seq: ::rustpython_vm::PyObjectRef) -> ::rustpython_vm::PyResult<Self> {
245+
let seq = <#pytype_ident as ::rustpython_vm::types::PyStructSequence>::try_elements_from(seq, vm)?;
246+
let mut iter = seq.into_iter();
247+
Ok(Self {
248+
#(#not_skipped_fields: iter.next().unwrap().clone().try_into_value(vm)?,)*
249+
#(#skipped_fields: match iter.next() {
250+
Some(v) => v.clone().try_into_value(vm)?,
251+
None => vm.ctx.none(),
252+
},)*
253+
})
254+
}
255+
}
256+
257+
// Empty Python type struct
258+
#[doc(hidden)]
259+
pub struct #pytype_ident;
260+
261+
// PyClassDef for Python type
262+
impl ::rustpython_vm::class::PyClassDef for #pytype_ident {
263+
const NAME: &'static str = #class_name;
264+
const MODULE_NAME: Option<&'static str> = #module_name_tokens;
265+
const TP_NAME: &'static str = #module_class_name;
266+
const DOC: Option<&'static str> = None;
267+
const BASICSIZE: usize = 0;
268+
const UNHASHABLE: bool = false;
269+
270+
type Base = ::rustpython_vm::builtins::PyTuple;
271+
}
272+
273+
// StaticType for Python type
274+
impl ::rustpython_vm::class::StaticType for #pytype_ident {
275+
fn static_cell() -> &'static ::rustpython_vm::common::static_cell::StaticCell<::rustpython_vm::builtins::PyTypeRef> {
276+
::rustpython_vm::common::static_cell! {
277+
static CELL: ::rustpython_vm::builtins::PyTypeRef;
278+
}
279+
&CELL
280+
}
281+
282+
fn static_baseclass() -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> {
283+
use ::rustpython_vm::class::StaticType;
284+
::rustpython_vm::builtins::PyTuple::static_type()
285+
}
286+
}
287+
288+
// MaybeTraverse (empty - no GC fields in empty struct)
289+
impl ::rustpython_vm::object::MaybeTraverse for #pytype_ident {
290+
fn try_traverse(&self, _traverse_fn: &mut ::rustpython_vm::object::TraverseFn<'_>) {
291+
// Empty struct has no fields to traverse
292+
}
293+
}
294+
295+
// PyStructSequence trait for Python type
296+
impl ::rustpython_vm::types::PyStructSequence for #pytype_ident {
297+
type Data = #data_ident;
298+
}
299+
};
300+
301+
Ok(output)
302+
}
303+
304+
// Legacy derive macros (kept for backward compatibility during migration)
305+
// These use PyStructSequenceLegacy trait
306+
82307
pub(crate) fn impl_pystruct_sequence(mut input: DeriveInput) -> Result<TokenStream> {
83308
let (not_skipped_fields, skipped_fields) = field_names(&mut input)?;
84309
let ty = &input.ident;
85310
let ret = quote! {
86-
impl ::rustpython_vm::types::PyStructSequence for #ty {
311+
impl ::rustpython_vm::types::PyStructSequenceLegacy for #ty {
87312
const REQUIRED_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#not_skipped_fields),)*];
88313
const OPTIONAL_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#skipped_fields),)*];
89314
fn into_tuple(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::builtins::PyTuple {
@@ -98,7 +323,7 @@ pub(crate) fn impl_pystruct_sequence(mut input: DeriveInput) -> Result<TokenStre
98323
}
99324
impl ::rustpython_vm::convert::ToPyObject for #ty {
100325
fn to_pyobject(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::PyObjectRef {
101-
::rustpython_vm::types::PyStructSequence::into_struct_sequence(self, vm).into()
326+
::rustpython_vm::types::PyStructSequenceLegacy::into_struct_sequence(self, vm).into()
102327
}
103328
}
104329
};
@@ -113,7 +338,7 @@ pub(crate) fn impl_pystruct_sequence_try_from_object(
113338
let ret = quote! {
114339
impl ::rustpython_vm::TryFromObject for #ty {
115340
fn try_from_object(vm: &::rustpython_vm::VirtualMachine, seq: ::rustpython_vm::PyObjectRef) -> ::rustpython_vm::PyResult<Self> {
116-
let seq = Self::try_elements_from(seq, vm)?;
341+
let seq = <Self as ::rustpython_vm::types::PyStructSequenceLegacy>::try_elements_from(seq, vm)?;
117342
let mut iter = seq.into_iter();
118343
Ok(Self {
119344
#(#not_skipped_fields: iter.next().unwrap().clone().try_into_value(vm)?,)*

crates/derive/src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,31 @@ pub fn pystruct_sequence_try_from_object(input: TokenStream) -> TokenStream {
226226
derive_impl::pystruct_sequence_try_from_object(input).into()
227227
}
228228

229+
/// Attribute macro for defining Python struct sequences.
230+
///
231+
/// This macro creates both a Data struct and a Python type struct from a single definition.
232+
///
233+
/// # Example
234+
/// ```ignore
235+
/// #[pystructseq(name = "struct_time")]
236+
/// pub struct StructTimeData {
237+
/// pub tm_year: PyObjectRef,
238+
/// pub tm_mon: PyObjectRef,
239+
/// #[pystruct(skip)] // optional field
240+
/// pub tm_gmtoff: PyObjectRef,
241+
/// }
242+
/// // This generates:
243+
/// // - StructTimeData with PyStructSequenceData trait
244+
/// // - PyStructTime (empty struct) with PyStructSequence trait
245+
/// // - Field index constants (TM_YEAR_INDEX, etc.)
246+
/// ```
247+
#[proc_macro_attribute]
248+
pub fn pystructseq(attr: TokenStream, item: TokenStream) -> TokenStream {
249+
let attr = parse_macro_input!(attr with Punctuated::parse_terminated);
250+
let item = parse_macro_input!(item);
251+
derive_impl::pystructseq(attr, item).into()
252+
}
253+
229254
struct Compiler;
230255
impl derive_impl::Compiler for Compiler {
231256
fn compile(

crates/stdlib/src/grp.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ mod grp {
88
builtins::{PyIntRef, PyListRef, PyStrRef},
99
convert::{IntoPyException, ToPyObject},
1010
exceptions,
11-
types::PyStructSequence,
11+
types::PyStructSequenceLegacy,
1212
};
1313
use nix::unistd;
1414
use std::ptr::NonNull;
@@ -25,7 +25,7 @@ mod grp {
2525
gr_gid: u32,
2626
gr_mem: PyListRef,
2727
}
28-
#[pyclass(with(PyStructSequence))]
28+
#[pyclass(with(PyStructSequenceLegacy))]
2929
impl Group {
3030
fn from_unistd_group(group: unistd::Group, vm: &VirtualMachine) -> Self {
3131
let cstr_lossy = |s: std::ffi::CString| {

0 commit comments

Comments
 (0)