Skip to content

Commit c6f53fb

Browse files
committed
PyStructSequence
1 parent 5ad536e commit c6f53fb

File tree

19 files changed

+1136
-280
lines changed

19 files changed

+1136
-280
lines changed

Lib/test/datetimetester.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2943,8 +2943,6 @@ def newmeth(self, start):
29432943
self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
29442944
dt1.second - 7)
29452945

2946-
# TODO: RUSTPYTHON
2947-
@unittest.expectedFailure
29482946
def test_subclass_alternate_constructors_datetime(self):
29492947
# Test that alternate constructors call the constructor
29502948
class DateTimeSubclass(self.theclass):

Lib/test/test_logging.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4748,7 +4748,6 @@ def test_defaults_parameter(self):
47484748
def test_invalid_style(self):
47494749
self.assertRaises(ValueError, logging.Formatter, None, None, 'x')
47504750

4751-
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'struct_time' object has no attribute 'tm_gmtoff'
47524751
def test_time(self):
47534752
r = self.get_record()
47544753
dt = datetime.datetime(1993, 4, 21, 8, 3, 0, 0, utc)
@@ -4763,7 +4762,6 @@ def test_time(self):
47634762
f.format(r)
47644763
self.assertEqual(r.asctime, '1993-04-21 08:03:00,123')
47654764

4766-
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'struct_time' object has no attribute 'tm_gmtoff'
47674765
def test_default_msec_format_none(self):
47684766
class NoMsecFormatter(logging.Formatter):
47694767
default_msec_format = None

Lib/test/test_structseq.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,6 @@ def test_cmp(self):
7575
self.assertTrue(t1 >= t2)
7676
self.assertTrue(not (t1 != t2))
7777

78-
# TODO: RUSTPYTHON
79-
@unittest.expectedFailure
8078
def test_fields(self):
8179
t = time.gmtime()
8280
self.assertEqual(len(t), t.n_sequence_fields)
@@ -129,8 +127,6 @@ def test_match_args(self):
129127
'tm_sec', 'tm_wday', 'tm_yday', 'tm_isdst')
130128
self.assertEqual(time.struct_time.__match_args__, expected_args)
131129

132-
# TODO: RUSTPYTHON
133-
@unittest.expectedFailure
134130
def test_match_args_with_unnamed_fields(self):
135131
expected_args = ('st_mode', 'st_ino', 'st_dev', 'st_nlink', 'st_uid',
136132
'st_gid', 'st_size')

crates/derive-impl/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,12 @@ pub fn pymodule(attr: PunctuatedNestedMeta, item: Item) -> TokenStream {
5858
result_to_tokens(pymodule::impl_pymodule(attr, item))
5959
}
6060

