Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -2943,8 +2943,6 @@ def newmeth(self, start):
self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
dt1.second - 7)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_subclass_alternate_constructors_datetime(self):
# Test that alternate constructors call the constructor
class DateTimeSubclass(self.theclass):
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -4748,7 +4748,6 @@ def test_defaults_parameter(self):
def test_invalid_style(self):
self.assertRaises(ValueError, logging.Formatter, None, None, 'x')

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

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'struct_time' object has no attribute 'tm_gmtoff'
def test_default_msec_format_none(self):
class NoMsecFormatter(logging.Formatter):
default_msec_format = None
Expand Down
4 changes: 0 additions & 4 deletions Lib/test/test_structseq.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ def test_cmp(self):
self.assertTrue(t1 >= t2)
self.assertTrue(not (t1 != t2))

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_fields(self):
t = time.gmtime()
self.assertEqual(len(t), t.n_sequence_fields)
Expand Down Expand Up @@ -129,8 +127,6 @@ def test_match_args(self):
'tm_sec', 'tm_wday', 'tm_yday', 'tm_isdst')
self.assertEqual(time.struct_time.__match_args__, expected_args)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_match_args_with_unnamed_fields(self):
expected_args = ('st_mode', 'st_ino', 'st_dev', 'st_nlink', 'st_uid',
'st_gid', 'st_size')
Expand Down
8 changes: 4 additions & 4 deletions crates/derive-impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ pub fn pymodule(attr: PunctuatedNestedMeta, item: Item) -> TokenStream {
result_to_tokens(pymodule::impl_pymodule(attr, item))
}

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

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

pub fn py_compile(input: TokenStream, compiler: &dyn Compiler) -> TokenStream {
Expand Down
107 changes: 105 additions & 2 deletions crates/derive-impl/src/pymodule.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::error::Diagnostic;
use crate::pystructseq::PyStructSequenceMeta;
use crate::util::{
ALL_ALLOWED_NAMES, AttrItemMeta, AttributeExt, ClassItemMeta, ContentItem, ContentItemInner,
ErrorVec, ItemMeta, ItemNursery, ModuleItemMeta, SimpleItemMeta, format_doc, iter_use_idents,
Expand All @@ -18,6 +19,7 @@ enum AttrName {
Attr,
Class,
Exception,
StructSequence,
}

impl std::fmt::Display for AttrName {
Expand All @@ -27,6 +29,7 @@ impl std::fmt::Display for AttrName {
Self::Attr => "pyattr",
Self::Class => "pyclass",
Self::Exception => "pyexception",
Self::StructSequence => "pystruct_sequence",
};
s.fmt(f)
}
Expand All @@ -41,6 +44,7 @@ impl FromStr for AttrName {
"pyattr" => Self::Attr,
"pyclass" => Self::Class,
"pyexception" => Self::Exception,
"pystruct_sequence" => Self::StructSequence,
s => {
return Err(s.to_owned());
}
Expand Down Expand Up @@ -235,6 +239,10 @@ fn module_item_new(
inner: ContentItemInner { index, attr_name },
py_attrs,
}),
AttrName::StructSequence => Box::new(StructSequenceItem {
inner: ContentItemInner { index, attr_name },
py_attrs,
}),
}
}

Expand Down Expand Up @@ -301,13 +309,16 @@ where
result.push(item_new(i, attr_name, Vec::new()));
} else {
match attr_name {
AttrName::Class | AttrName::Function | AttrName::Exception => {
AttrName::Class
| AttrName::Function
| AttrName::Exception
| AttrName::StructSequence => {
result.push(item_new(i, attr_name, py_attrs.clone()));
}
_ => {
bail_span!(
attr,
"#[pyclass], #[pyfunction], or #[pyexception] can follow #[pyattr]",
"#[pyclass], #[pyfunction], #[pyexception], or #[pystruct_sequence] can follow #[pyattr]",
)
}
}
Expand Down Expand Up @@ -402,6 +413,12 @@ struct AttributeItem {
py_attrs: Vec<usize>,
}

/// #[pystruct_sequence]
struct StructSequenceItem {
inner: ContentItemInner<AttrName>,
py_attrs: Vec<usize>,
}

impl ContentItem for FunctionItem {
type AttrName = AttrName;

Expand All @@ -426,6 +443,14 @@ impl ContentItem for AttributeItem {
}
}

impl ContentItem for StructSequenceItem {
type AttrName = AttrName;

fn inner(&self) -> &ContentItemInner<AttrName> {
&self.inner
}
}

struct ModuleItemArgs<'a> {
item: &'a mut Item,
attrs: &'a mut Vec<Attribute>,
Expand Down Expand Up @@ -602,6 +627,84 @@ impl ModuleItem for ClassItem {
}
}

impl ModuleItem for StructSequenceItem {
fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> {
// Get the struct identifier (this IS the Python type, e.g., PyStructTime)
let pytype_ident = match args.item {
Item::Struct(s) => s.ident.clone(),
other => bail_span!(other, "#[pystruct_sequence] can only be on a struct"),
};

// Parse the #[pystruct_sequence(name = "...", module = "...", no_attr)] attribute
let structseq_attr = &args.attrs[self.inner.index];
let meta = PyStructSequenceMeta::from_attr(pytype_ident.clone(), structseq_attr)?;

let class_name = meta.class_name()?.ok_or_else(|| {
syn::Error::new_spanned(
structseq_attr,
"#[pystruct_sequence] requires name parameter",
)
})?;
let module_name = meta.module()?.unwrap_or_else(|| args.context.name.clone());
let no_attr = meta.no_attr()?;

// Generate the class creation code
let class_new = quote_spanned!(pytype_ident.span() =>
let new_class = <#pytype_ident as ::rustpython_vm::class::PyClassImpl>::make_class(ctx);
new_class.set_attr(rustpython_vm::identifier!(ctx, __module__), vm.new_pyobj(#module_name));
);

// Handle py_attrs for custom names, or use class_name as default
let mut py_names = Vec::new();
for attr_index in self.py_attrs.iter().rev() {
let attr_attr = args.attrs.remove(*attr_index);
let item_meta = SimpleItemMeta::from_attr(pytype_ident.clone(), &attr_attr)?;
let py_name = item_meta
.optional_name()
.unwrap_or_else(|| class_name.clone());
py_names.push(py_name);
}

// Require explicit #[pyattr] or no_attr, like #[pyclass]
if self.py_attrs.is_empty() && !no_attr {
bail_span!(
pytype_ident,
"#[pystruct_sequence] requires #[pyattr] to be a module attribute. \
To keep it free type, try #[pystruct_sequence(..., no_attr)]"
)
}

let set_attr = match py_names.len() {
0 => quote! {
let _ = new_class; // suppress warning
},
1 => {
let py_name = &py_names[0];
quote! {
vm.__module_set_attr(&module, vm.ctx.intern_str(#py_name), new_class).unwrap();
}
}
_ => quote! {
for name in [#(#py_names,)*] {
vm.__module_set_attr(&module, vm.ctx.intern_str(name), new_class.clone()).unwrap();
}
},
};

args.context.attribute_items.add_item(
pytype_ident.clone(),
py_names,
args.cfgs.to_vec(),
quote_spanned! { pytype_ident.span() =>
#class_new
#set_attr
},
0,
)?;
Ok(())
}
}

impl ModuleItem for AttributeItem {
fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> {
let cfgs = args.cfgs.to_vec();
Expand Down
Loading
Loading