61-
pub fn pystruct_sequence(input: DeriveInput) -> TokenStream {
62-
result_to_tokens(pystructseq::impl_pystruct_sequence(input))
61+
pub fn pystruct_sequence(attr: PunctuatedNestedMeta, item: Item) -> TokenStream {
62+
result_to_tokens(pystructseq::impl_pystruct_sequence(attr, item))
6363
}
6464

65-
pub fn pystruct_sequence_try_from_object(input: DeriveInput) -> TokenStream {
66-
result_to_tokens(pystructseq::impl_pystruct_sequence_try_from_object(input))
65+
pub fn pystruct_sequence_data(attr: PunctuatedNestedMeta, item: Item) -> TokenStream {
66+
result_to_tokens(pystructseq::impl_pystruct_sequence_data(attr, item))
6767
}
6868

6969
pub fn py_compile(input: TokenStream, compiler: &dyn Compiler) -> TokenStream {

crates/derive-impl/src/pymodule.rs

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::error::Diagnostic;
2+
use crate::pystructseq::PyStructSequenceMeta;
23
use crate::util::{
34
ALL_ALLOWED_NAMES, AttrItemMeta, AttributeExt, ClassItemMeta, ContentItem, ContentItemInner,
45
ErrorVec, ItemMeta, ItemNursery, ModuleItemMeta, SimpleItemMeta, format_doc, iter_use_idents,
@@ -18,6 +19,7 @@ enum AttrName {
1819
Attr,
1920
Class,
2021
Exception,
22+
StructSequence,
2123
}
2224

2325
impl std::fmt::Display for AttrName {
@@ -27,6 +29,7 @@ impl std::fmt::Display for AttrName {
2729
Self::Attr => "pyattr",
2830
Self::Class => "pyclass",
2931
Self::Exception => "pyexception",
32+
Self::StructSequence => "pystruct_sequence",
3033
};
3134
s.fmt(f)
3235
}
@@ -41,6 +44,7 @@ impl FromStr for AttrName {
4144
"pyattr" => Self::Attr,
4245
"pyclass" => Self::Class,
4346
"pyexception" => Self::Exception,
47+
"pystruct_sequence" => Self::StructSequence,
4448
s => {
4549
return Err(s.to_owned());
4650
}
@@ -235,6 +239,10 @@ fn module_item_new(
235239
inner: ContentItemInner { index, attr_name },
236240
py_attrs,
237241
}),
242+
AttrName::StructSequence => Box::new(StructSequenceItem {
243+
inner: ContentItemInner { index, attr_name },
244+
py_attrs,
245+
}),
238246
}
239247
}
240248

@@ -301,13 +309,16 @@ where
301309
result.push(item_new(i, attr_name, Vec::new()));
302310
} else {
303311
match attr_name {
304-
AttrName::Class | AttrName::Function | AttrName::Exception => {
312+
AttrName::Class
313+
| AttrName::Function
314+
| AttrName::Exception
315+
| AttrName::StructSequence => {
305316
result.push(item_new(i, attr_name, py_attrs.clone()));
306317
}
307318
_ => {
308319
bail_span!(
309320
attr,
310-
"#[pyclass], #[pyfunction], or #[pyexception] can follow #[pyattr]",
321+
"#[pyclass], #[pyfunction], #[pyexception], or #[pystruct_sequence] can follow #[pyattr]",
311322
)
312323
}
313324
}
@@ -402,6 +413,12 @@ struct AttributeItem {
402413
py_attrs: Vec<usize>,
403414
}
404415

416+
/// #[pystruct_sequence]
417+
struct StructSequenceItem {
418+
inner: ContentItemInner<AttrName>,
419+
py_attrs: Vec<usize>,
420+
}
421+
405422
impl ContentItem for FunctionItem {
406423
type AttrName = AttrName;
407424

@@ -426,6 +443,14 @@ impl ContentItem for AttributeItem {
426443
}
427444
}
428445

446+
impl ContentItem for StructSequenceItem {
447+
type AttrName = AttrName;
448+
449+
fn inner(&self) -> &ContentItemInner<AttrName> {
450+
&self.inner
451+
}
452+
}
453+
429454
struct ModuleItemArgs<'a> {
430455
item: &'a mut Item,
431456
attrs: &'a mut Vec<Attribute>,
@@ -602,6 +627,84 @@ impl ModuleItem for ClassItem {
602627
}
603628
}
604629

630+
impl ModuleItem for StructSequenceItem {
631+
fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> {
632+
// Get the struct identifier (this IS the Python type, e.g., PyStructTime)
633+
let pytype_ident = match args.item {
634+
Item::Struct(s) => s.ident.clone(),
635+
other => bail_span!(other, "#[pystruct_sequence] can only be on a struct"),
636+
};
637+
638+
// Parse the #[pystruct_sequence(name = "...", module = "...", no_attr)] attribute
639+
let structseq_attr = &args.attrs[self.inner.index];
640+
let meta = PyStructSequenceMeta::from_attr(pytype_ident.clone(), structseq_attr)?;
641+
642+
let class_name = meta.class_name()?.ok_or_else(|| {
643+
syn::Error::new_spanned(
644+
structseq_attr,
645+
"#[pystruct_sequence] requires name parameter",
646+
)
647+
})?;
648+
let module_name = meta.module()?.unwrap_or_else(|| args.context.name.clone());
649+
let no_attr = meta.no_attr()?;
650+
651+
// Generate the class creation code
652+
let class_new = quote_spanned!(pytype_ident.span() =>
653+
let new_class = <#pytype_ident as ::rustpython_vm::class::PyClassImpl>::make_class(ctx);
654+
new_class.set_attr(rustpython_vm::identifier!(ctx, __module__), vm.new_pyobj(#module_name));
655+
);
656+
657+
// Handle py_attrs for custom names, or use class_name as default
658+
let mut py_names = Vec::new();
659+
for attr_index in self.py_attrs.iter().rev() {
660+
let attr_attr = args.attrs.remove(*attr_index);
661+
let item_meta = SimpleItemMeta::from_attr(pytype_ident.clone(), &attr_attr)?;
662+
let py_name = item_meta
663+
.optional_name()
664+
.unwrap_or_else(|| class_name.clone());
665+
py_names.push(py_name);
666+
}
667+
668+
// Require explicit #[pyattr] or no_attr, like #[pyclass]
669+
if self.py_attrs.is_empty() && !no_attr {
670+
bail_span!(
671+
pytype_ident,
672+
"#[pystruct_sequence] requires #[pyattr] to be a module attribute. \
673+
To keep it free type, try #[pystruct_sequence(..., no_attr)]"
674+
)
675+
}
676+
677+
let set_attr = match py_names.len() {
678+
0 => quote! {
679+
let _ = new_class; // suppress warning
680+
},
681+
1 => {
682+
let py_name = &py_names[0];
683+
quote! {
684+
vm.__module_set_attr(&module, vm.ctx.intern_str(#py_name), new_class).unwrap();
685+
}
686+
}
687+
_ => quote! {
688+
for name in [#(#py_names,)*] {
689+
vm.__module_set_attr(&module, vm.ctx.intern_str(name), new_class.clone()).unwrap();
690+
}
691+
},
692+
};
693+
694+
args.context.attribute_items.add_item(
695+
pytype_ident.clone(),
696+
py_names,
697+
args.cfgs.to_vec(),
698+
quote_spanned! { pytype_ident.span() =>
699+
#class_new
700+
#set_attr
701+
},
702+
0,
703+
)?;
704+
Ok(())
705+
}
706+
}
707+
605708
impl ModuleItem for AttributeItem {
606709
fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> {
607710
let cfgs = args.cfgs.to_vec();

0 commit comments

Comments
 (0)