mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
PyStructSequence Compatibility (#6327)
* Fix local time * PyStructSequence
This commit is contained in:
1 parent
6f786b58ad
commit
c826f9d809
19 files changed
+1156
-293
No files matched your search
Vendored
-2
@@ -2943,8 +2943,6 @@ class TestDateTime(TestDate):
|
||||
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):
|
||||
|
||||
Vendored
-2
@@ -4748,7 +4748,6 @@ class FormatterTest(unittest.TestCase, AssertErrorMessage):
|
||||
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)
|
||||
@@ -4763,7 +4762,6 @@ class FormatterTest(unittest.TestCase, AssertErrorMessage):
|
||||
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
|
||||
|
||||
Vendored
-4
@@ -75,8 +75,6 @@ class StructSeqTest(unittest.TestCase):
|
||||
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)
|
||||
@@ -129,8 +127,6 @@ class StructSeqTest(unittest.TestCase):
|
||||
'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')
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
@@ -18,6 +19,7 @@ enum AttrName {
|
||||
Attr,
|
||||
Class,
|
||||
Exception,
|
||||
StructSequence,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AttrName {
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>,
|
||||
@@ -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();
|
||||
|
||||
@@ -1,34 +1,90 @@
|
||||
use crate::util::{ItemMeta, ItemMetaInner};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{DeriveInput, Ident, Result};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{DeriveInput, Ident, Item, Result};
|
||||
use syn_ext::ext::{AttributeExt, GetIdent};
|
||||
use syn_ext::types::Meta;
|
||||
use syn_ext::types::{Meta, PunctuatedNestedMeta};
|
||||
|
||||
// returning a pair of not-skipped and skipped field names
|
||||
fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> {
|
||||
// #[pystruct_sequence_data] - For Data structs
|
||||
|
||||
/// Field kind for struct sequence
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum FieldKind {
|
||||
/// Named visible field (has getter, shown in repr)
|
||||
Named,
|
||||
/// Unnamed visible field (index-only, no getter)
|
||||
Unnamed,
|
||||
/// Hidden/skipped field (stored in tuple, but hidden from repr/len/index)
|
||||
Skipped,
|
||||
}
|
||||
|
||||
/// Parsed field with its kind
|
||||
struct ParsedField {
|
||||
ident: Ident,
|
||||
kind: FieldKind,
|
||||
}
|
||||
|
||||
/// Parsed field info from struct
|
||||
struct FieldInfo {
|
||||
/// All fields in order with their kinds
|
||||
fields: Vec<ParsedField>,
|
||||
}
|
||||
|
||||
impl FieldInfo {
|
||||
fn named_fields(&self) -> Vec<Ident> {
|
||||
self.fields
|
||||
.iter()
|
||||
.filter(|f| f.kind == FieldKind::Named)
|
||||
.map(|f| f.ident.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn visible_fields(&self) -> Vec<Ident> {
|
||||
self.fields
|
||||
.iter()
|
||||
.filter(|f| f.kind != FieldKind::Skipped)
|
||||
.map(|f| f.ident.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn skipped_fields(&self) -> Vec<Ident> {
|
||||
self.fields
|
||||
.iter()
|
||||
.filter(|f| f.kind == FieldKind::Skipped)
|
||||
.map(|f| f.ident.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn n_unnamed_fields(&self) -> usize {
|
||||
self.fields
|
||||
.iter()
|
||||
.filter(|f| f.kind == FieldKind::Unnamed)
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse field info from struct
|
||||
fn parse_fields(input: &mut DeriveInput) -> Result<FieldInfo> {
|
||||
let syn::Data::Struct(struc) = &mut input.data else {
|
||||
bail_span!(
|
||||
input,
|
||||
"#[pystruct_sequence] can only be on a struct declaration"
|
||||
)
|
||||
bail_span!(input, "#[pystruct_sequence_data] can only be on a struct")
|
||||
};
|
||||
|
||||
let syn::Fields::Named(fields) = &mut struc.fields else {
|
||||
bail_span!(
|
||||
input,
|
||||
"#[pystruct_sequence] can only be on a struct with named fields"
|
||||
"#[pystruct_sequence_data] can only be on a struct with named fields"
|
||||
);
|
||||
};
|
||||
|
||||
let mut not_skipped = Vec::with_capacity(fields.named.len());
|
||||
let mut skipped = Vec::with_capacity(fields.named.len());
|
||||
let mut parsed_fields = Vec::with_capacity(fields.named.len());
|
||||
|
||||
for field in &mut fields.named {
|
||||
let mut skip = false;
|
||||
// Collect all attributes with pystruct and their indices
|
||||
let mut unnamed = false;
|
||||
let mut attrs_to_remove = Vec::new();
|
||||
|
||||
for (i, attr) in field.attrs.iter().enumerate() {
|
||||
if !attr.path().is_ident("pystruct") {
|
||||
if !attr.path().is_ident("pystruct_sequence") {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -37,7 +93,7 @@ fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> {
|
||||
};
|
||||
|
||||
let Meta::List(l) = meta else {
|
||||
bail_span!(input, "Only #[pystruct(...)] form is allowed");
|
||||
bail_span!(input, "Only #[pystruct_sequence(...)] form is allowed");
|
||||
};
|
||||
|
||||
let idents: Vec<_> = l
|
||||
@@ -47,15 +103,16 @@ fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> {
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// Follow #[serde(skip)] convention.
|
||||
// Consider to add skip_serializing and skip_deserializing if required.
|
||||
for ident in idents {
|
||||
match ident.to_string().as_str() {
|
||||
"skip" => {
|
||||
skip = true;
|
||||
}
|
||||
"unnamed" => {
|
||||
unnamed = true;
|
||||
}
|
||||
_ => {
|
||||
bail_span!(ident, "Unknown item for #[pystruct(...)]")
|
||||
bail_span!(ident, "Unknown item for #[pystruct_sequence(...)]")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,60 +120,127 @@ fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> {
|
||||
attrs_to_remove.push(i);
|
||||
}
|
||||
|
||||
// Remove attributes in reverse order to maintain valid indices
|
||||
attrs_to_remove.sort_unstable_by(|a, b| b.cmp(a)); // Sort in descending order
|
||||
// Remove attributes in reverse order
|
||||
attrs_to_remove.sort_unstable_by(|a, b| b.cmp(a));
|
||||
for index in attrs_to_remove {
|
||||
field.attrs.remove(index);
|
||||
}
|
||||
|
||||
let ident = field.ident.clone().unwrap();
|
||||
if skip {
|
||||
skipped.push(ident.clone());
|
||||
let kind = if skip {
|
||||
FieldKind::Skipped
|
||||
} else if unnamed {
|
||||
FieldKind::Unnamed
|
||||
} else {
|
||||
not_skipped.push(ident.clone());
|
||||
}
|
||||
FieldKind::Named
|
||||
};
|
||||
|
||||
parsed_fields.push(ParsedField { ident, kind });
|
||||
}
|
||||
|
||||
Ok((not_skipped, skipped))
|
||||
Ok(FieldInfo {
|
||||
fields: parsed_fields,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn impl_pystruct_sequence(mut input: DeriveInput) -> Result<TokenStream> {
|
||||
let (not_skipped_fields, skipped_fields) = field_names(&mut input)?;
|
||||
let ty = &input.ident;
|
||||
let ret = quote! {
|
||||
impl ::rustpython_vm::types::PyStructSequence for #ty {
|
||||
const REQUIRED_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#not_skipped_fields),)*];
|
||||
const OPTIONAL_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#skipped_fields),)*];
|
||||
fn into_tuple(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::builtins::PyTuple {
|
||||
let items = vec![
|
||||
#(::rustpython_vm::convert::ToPyObject::to_pyobject(
|
||||
self.#not_skipped_fields,
|
||||
vm,
|
||||
),)*
|
||||
];
|
||||
::rustpython_vm::builtins::PyTuple::new_unchecked(items.into_boxed_slice())
|
||||
}
|
||||
}
|
||||
impl ::rustpython_vm::convert::ToPyObject for #ty {
|
||||
fn to_pyobject(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::PyObjectRef {
|
||||
::rustpython_vm::types::PyStructSequence::into_struct_sequence(self, vm).into()
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(ret)
|
||||
/// Check if `try_from_object` is present in attribute arguments
|
||||
fn has_try_from_object(attr: &PunctuatedNestedMeta) -> bool {
|
||||
attr.iter().any(|nested| {
|
||||
nested
|
||||
.get_ident()
|
||||
.is_some_and(|ident| ident == "try_from_object")
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn impl_pystruct_sequence_try_from_object(
|
||||
mut input: DeriveInput,
|
||||
/// Attribute macro for Data structs: #[pystruct_sequence_data(...)]
|
||||
///
|
||||
/// Generates:
|
||||
/// - `REQUIRED_FIELD_NAMES` constant (named visible fields)
|
||||
/// - `OPTIONAL_FIELD_NAMES` constant (hidden/skipped fields)
|
||||
/// - `UNNAMED_FIELDS_LEN` constant
|
||||
/// - `into_tuple()` method
|
||||
/// - Field index constants (e.g., `TM_YEAR_INDEX`)
|
||||
///
|
||||
/// Options:
|
||||
/// - `try_from_object`: Generate `try_from_elements()` method and `TryFromObject` impl
|
||||
pub(crate) fn impl_pystruct_sequence_data(
|
||||
attr: PunctuatedNestedMeta,
|
||||
item: Item,
|
||||
) -> Result<TokenStream> {
|
||||
let (not_skipped_fields, skipped_fields) = field_names(&mut input)?;
|
||||
let ty = &input.ident;
|
||||
let ret = quote! {
|
||||
impl ::rustpython_vm::TryFromObject for #ty {
|
||||
fn try_from_object(vm: &::rustpython_vm::VirtualMachine, seq: ::rustpython_vm::PyObjectRef) -> ::rustpython_vm::PyResult<Self> {
|
||||
let seq = Self::try_elements_from(seq, vm)?;
|
||||
let mut iter = seq.into_iter();
|
||||
let Item::Struct(item_struct) = item else {
|
||||
bail_span!(
|
||||
item,
|
||||
"#[pystruct_sequence_data] can only be applied to structs"
|
||||
);
|
||||
};
|
||||
|
||||
let try_from_object = has_try_from_object(&attr);
|
||||
let mut input: DeriveInput = DeriveInput {
|
||||
attrs: item_struct.attrs.clone(),
|
||||
vis: item_struct.vis.clone(),
|
||||
ident: item_struct.ident.clone(),
|
||||
generics: item_struct.generics.clone(),
|
||||
data: syn::Data::Struct(syn::DataStruct {
|
||||
struct_token: item_struct.struct_token,
|
||||
fields: item_struct.fields.clone(),
|
||||
semi_token: item_struct.semi_token,
|
||||
}),
|
||||
};
|
||||
let field_info = parse_fields(&mut input)?;
|
||||
let data_ident = &input.ident;
|
||||
|
||||
let named_fields = field_info.named_fields();
|
||||
let visible_fields = field_info.visible_fields();
|
||||
let skipped_fields = field_info.skipped_fields();
|
||||
let n_unnamed_fields = field_info.n_unnamed_fields();
|
||||
|
||||
// Generate field index constants for visible fields
|
||||
let field_indices: Vec<_> = visible_fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| {
|
||||
let const_name = format_ident!("{}_INDEX", field.to_string().to_uppercase());
|
||||
quote! {
|
||||
pub const #const_name: usize = #i;
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Generate TryFromObject impl only when try_from_object=true
|
||||
let try_from_object_impl = if try_from_object {
|
||||
let n_required = visible_fields.len();
|
||||
quote! {
|
||||
impl ::rustpython_vm::TryFromObject for #data_ident {
|
||||
fn try_from_object(
|
||||
vm: &::rustpython_vm::VirtualMachine,
|
||||
obj: ::rustpython_vm::PyObjectRef,
|
||||
) -> ::rustpython_vm::PyResult<Self> {
|
||||
let seq: Vec<::rustpython_vm::PyObjectRef> = obj.try_into_value(vm)?;
|
||||
if seq.len() < #n_required {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{} requires at least {} elements",
|
||||
stringify!(#data_ident),
|
||||
#n_required
|
||||
)));
|
||||
}
|
||||
<Self as ::rustpython_vm::types::PyStructSequenceData>::try_from_elements(seq, vm)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
// Generate try_from_elements trait override only when try_from_object=true
|
||||
let try_from_elements_trait_override = if try_from_object {
|
||||
quote! {
|
||||
fn try_from_elements(
|
||||
elements: Vec<::rustpython_vm::PyObjectRef>,
|
||||
vm: &::rustpython_vm::VirtualMachine,
|
||||
) -> ::rustpython_vm::PyResult<Self> {
|
||||
let mut iter = elements.into_iter();
|
||||
Ok(Self {
|
||||
#(#not_skipped_fields: iter.next().unwrap().clone().try_into_value(vm)?,)*
|
||||
#(#visible_fields: iter.next().unwrap().clone().try_into_value(vm)?,)*
|
||||
#(#skipped_fields: match iter.next() {
|
||||
Some(v) => v.clone().try_into_value(vm)?,
|
||||
None => vm.ctx.none(),
|
||||
@@ -124,6 +248,253 @@ pub(crate) fn impl_pystruct_sequence_try_from_object(
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
Ok(ret)
|
||||
|
||||
let output = quote! {
|
||||
impl #data_ident {
|
||||
#(#field_indices)*
|
||||
}
|
||||
|
||||
// PyStructSequenceData trait impl
|
||||
impl ::rustpython_vm::types::PyStructSequenceData for #data_ident {
|
||||
const REQUIRED_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#named_fields),)*];
|
||||
const OPTIONAL_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#skipped_fields),)*];
|
||||
const UNNAMED_FIELDS_LEN: usize = #n_unnamed_fields;
|
||||
|
||||
fn into_tuple(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::builtins::PyTuple {
|
||||
let items = vec![
|
||||
#(::rustpython_vm::convert::ToPyObject::to_pyobject(
|
||||
self.#visible_fields,
|
||||
vm,
|
||||
),)*
|
||||
#(::rustpython_vm::convert::ToPyObject::to_pyobject(
|
||||
self.#skipped_fields,
|
||||
vm,
|
||||
),)*
|
||||
];
|
||||
::rustpython_vm::builtins::PyTuple::new_unchecked(items.into_boxed_slice())
|
||||
}
|
||||
|
||||
#try_from_elements_trait_override
|
||||
}
|
||||
|
||||
#try_from_object_impl
|
||||
};
|
||||
|
||||
// For attribute macro, we need to output the original struct as well
|
||||
// But first, strip #[pystruct_sequence] attributes from fields
|
||||
let mut clean_struct = item_struct.clone();
|
||||
if let syn::Fields::Named(ref mut fields) = clean_struct.fields {
|
||||
for field in &mut fields.named {
|
||||
field
|
||||
.attrs
|
||||
.retain(|attr| !attr.path().is_ident("pystruct_sequence"));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(quote! {
|
||||
#clean_struct
|
||||
#output
|
||||
})
|
||||
}
|
||||
|
||||
// #[pystruct_sequence(...)] - For Python type structs
|
||||
|
||||
/// Meta parser for #[pystruct_sequence(...)]
|
||||
pub(crate) struct PyStructSequenceMeta {
|
||||
inner: ItemMetaInner,
|
||||
}
|
||||
|
||||
impl ItemMeta for PyStructSequenceMeta {
|
||||
const ALLOWED_NAMES: &'static [&'static str] = &["name", "module", "data", "no_attr"];
|
||||
|
||||
fn from_inner(inner: ItemMetaInner) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
fn inner(&self) -> &ItemMetaInner {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl PyStructSequenceMeta {
|
||||
pub fn class_name(&self) -> Result<Option<String>> {
|
||||
const KEY: &str = "name";
|
||||
let inner = self.inner();
|
||||
if let Some((_, meta)) = inner.meta_map.get(KEY) {
|
||||
if let Meta::NameValue(syn::MetaNameValue {
|
||||
value:
|
||||
syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
}),
|
||||
..
|
||||
}) = meta
|
||||
{
|
||||
return Ok(Some(lit.value()));
|
||||
}
|
||||
bail_span!(
|
||||
inner.meta_ident,
|
||||
"#[pystruct_sequence({KEY}=value)] expects a string value"
|
||||
)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn module(&self) -> Result<Option<String>> {
|
||||
const KEY: &str = "module";
|
||||
let inner = self.inner();
|
||||
if let Some((_, meta)) = inner.meta_map.get(KEY) {
|
||||
if let Meta::NameValue(syn::MetaNameValue {
|
||||
value:
|
||||
syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
}),
|
||||
..
|
||||
}) = meta
|
||||
{
|
||||
return Ok(Some(lit.value()));
|
||||
}
|
||||
bail_span!(
|
||||
inner.meta_ident,
|
||||
"#[pystruct_sequence({KEY}=value)] expects a string value"
|
||||
)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn data_type(&self) -> Result<Ident> {
|
||||
const KEY: &str = "data";
|
||||
let inner = self.inner();
|
||||
if let Some((_, meta)) = inner.meta_map.get(KEY) {
|
||||
if let Meta::NameValue(syn::MetaNameValue {
|
||||
value:
|
||||
syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
}),
|
||||
..
|
||||
}) = meta
|
||||
{
|
||||
return Ok(format_ident!("{}", lit.value()));
|
||||
}
|
||||
bail_span!(
|
||||
inner.meta_ident,
|
||||
"#[pystruct_sequence({KEY}=value)] expects a string value"
|
||||
)
|
||||
} else {
|
||||
bail_span!(
|
||||
inner.meta_ident,
|
||||
"#[pystruct_sequence] requires data parameter (e.g., data = \"DataStructName\")"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_attr(&self) -> Result<bool> {
|
||||
self.inner()._bool("no_attr")
|
||||
}
|
||||
}
|
||||
|
||||
/// Attribute macro for struct sequences.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```ignore
|
||||
/// #[pystruct_sequence_data]
|
||||
/// struct StructTimeData { ... }
|
||||
///
|
||||
/// #[pystruct_sequence(name = "struct_time", module = "time", data = "StructTimeData")]
|
||||
/// struct PyStructTime;
|
||||
/// ```
|
||||
pub(crate) fn impl_pystruct_sequence(
|
||||
attr: PunctuatedNestedMeta,
|
||||
item: Item,
|
||||
) -> Result<TokenStream> {
|
||||
let Item::Struct(struct_item) = item else {
|
||||
bail_span!(item, "#[pystruct_sequence] can only be applied to a struct");
|
||||
};
|
||||
|
||||
let ident = struct_item.ident.clone();
|
||||
let fake_ident = Ident::new("pystruct_sequence", ident.span());
|
||||
let meta = PyStructSequenceMeta::from_nested(ident, fake_ident, attr.into_iter())?;
|
||||
|
||||
let pytype_ident = struct_item.ident.clone();
|
||||
let pytype_vis = struct_item.vis.clone();
|
||||
let data_ident = meta.data_type()?;
|
||||
|
||||
let class_name = meta.class_name()?.ok_or_else(|| {
|
||||
syn::Error::new_spanned(
|
||||
&struct_item.ident,
|
||||
"#[pystruct_sequence] requires name parameter",
|
||||
)
|
||||
})?;
|
||||
let module_name = meta.module()?;
|
||||
|
||||
// Module name handling
|
||||
let module_name_tokens = match &module_name {
|
||||
Some(m) => quote!(Some(#m)),
|
||||
None => quote!(None),
|
||||
};
|
||||
|
||||
let module_class_name = if let Some(ref m) = module_name {
|
||||
format!("{}.{}", m, class_name)
|
||||
} else {
|
||||
class_name.clone()
|
||||
};
|
||||
|
||||
let output = quote! {
|
||||
// The Python type struct (user-defined, possibly empty)
|
||||
#pytype_vis struct #pytype_ident;
|
||||
|
||||
// PyClassDef for Python type
|
||||
impl ::rustpython_vm::class::PyClassDef for #pytype_ident {
|
||||
const NAME: &'static str = #class_name;
|
||||
const MODULE_NAME: Option<&'static str> = #module_name_tokens;
|
||||
const TP_NAME: &'static str = #module_class_name;
|
||||
const DOC: Option<&'static str> = None;
|
||||
const BASICSIZE: usize = 0;
|
||||
const UNHASHABLE: bool = false;
|
||||
|
||||
type Base = ::rustpython_vm::builtins::PyTuple;
|
||||
}
|
||||
|
||||
// StaticType for Python type
|
||||
impl ::rustpython_vm::class::StaticType for #pytype_ident {
|
||||
fn static_cell() -> &'static ::rustpython_vm::common::static_cell::StaticCell<::rustpython_vm::builtins::PyTypeRef> {
|
||||
::rustpython_vm::common::static_cell! {
|
||||
static CELL: ::rustpython_vm::builtins::PyTypeRef;
|
||||
}
|
||||
&CELL
|
||||
}
|
||||
|
||||
fn static_baseclass() -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> {
|
||||
use ::rustpython_vm::class::StaticType;
|
||||
::rustpython_vm::builtins::PyTuple::static_type()
|
||||
}
|
||||
}
|
||||
|
||||
// MaybeTraverse (empty - no GC fields in empty struct)
|
||||
impl ::rustpython_vm::object::MaybeTraverse for #pytype_ident {
|
||||
fn try_traverse(&self, _traverse_fn: &mut ::rustpython_vm::object::TraverseFn<'_>) {
|
||||
// Empty struct has no fields to traverse
|
||||
}
|
||||
}
|
||||
|
||||
// PyStructSequence trait for Python type
|
||||
impl ::rustpython_vm::types::PyStructSequence for #pytype_ident {
|
||||
type Data = #data_ident;
|
||||
}
|
||||
|
||||
// ToPyObject for Data struct - uses PyStructSequence::from_data
|
||||
impl ::rustpython_vm::convert::ToPyObject for #data_ident {
|
||||
fn to_pyobject(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::PyObjectRef {
|
||||
<#pytype_ident as ::rustpython_vm::types::PyStructSequence>::from_data(self, vm).into()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
@@ -214,16 +214,57 @@ pub fn pymodule(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
derive_impl::pymodule(attr, item).into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(PyStructSequence, attributes(pystruct))]
|
||||
pub fn pystruct_sequence(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input);
|
||||
derive_impl::pystruct_sequence(input).into()
|
||||
/// Attribute macro for defining Python struct sequence types.
|
||||
///
|
||||
/// This macro is applied to an empty struct to create a Python type
|
||||
/// that wraps a Data struct.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// #[pystruct_sequence_data]
|
||||
/// struct StructTimeData {
|
||||
/// pub tm_year: PyObjectRef,
|
||||
/// #[pystruct_sequence(skip)]
|
||||
/// pub tm_gmtoff: PyObjectRef,
|
||||
/// }
|
||||
///
|
||||
/// #[pystruct_sequence(name = "struct_time", module = "time", data = "StructTimeData")]
|
||||
/// struct PyStructTime;
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn pystruct_sequence(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let attr = parse_macro_input!(attr with Punctuated::parse_terminated);
|
||||
let item = parse_macro_input!(item);
|
||||
derive_impl::pystruct_sequence(attr, item).into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(TryIntoPyStructSequence, attributes(pystruct))]
|
||||
pub fn pystruct_sequence_try_from_object(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input);
|
||||
derive_impl::pystruct_sequence_try_from_object(input).into()
|
||||
/// Attribute macro for struct sequence Data structs.
|
||||
///
|
||||
/// Generates field name constants, index constants, and `into_tuple()` method.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// #[pystruct_sequence_data]
|
||||
/// struct StructTimeData {
|
||||
/// pub tm_year: PyObjectRef,
|
||||
/// pub tm_mon: PyObjectRef,
|
||||
/// #[pystruct_sequence(skip)] // optional field, not included in tuple
|
||||
/// pub tm_gmtoff: PyObjectRef,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Options
|
||||
/// - `try_from_object`: Generate `try_from_elements()` method and `TryFromObject` impl
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[pystruct_sequence_data(try_from_object)]
|
||||
/// struct StructTimeData { ... }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn pystruct_sequence_data(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let attr = parse_macro_input!(attr with Punctuated::parse_terminated);
|
||||
let item = parse_macro_input!(item);
|
||||
derive_impl::pystruct_sequence_data(attr, item).into()
|
||||
}
|
||||
|
||||
struct Compiler;
|
||||
|
||||
+16
-14
@@ -13,26 +13,28 @@ mod grp {
|
||||
use nix::unistd;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(module = "grp", name = "struct_group", traverse)]
|
||||
#[derive(PyStructSequence)]
|
||||
struct Group {
|
||||
#[pytraverse(skip)]
|
||||
#[pystruct_sequence_data]
|
||||
struct GroupData {
|
||||
gr_name: String,
|
||||
#[pytraverse(skip)]
|
||||
gr_passwd: String,
|
||||
#[pytraverse(skip)]
|
||||
gr_gid: u32,
|
||||
gr_mem: PyListRef,
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pystruct_sequence(name = "struct_group", module = "grp", data = "GroupData")]
|
||||
struct PyGroup;
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl Group {
|
||||
impl PyGroup {}
|
||||
|
||||
impl GroupData {
|
||||
fn from_unistd_group(group: unistd::Group, vm: &VirtualMachine) -> Self {
|
||||
let cstr_lossy = |s: std::ffi::CString| {
|
||||
s.into_string()
|
||||
.unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned())
|
||||
};
|
||||
Group {
|
||||
GroupData {
|
||||
gr_name: group.name,
|
||||
gr_passwd: cstr_lossy(group.passwd),
|
||||
gr_gid: group.gid.as_raw(),
|
||||
@@ -44,7 +46,7 @@ mod grp {
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn getgrgid(gid: PyIntRef, vm: &VirtualMachine) -> PyResult<Group> {
|
||||
fn getgrgid(gid: PyIntRef, vm: &VirtualMachine) -> PyResult<GroupData> {
|
||||
let gr_gid = gid.as_bigint();
|
||||
let gid = libc::gid_t::try_from(gr_gid)
|
||||
.map(unistd::Gid::from_raw)
|
||||
@@ -61,11 +63,11 @@ mod grp {
|
||||
.into(),
|
||||
)
|
||||
})?;
|
||||
Ok(Group::from_unistd_group(group, vm))
|
||||
Ok(GroupData::from_unistd_group(group, vm))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn getgrnam(name: PyStrRef, vm: &VirtualMachine) -> PyResult<Group> {
|
||||
fn getgrnam(name: PyStrRef, vm: &VirtualMachine) -> PyResult<GroupData> {
|
||||
let gr_name = name.as_str();
|
||||
if gr_name.contains('\0') {
|
||||
return Err(exceptions::cstring_error(vm));
|
||||
@@ -78,7 +80,7 @@ mod grp {
|
||||
.into(),
|
||||
)
|
||||
})?;
|
||||
Ok(Group::from_unistd_group(group, vm))
|
||||
Ok(GroupData::from_unistd_group(group, vm))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
@@ -91,7 +93,7 @@ mod grp {
|
||||
unsafe { libc::setgrent() };
|
||||
while let Some(ptr) = NonNull::new(unsafe { libc::getgrent() }) {
|
||||
let group = unistd::Group::from(unsafe { ptr.as_ref() });
|
||||
let group = Group::from_unistd_group(group, vm).to_pyobject(vm);
|
||||
let group = GroupData::from_unistd_group(group, vm).to_pyobject(vm);
|
||||
list.push(group);
|
||||
}
|
||||
unsafe { libc::endgrent() };
|
||||
|
||||
@@ -63,10 +63,8 @@ mod resource {
|
||||
#[pyattr]
|
||||
use libc::{RUSAGE_CHILDREN, RUSAGE_SELF};
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "struct_rusage")]
|
||||
#[derive(PyStructSequence)]
|
||||
struct Rusage {
|
||||
#[pystruct_sequence_data]
|
||||
struct RUsageData {
|
||||
ru_utime: f64,
|
||||
ru_stime: f64,
|
||||
ru_maxrss: libc::c_long,
|
||||
@@ -85,10 +83,14 @@ mod resource {
|
||||
ru_nivcsw: libc::c_long,
|
||||
}
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl Rusage {}
|
||||
#[pyattr]
|
||||
#[pystruct_sequence(name = "struct_rusage", module = "resource", data = "RUsageData")]
|
||||
struct PyRUsage;
|
||||
|
||||
impl From<libc::rusage> for Rusage {
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyRUsage {}
|
||||
|
||||
impl From<libc::rusage> for RUsageData {
|
||||
fn from(rusage: libc::rusage) -> Self {
|
||||
let tv = |tv: libc::timeval| tv.tv_sec as f64 + (tv.tv_usec as f64 / 1_000_000.0);
|
||||
Self {
|
||||
@@ -113,7 +115,7 @@ mod resource {
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn getrusage(who: i32, vm: &VirtualMachine) -> PyResult<Rusage> {
|
||||
fn getrusage(who: i32, vm: &VirtualMachine) -> PyResult<RUsageData> {
|
||||
let res = unsafe {
|
||||
let mut rusage = mem::MaybeUninit::<libc::rusage>::uninit();
|
||||
if libc::getrusage(who, rusage.as_mut_ptr()) == -1 {
|
||||
@@ -122,7 +124,7 @@ mod resource {
|
||||
Ok(rusage.assume_init())
|
||||
}
|
||||
};
|
||||
res.map(Rusage::from).map_err(|e| {
|
||||
res.map(RUsageData::from).map_err(|e| {
|
||||
if e.kind() == io::ErrorKind::InvalidInput {
|
||||
vm.new_value_error("invalid who parameter")
|
||||
} else {
|
||||
|
||||
@@ -179,7 +179,7 @@ pub(crate) mod module {
|
||||
fn get_terminal_size(
|
||||
fd: OptionalArg<i32>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<_os::PyTerminalSize> {
|
||||
) -> PyResult<_os::TerminalSizeData> {
|
||||
let (columns, lines) = {
|
||||
let stdhandle = match fd {
|
||||
OptionalArg::Present(0) => Console::STD_INPUT_HANDLE,
|
||||
@@ -206,7 +206,7 @@ pub(crate) mod module {
|
||||
(w.Bottom - w.Top + 1) as usize,
|
||||
)
|
||||
};
|
||||
Ok(_os::PyTerminalSize { columns, lines })
|
||||
Ok(_os::TerminalSizeData { columns, lines })
|
||||
}
|
||||
|
||||
#[cfg(target_env = "msvc")]
|
||||
|
||||
+58
-31
@@ -735,10 +735,9 @@ pub(super) mod _os {
|
||||
.into())
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(module = "os", name = "stat_result")]
|
||||
#[derive(Debug, PyStructSequence, FromArgs)]
|
||||
struct StatResult {
|
||||
#[derive(Debug, FromArgs)]
|
||||
#[pystruct_sequence_data]
|
||||
struct StatResultData {
|
||||
pub st_mode: PyIntRef,
|
||||
pub st_ino: PyIntRef,
|
||||
pub st_dev: PyIntRef,
|
||||
@@ -746,31 +745,42 @@ pub(super) mod _os {
|
||||
pub st_uid: PyIntRef,
|
||||
pub st_gid: PyIntRef,
|
||||
pub st_size: PyIntRef,
|
||||
// TODO: unnamed structsequence fields
|
||||
// Indices 7-9: integer seconds
|
||||
#[pyarg(positional, default)]
|
||||
pub __st_atime_int: libc::time_t,
|
||||
#[pystruct_sequence(unnamed)]
|
||||
pub st_atime_int: libc::time_t,
|
||||
#[pyarg(positional, default)]
|
||||
pub __st_mtime_int: libc::time_t,
|
||||
#[pystruct_sequence(unnamed)]
|
||||
pub st_mtime_int: libc::time_t,
|
||||
#[pyarg(positional, default)]
|
||||
pub __st_ctime_int: libc::time_t,
|
||||
#[pystruct_sequence(unnamed)]
|
||||
pub st_ctime_int: libc::time_t,
|
||||
// Float time attributes
|
||||
#[pyarg(any, default)]
|
||||
#[pystruct_sequence(skip)]
|
||||
pub st_atime: f64,
|
||||
#[pyarg(any, default)]
|
||||
#[pystruct_sequence(skip)]
|
||||
pub st_mtime: f64,
|
||||
#[pyarg(any, default)]
|
||||
#[pystruct_sequence(skip)]
|
||||
pub st_ctime: f64,
|
||||
// Nanosecond attributes
|
||||
#[pyarg(any, default)]
|
||||
#[pystruct_sequence(skip)]
|
||||
pub st_atime_ns: i128,
|
||||
#[pyarg(any, default)]
|
||||
#[pystruct_sequence(skip)]
|
||||
pub st_mtime_ns: i128,
|
||||
#[pyarg(any, default)]
|
||||
#[pystruct_sequence(skip)]
|
||||
pub st_ctime_ns: i128,
|
||||
#[pyarg(any, default)]
|
||||
#[pystruct_sequence(skip)]
|
||||
pub st_reparse_tag: u32,
|
||||
}
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl StatResult {
|
||||
impl StatResultData {
|
||||
fn from_stat(stat: &StatStruct, vm: &VirtualMachine) -> Self {
|
||||
let (atime, mtime, ctime);
|
||||
#[cfg(any(unix, windows))]
|
||||
@@ -810,9 +820,9 @@ pub(super) mod _os {
|
||||
st_uid: vm.ctx.new_pyref(stat.st_uid),
|
||||
st_gid: vm.ctx.new_pyref(stat.st_gid),
|
||||
st_size: vm.ctx.new_pyref(stat.st_size),
|
||||
__st_atime_int: atime.0,
|
||||
__st_mtime_int: mtime.0,
|
||||
__st_ctime_int: ctime.0,
|
||||
st_atime_int: atime.0,
|
||||
st_mtime_int: mtime.0,
|
||||
st_ctime_int: ctime.0,
|
||||
st_atime: to_f64(atime),
|
||||
st_mtime: to_f64(mtime),
|
||||
st_ctime: to_f64(ctime),
|
||||
@@ -822,7 +832,14 @@ pub(super) mod _os {
|
||||
st_reparse_tag,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pystruct_sequence(name = "stat_result", module = "os", data = "StatResultData")]
|
||||
struct PyStatResult;
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyStatResult {
|
||||
#[pyslot]
|
||||
fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
let flatten_args = |r: &[PyObjectRef]| {
|
||||
@@ -845,7 +862,7 @@ pub(super) mod _os {
|
||||
|
||||
let args: FuncArgs = flatten_args(&args.args).into();
|
||||
|
||||
let stat: Self = args.bind(vm)?;
|
||||
let stat: StatResultData = args.bind(vm)?;
|
||||
Ok(stat.to_pyobject(vm))
|
||||
}
|
||||
}
|
||||
@@ -920,7 +937,7 @@ pub(super) mod _os {
|
||||
let stat = stat_inner(file.clone(), dir_fd, follow_symlinks)
|
||||
.map_err(|err| IOErrorBuilder::with_filename(&err, file, vm))?
|
||||
.ok_or_else(|| crate::exceptions::cstring_error(vm))?;
|
||||
Ok(StatResult::from_stat(&stat, vm).to_pyobject(vm))
|
||||
Ok(StatResultData::from_stat(&stat, vm).to_pyobject(vm))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
@@ -1215,10 +1232,9 @@ pub(super) mod _os {
|
||||
}
|
||||
|
||||
#[cfg(all(any(unix, windows), not(target_os = "redox")))]
|
||||
#[pyattr]
|
||||
#[pyclass(module = "os", name = "times_result")]
|
||||
#[derive(Debug, PyStructSequence)]
|
||||
struct TimesResult {
|
||||
#[derive(Debug)]
|
||||
#[pystruct_sequence_data]
|
||||
struct TimesResultData {
|
||||
pub user: f64,
|
||||
pub system: f64,
|
||||
pub children_user: f64,
|
||||
@@ -1226,9 +1242,14 @@ pub(super) mod _os {
|
||||
pub elapsed: f64,
|
||||
}
|
||||
|
||||
#[cfg(all(any(unix, windows), not(target_os = "redox")))]
|
||||
#[pyattr]
|
||||
#[pystruct_sequence(name = "times_result", module = "os", data = "TimesResultData")]
|
||||
struct PyTimesResult;
|
||||
|
||||
#[cfg(all(any(unix, windows), not(target_os = "redox")))]
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl TimesResult {}
|
||||
impl PyTimesResult {}
|
||||
|
||||
#[cfg(all(any(unix, windows), not(target_os = "redox")))]
|
||||
#[pyfunction]
|
||||
@@ -1257,7 +1278,7 @@ pub(super) mod _os {
|
||||
let kernel = unsafe { kernel.assume_init() };
|
||||
let user = unsafe { user.assume_init() };
|
||||
|
||||
let times_result = TimesResult {
|
||||
let times_result = TimesResultData {
|
||||
user: user.dwHighDateTime as f64 * 429.4967296 + user.dwLowDateTime as f64 * 1e-7,
|
||||
system: kernel.dwHighDateTime as f64 * 429.4967296
|
||||
+ kernel.dwLowDateTime as f64 * 1e-7,
|
||||
@@ -1285,7 +1306,7 @@ pub(super) mod _os {
|
||||
return Err(vm.new_os_error("Fail to get times".to_string()));
|
||||
}
|
||||
|
||||
let times_result = TimesResult {
|
||||
let times_result = TimesResultData {
|
||||
user: t.tms_utime as f64 / tick_for_second,
|
||||
system: t.tms_stime as f64 / tick_for_second,
|
||||
children_user: t.tms_cutime as f64 / tick_for_second,
|
||||
@@ -1458,21 +1479,23 @@ pub(super) mod _os {
|
||||
}
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(module = "os", name = "terminal_size")]
|
||||
#[derive(PyStructSequence)]
|
||||
#[pystruct_sequence_data]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct PyTerminalSize {
|
||||
pub(crate) struct TerminalSizeData {
|
||||
pub columns: usize,
|
||||
pub lines: usize,
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pystruct_sequence(name = "terminal_size", module = "os", data = "TerminalSizeData")]
|
||||
pub(crate) struct PyTerminalSize;
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyTerminalSize {}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(module = "os", name = "uname_result")]
|
||||
#[derive(Debug, PyStructSequence)]
|
||||
pub(crate) struct UnameResult {
|
||||
#[derive(Debug)]
|
||||
#[pystruct_sequence_data]
|
||||
pub(crate) struct UnameResultData {
|
||||
pub sysname: String,
|
||||
pub nodename: String,
|
||||
pub release: String,
|
||||
@@ -1480,8 +1503,12 @@ pub(super) mod _os {
|
||||
pub machine: String,
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pystruct_sequence(name = "uname_result", module = "os", data = "UnameResultData")]
|
||||
pub(crate) struct PyUnameResult;
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl UnameResult {}
|
||||
impl PyUnameResult {}
|
||||
|
||||
pub(super) fn support_funcs() -> Vec<SupportFunc> {
|
||||
let mut supports = super::platform::module::support_funcs();
|
||||
|
||||
@@ -1330,9 +1330,9 @@ pub mod module {
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn uname(vm: &VirtualMachine) -> PyResult<_os::UnameResult> {
|
||||
fn uname(vm: &VirtualMachine) -> PyResult<_os::UnameResultData> {
|
||||
let info = uname::uname().map_err(|err| err.into_pyexception(vm))?;
|
||||
Ok(_os::UnameResult {
|
||||
Ok(_os::UnameResultData {
|
||||
sysname: info.sysname,
|
||||
nodename: info.nodename,
|
||||
release: info.release,
|
||||
@@ -1730,7 +1730,7 @@ pub mod module {
|
||||
fn get_terminal_size(
|
||||
fd: OptionalArg<i32>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<_os::PyTerminalSize> {
|
||||
) -> PyResult<_os::TerminalSizeData> {
|
||||
let (columns, lines) = {
|
||||
nix::ioctl_read_bad!(winsz, libc::TIOCGWINSZ, libc::winsize);
|
||||
let mut w = libc::winsize {
|
||||
@@ -1743,7 +1743,7 @@ pub mod module {
|
||||
.map_err(|err| err.into_pyexception(vm))?;
|
||||
(w.ws_col.into(), w.ws_row.into())
|
||||
};
|
||||
Ok(_os::PyTerminalSize { columns, lines })
|
||||
Ok(_os::TerminalSizeData { columns, lines })
|
||||
}
|
||||
|
||||
// from libstd:
|
||||
|
||||
+15
-13
@@ -16,10 +16,8 @@ mod pwd {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use crate::{PyObjectRef, convert::ToPyObject};
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(module = "pwd", name = "struct_passwd")]
|
||||
#[derive(PyStructSequence)]
|
||||
struct Passwd {
|
||||
#[pystruct_sequence_data]
|
||||
struct PasswdData {
|
||||
pw_name: String,
|
||||
pw_passwd: String,
|
||||
pw_uid: u32,
|
||||
@@ -29,10 +27,14 @@ mod pwd {
|
||||
pw_shell: String,
|
||||
}
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl Passwd {}
|
||||
#[pyattr]
|
||||
#[pystruct_sequence(name = "struct_passwd", module = "pwd", data = "PasswdData")]
|
||||
struct PyPasswd;
|
||||
|
||||
impl From<User> for Passwd {
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyPasswd {}
|
||||
|
||||
impl From<User> for PasswdData {
|
||||
fn from(user: User) -> Self {
|
||||
// this is just a pain...
|
||||
let cstr_lossy = |s: std::ffi::CString| {
|
||||
@@ -44,7 +46,7 @@ mod pwd {
|
||||
.into_string()
|
||||
.unwrap_or_else(|s| s.to_string_lossy().into_owned())
|
||||
};
|
||||
Passwd {
|
||||
PasswdData {
|
||||
pw_name: user.name,
|
||||
pw_passwd: cstr_lossy(user.passwd),
|
||||
pw_uid: user.uid.as_raw(),
|
||||
@@ -57,7 +59,7 @@ mod pwd {
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn getpwnam(name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<Passwd> {
|
||||
fn getpwnam(name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<PasswdData> {
|
||||
let pw_name = name.as_str();
|
||||
if pw_name.contains('\0') {
|
||||
return Err(exceptions::cstring_error(vm));
|
||||
@@ -70,11 +72,11 @@ mod pwd {
|
||||
.into(),
|
||||
)
|
||||
})?;
|
||||
Ok(Passwd::from(user))
|
||||
Ok(PasswdData::from(user))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn getpwuid(uid: PyIntRef, vm: &VirtualMachine) -> PyResult<Passwd> {
|
||||
fn getpwuid(uid: PyIntRef, vm: &VirtualMachine) -> PyResult<PasswdData> {
|
||||
let uid_t = libc::uid_t::try_from(uid.as_bigint())
|
||||
.map(unistd::Uid::from_raw)
|
||||
.ok();
|
||||
@@ -90,7 +92,7 @@ mod pwd {
|
||||
.into(),
|
||||
)
|
||||
})?;
|
||||
Ok(Passwd::from(user))
|
||||
Ok(PasswdData::from(user))
|
||||
}
|
||||
|
||||
// TODO: maybe merge this functionality into nix?
|
||||
@@ -105,7 +107,7 @@ mod pwd {
|
||||
unsafe { libc::setpwent() };
|
||||
while let Some(ptr) = std::ptr::NonNull::new(unsafe { libc::getpwent() }) {
|
||||
let user = User::from(unsafe { ptr.as_ref() });
|
||||
let passwd = Passwd::from(user).to_pyobject(vm);
|
||||
let passwd = PasswdData::from(user).to_pyobject(vm);
|
||||
list.push(passwd);
|
||||
}
|
||||
unsafe { libc::endpwent() };
|
||||
|
||||
+96
-57
@@ -1,6 +1,6 @@
|
||||
use crate::{Py, PyResult, VirtualMachine, builtins::PyModule, convert::ToPyObject};
|
||||
|
||||
pub(crate) use sys::{__module_def, DOC, MAXSIZE, MULTIARCH, UnraisableHookArgs};
|
||||
pub(crate) use sys::{__module_def, DOC, MAXSIZE, MULTIARCH, UnraisableHookArgsData};
|
||||
|
||||
#[pymodule]
|
||||
mod sys {
|
||||
@@ -450,12 +450,12 @@ mod sys {
|
||||
|
||||
#[pyattr]
|
||||
fn flags(vm: &VirtualMachine) -> PyTupleRef {
|
||||
Flags::from_settings(&vm.state.settings).into_struct_sequence(vm)
|
||||
PyFlags::from_data(FlagsData::from_settings(&vm.state.settings), vm)
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
fn float_info(vm: &VirtualMachine) -> PyTupleRef {
|
||||
PyFloatInfo::INFO.into_struct_sequence(vm)
|
||||
PyFloatInfo::from_data(FloatInfoData::INFO, vm)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
@@ -656,7 +656,7 @@ mod sys {
|
||||
.map_err(|_| vm.new_os_error("service pack is not ASCII".to_owned()))?
|
||||
};
|
||||
let real_version = get_kernel32_version().map_err(|e| vm.new_os_error(e.to_string()))?;
|
||||
Ok(WindowsVersion {
|
||||
let winver = WindowsVersionData {
|
||||
major: real_version.0,
|
||||
minor: real_version.1,
|
||||
build: real_version.2,
|
||||
@@ -667,11 +667,11 @@ mod sys {
|
||||
suite_mask: version.wSuiteMask,
|
||||
product_type: version.wProductType,
|
||||
platform_version: (real_version.0, real_version.1, real_version.2), // TODO Provide accurate version, like CPython impl
|
||||
}
|
||||
.into_struct_sequence(vm))
|
||||
};
|
||||
Ok(PyWindowsVersion::from_data(winver, vm))
|
||||
}
|
||||
|
||||
fn _unraisablehook(unraisable: UnraisableHookArgs, vm: &VirtualMachine) -> PyResult<()> {
|
||||
fn _unraisablehook(unraisable: UnraisableHookArgsData, vm: &VirtualMachine) -> PyResult<()> {
|
||||
use super::PyStderr;
|
||||
|
||||
let stderr = PyStderr(vm);
|
||||
@@ -727,7 +727,7 @@ mod sys {
|
||||
|
||||
#[pyattr]
|
||||
#[pyfunction(name = "__unraisablehook__")]
|
||||
fn unraisablehook(unraisable: UnraisableHookArgs, vm: &VirtualMachine) {
|
||||
fn unraisablehook(unraisable: UnraisableHookArgsData, vm: &VirtualMachine) {
|
||||
if let Err(e) = _unraisablehook(unraisable, vm) {
|
||||
let stderr = super::PyStderr(vm);
|
||||
writeln!(
|
||||
@@ -742,7 +742,7 @@ mod sys {
|
||||
|
||||
#[pyattr]
|
||||
fn hash_info(vm: &VirtualMachine) -> PyTupleRef {
|
||||
PyHashInfo::INFO.into_struct_sequence(vm)
|
||||
PyHashInfo::from_data(HashInfoData::INFO, vm)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
@@ -752,7 +752,7 @@ mod sys {
|
||||
|
||||
#[pyattr]
|
||||
fn int_info(vm: &VirtualMachine) -> PyTupleRef {
|
||||
PyIntInfo::INFO.into_struct_sequence(vm)
|
||||
PyIntInfo::from_data(IntInfoData::INFO, vm)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
@@ -762,7 +762,7 @@ mod sys {
|
||||
|
||||
#[pyfunction]
|
||||
fn set_int_max_str_digits(maxdigits: usize, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let threshold = PyIntInfo::INFO.str_digits_check_threshold;
|
||||
let threshold = IntInfoData::INFO.str_digits_check_threshold;
|
||||
if maxdigits == 0 || maxdigits >= threshold {
|
||||
vm.state.int_max_str_digits.store(maxdigits);
|
||||
Ok(())
|
||||
@@ -812,12 +812,12 @@ mod sys {
|
||||
#[cfg(feature = "threading")]
|
||||
#[pyattr]
|
||||
fn thread_info(vm: &VirtualMachine) -> PyTupleRef {
|
||||
PyThreadInfo::INFO.into_struct_sequence(vm)
|
||||
PyThreadInfo::from_data(ThreadInfoData::INFO, vm)
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
fn version_info(vm: &VirtualMachine) -> PyTupleRef {
|
||||
VersionInfo::VERSION.into_struct_sequence(vm)
|
||||
PyVersionInfo::from_data(VersionInfoData::VERSION, vm)
|
||||
}
|
||||
|
||||
fn update_use_tracing(vm: &VirtualMachine) {
|
||||
@@ -898,19 +898,22 @@ mod sys {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pyclass(no_attr, name = "asyncgen_hooks")]
|
||||
#[derive(PyStructSequence)]
|
||||
pub(super) struct PyAsyncgenHooks {
|
||||
#[pystruct_sequence_data]
|
||||
pub(super) struct AsyncgenHooksData {
|
||||
firstiter: PyObjectRef,
|
||||
finalizer: PyObjectRef,
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pystruct_sequence(name = "asyncgen_hooks", data = "AsyncgenHooksData")]
|
||||
pub(super) struct PyAsyncgenHooks;
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyAsyncgenHooks {}
|
||||
|
||||
#[pyfunction]
|
||||
fn get_asyncgen_hooks(vm: &VirtualMachine) -> PyAsyncgenHooks {
|
||||
PyAsyncgenHooks {
|
||||
fn get_asyncgen_hooks(vm: &VirtualMachine) -> AsyncgenHooksData {
|
||||
AsyncgenHooksData {
|
||||
firstiter: crate::vm::thread::ASYNC_GEN_FIRSTITER
|
||||
.with_borrow(Clone::clone)
|
||||
.to_pyobject(vm),
|
||||
@@ -923,9 +926,9 @@ mod sys {
|
||||
/// sys.flags
|
||||
///
|
||||
/// Flags provided through command line arguments or environment vars.
|
||||
#[pyclass(no_attr, name = "flags", module = "sys")]
|
||||
#[derive(Debug, PyStructSequence)]
|
||||
pub(super) struct Flags {
|
||||
#[derive(Debug)]
|
||||
#[pystruct_sequence_data]
|
||||
pub(super) struct FlagsData {
|
||||
/// -d
|
||||
debug: u8,
|
||||
/// -i
|
||||
@@ -964,8 +967,7 @@ mod sys {
|
||||
warn_default_encoding: u8,
|
||||
}
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl Flags {
|
||||
impl FlagsData {
|
||||
const fn from_settings(settings: &Settings) -> Self {
|
||||
Self {
|
||||
debug: settings.debug,
|
||||
@@ -988,7 +990,13 @@ mod sys {
|
||||
warn_default_encoding: settings.warn_default_encoding as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pystruct_sequence(name = "flags", module = "sys", data = "FlagsData", no_attr)]
|
||||
pub(super) struct PyFlags;
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyFlags {
|
||||
#[pyslot]
|
||||
fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
Err(vm.new_type_error("cannot create 'sys.flags' instances"))
|
||||
@@ -996,17 +1004,15 @@ mod sys {
|
||||
}
|
||||
|
||||
#[cfg(feature = "threading")]
|
||||
#[pyclass(no_attr, name = "thread_info")]
|
||||
#[derive(PyStructSequence)]
|
||||
pub(super) struct PyThreadInfo {
|
||||
#[pystruct_sequence_data]
|
||||
pub(super) struct ThreadInfoData {
|
||||
name: Option<&'static str>,
|
||||
lock: Option<&'static str>,
|
||||
version: Option<&'static str>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "threading")]
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyThreadInfo {
|
||||
impl ThreadInfoData {
|
||||
const INFO: Self = Self {
|
||||
name: crate::stdlib::thread::_thread::PYTHREAD_NAME,
|
||||
// As I know, there's only way to use lock as "Mutex" in Rust
|
||||
@@ -1016,9 +1022,16 @@ mod sys {
|
||||
};
|
||||
}
|
||||
|
||||
#[pyclass(no_attr, name = "float_info")]
|
||||
#[derive(PyStructSequence)]
|
||||
pub(super) struct PyFloatInfo {
|
||||
#[cfg(feature = "threading")]
|
||||
#[pystruct_sequence(name = "thread_info", data = "ThreadInfoData", no_attr)]
|
||||
pub(super) struct PyThreadInfo;
|
||||
|
||||
#[cfg(feature = "threading")]
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyThreadInfo {}
|
||||
|
||||
#[pystruct_sequence_data]
|
||||
pub(super) struct FloatInfoData {
|
||||
max: f64,
|
||||
max_exp: i32,
|
||||
max_10_exp: i32,
|
||||
@@ -1032,8 +1045,7 @@ mod sys {
|
||||
rounds: i32,
|
||||
}
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyFloatInfo {
|
||||
impl FloatInfoData {
|
||||
const INFO: Self = Self {
|
||||
max: f64::MAX,
|
||||
max_exp: f64::MAX_EXP,
|
||||
@@ -1049,9 +1061,14 @@ mod sys {
|
||||
};
|
||||
}
|
||||
|
||||
#[pyclass(no_attr, name = "hash_info")]
|
||||
#[derive(PyStructSequence)]
|
||||
pub(super) struct PyHashInfo {
|
||||
#[pystruct_sequence(name = "float_info", data = "FloatInfoData", no_attr)]
|
||||
pub(super) struct PyFloatInfo;
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyFloatInfo {}
|
||||
|
||||
#[pystruct_sequence_data]
|
||||
pub(super) struct HashInfoData {
|
||||
width: usize,
|
||||
modulus: PyUHash,
|
||||
inf: PyHash,
|
||||
@@ -1063,8 +1080,7 @@ mod sys {
|
||||
cutoff: usize,
|
||||
}
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyHashInfo {
|
||||
impl HashInfoData {
|
||||
const INFO: Self = {
|
||||
use rustpython_common::hash::*;
|
||||
Self {
|
||||
@@ -1081,17 +1097,21 @@ mod sys {
|
||||
};
|
||||
}
|
||||
|
||||
#[pyclass(no_attr, name = "int_info")]
|
||||
#[derive(PyStructSequence)]
|
||||
pub(super) struct PyIntInfo {
|
||||
#[pystruct_sequence(name = "hash_info", data = "HashInfoData", no_attr)]
|
||||
pub(super) struct PyHashInfo;
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyHashInfo {}
|
||||
|
||||
#[pystruct_sequence_data]
|
||||
pub(super) struct IntInfoData {
|
||||
bits_per_digit: usize,
|
||||
sizeof_digit: usize,
|
||||
default_max_str_digits: usize,
|
||||
str_digits_check_threshold: usize,
|
||||
}
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyIntInfo {
|
||||
impl IntInfoData {
|
||||
const INFO: Self = Self {
|
||||
bits_per_digit: 30, //?
|
||||
sizeof_digit: std::mem::size_of::<u32>(),
|
||||
@@ -1100,9 +1120,15 @@ mod sys {
|
||||
};
|
||||
}
|
||||
|
||||
#[pyclass(no_attr, name = "version_info")]
|
||||
#[derive(Default, Debug, PyStructSequence)]
|
||||
pub struct VersionInfo {
|
||||
#[pystruct_sequence(name = "int_info", data = "IntInfoData", no_attr)]
|
||||
pub(super) struct PyIntInfo;
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyIntInfo {}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[pystruct_sequence_data]
|
||||
pub struct VersionInfoData {
|
||||
major: usize,
|
||||
minor: usize,
|
||||
micro: usize,
|
||||
@@ -1110,8 +1136,7 @@ mod sys {
|
||||
serial: usize,
|
||||
}
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl VersionInfo {
|
||||
impl VersionInfoData {
|
||||
pub const VERSION: Self = Self {
|
||||
major: version::MAJOR,
|
||||
minor: version::MINOR,
|
||||
@@ -1119,6 +1144,13 @@ mod sys {
|
||||
releaselevel: version::RELEASELEVEL,
|
||||
serial: version::SERIAL,
|
||||
};
|
||||
}
|
||||
|
||||
#[pystruct_sequence(name = "version_info", data = "VersionInfoData", no_attr)]
|
||||
pub struct PyVersionInfo;
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyVersionInfo {
|
||||
#[pyslot]
|
||||
fn slot_new(
|
||||
_cls: crate::builtins::type_::PyTypeRef,
|
||||
@@ -1130,9 +1162,9 @@ mod sys {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[pyclass(no_attr, name = "getwindowsversion")]
|
||||
#[derive(Default, Debug, PyStructSequence)]
|
||||
pub(super) struct WindowsVersion {
|
||||
#[derive(Default, Debug)]
|
||||
#[pystruct_sequence_data]
|
||||
pub(super) struct WindowsVersionData {
|
||||
major: u32,
|
||||
minor: u32,
|
||||
build: u32,
|
||||
@@ -1146,12 +1178,16 @@ mod sys {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl WindowsVersion {}
|
||||
#[pystruct_sequence(name = "getwindowsversion", data = "WindowsVersionData", no_attr)]
|
||||
pub(super) struct PyWindowsVersion;
|
||||
|
||||
#[pyclass(no_attr, name = "UnraisableHookArgs")]
|
||||
#[derive(Debug, PyStructSequence, TryIntoPyStructSequence)]
|
||||
pub struct UnraisableHookArgs {
|
||||
#[cfg(windows)]
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyWindowsVersion {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[pystruct_sequence_data(try_from_object)]
|
||||
pub struct UnraisableHookArgsData {
|
||||
pub exc_type: PyTypeRef,
|
||||
pub exc_value: PyObjectRef,
|
||||
pub exc_traceback: PyObjectRef,
|
||||
@@ -1159,8 +1195,11 @@ mod sys {
|
||||
pub object: PyObjectRef,
|
||||
}
|
||||
|
||||
#[pystruct_sequence(name = "UnraisableHookArgs", data = "UnraisableHookArgsData", no_attr)]
|
||||
pub struct PyUnraisableHookArgs;
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl UnraisableHookArgs {}
|
||||
impl PyUnraisableHookArgs {}
|
||||
}
|
||||
|
||||
pub(crate) fn init_module(vm: &VirtualMachine, module: &Py<PyModule>, builtins: &Py<PyModule>) {
|
||||
|
||||
@@ -34,10 +34,10 @@ unsafe extern "C" {
|
||||
#[pymodule(name = "time", with(platform))]
|
||||
mod decl {
|
||||
use crate::{
|
||||
AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
|
||||
AsObject, PyObjectRef, PyResult, VirtualMachine,
|
||||
builtins::{PyStrRef, PyTypeRef, PyUtf8StrRef},
|
||||
function::{Either, FuncArgs, OptionalArg},
|
||||
types::PyStructSequence,
|
||||
types::{PyStructSequence, struct_sequence_new},
|
||||
};
|
||||
use chrono::{
|
||||
DateTime, Datelike, TimeZone, Timelike,
|
||||
@@ -280,7 +280,9 @@ mod decl {
|
||||
/// Construct a localtime from the optional seconds, or get the current local time.
|
||||
fn naive_or_local(self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> {
|
||||
Ok(match self {
|
||||
Self::Present(secs) => pyobj_to_date_time(secs, vm)?.naive_utc(),
|
||||
Self::Present(secs) => pyobj_to_date_time(secs, vm)?
|
||||
.with_timezone(&chrono::Local)
|
||||
.naive_local(),
|
||||
Self::Missing => chrono::offset::Local::now().naive_local(),
|
||||
})
|
||||
}
|
||||
@@ -293,7 +295,7 @@ mod decl {
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionalArg<PyStructTime> {
|
||||
impl OptionalArg<StructTimeData> {
|
||||
fn naive_or_local(self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> {
|
||||
Ok(match self {
|
||||
Self::Present(t) => t.to_date_time(vm)?,
|
||||
@@ -304,33 +306,41 @@ mod decl {
|
||||
|
||||
/// https://docs.python.org/3/library/time.html?highlight=gmtime#time.gmtime
|
||||
#[pyfunction]
|
||||
fn gmtime(secs: OptionalArg<Either<f64, i64>>, vm: &VirtualMachine) -> PyResult<PyStructTime> {
|
||||
fn gmtime(
|
||||
secs: OptionalArg<Either<f64, i64>>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<StructTimeData> {
|
||||
let instant = secs.naive_or_utc(vm)?;
|
||||
Ok(PyStructTime::new(vm, instant, 0))
|
||||
Ok(StructTimeData::new(vm, instant, 0))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn localtime(
|
||||
secs: OptionalArg<Either<f64, i64>>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyStructTime> {
|
||||
) -> PyResult<StructTimeData> {
|
||||
let instant = secs.naive_or_local(vm)?;
|
||||
// TODO: isdst flag must be valid value here
|
||||
// https://docs.python.org/3/library/time.html#time.localtime
|
||||
Ok(PyStructTime::new(vm, instant, -1))
|
||||
Ok(StructTimeData::new(vm, instant, -1))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn mktime(t: PyStructTime, vm: &VirtualMachine) -> PyResult<f64> {
|
||||
fn mktime(t: StructTimeData, vm: &VirtualMachine) -> PyResult<f64> {
|
||||
let datetime = t.to_date_time(vm)?;
|
||||
let seconds_since_epoch = datetime.and_utc().timestamp() as f64;
|
||||
// mktime interprets struct_time as local time
|
||||
let local_dt = chrono::Local
|
||||
.from_local_datetime(&datetime)
|
||||
.single()
|
||||
.ok_or_else(|| vm.new_overflow_error("mktime argument out of range"))?;
|
||||
let seconds_since_epoch = local_dt.timestamp() as f64;
|
||||
Ok(seconds_since_epoch)
|
||||
}
|
||||
|
||||
const CFMT: &str = "%a %b %e %H:%M:%S %Y";
|
||||
|
||||
#[pyfunction]
|
||||
fn asctime(t: OptionalArg<PyStructTime>, vm: &VirtualMachine) -> PyResult {
|
||||
fn asctime(t: OptionalArg<StructTimeData>, vm: &VirtualMachine) -> PyResult {
|
||||
let instant = t.naive_or_local(vm)?;
|
||||
let formatted_time = instant.format(CFMT).to_string();
|
||||
Ok(vm.ctx.new_str(formatted_time).into())
|
||||
@@ -345,7 +355,7 @@ mod decl {
|
||||
#[pyfunction]
|
||||
fn strftime(
|
||||
format: PyUtf8StrRef,
|
||||
t: OptionalArg<PyStructTime>,
|
||||
t: OptionalArg<StructTimeData>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
use std::fmt::Write;
|
||||
@@ -465,34 +475,31 @@ mod decl {
|
||||
Ok(get_process_time(vm)?.as_nanos() as u64)
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "struct_time")]
|
||||
#[derive(PyStructSequence, TryIntoPyStructSequence)]
|
||||
#[allow(dead_code)]
|
||||
struct PyStructTime {
|
||||
tm_year: PyObjectRef,
|
||||
tm_mon: PyObjectRef,
|
||||
tm_mday: PyObjectRef,
|
||||
tm_hour: PyObjectRef,
|
||||
tm_min: PyObjectRef,
|
||||
tm_sec: PyObjectRef,
|
||||
tm_wday: PyObjectRef,
|
||||
tm_yday: PyObjectRef,
|
||||
tm_isdst: PyObjectRef,
|
||||
#[pystruct(skip)]
|
||||
tm_gmtoff: PyObjectRef,
|
||||
#[pystruct(skip)]
|
||||
tm_zone: PyObjectRef,
|
||||
/// Data struct for struct_time
|
||||
#[pystruct_sequence_data(try_from_object)]
|
||||
pub struct StructTimeData {
|
||||
pub tm_year: PyObjectRef,
|
||||
pub tm_mon: PyObjectRef,
|
||||
pub tm_mday: PyObjectRef,
|
||||
pub tm_hour: PyObjectRef,
|
||||
pub tm_min: PyObjectRef,
|
||||
pub tm_sec: PyObjectRef,
|
||||
pub tm_wday: PyObjectRef,
|
||||
pub tm_yday: PyObjectRef,
|
||||
pub tm_isdst: PyObjectRef,
|
||||
#[pystruct_sequence(skip)]
|
||||
pub tm_gmtoff: PyObjectRef,
|
||||
#[pystruct_sequence(skip)]
|
||||
pub tm_zone: PyObjectRef,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PyStructTime {
|
||||
impl std::fmt::Debug for StructTimeData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "struct_time()")
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyStructTime {
|
||||
impl StructTimeData {
|
||||
fn new(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self {
|
||||
let local_time = chrono::Local.from_local_datetime(&tm).unwrap();
|
||||
let offset_seconds =
|
||||
@@ -531,12 +538,18 @@ mod decl {
|
||||
);
|
||||
Ok(dt)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pystruct_sequence(name = "struct_time", module = "time", data = "StructTimeData")]
|
||||
pub struct PyStructTime;
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyStructTime {
|
||||
#[pyslot]
|
||||
fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
// cls is ignorable because this is not a basetype
|
||||
let seq = args.bind(vm)?;
|
||||
Ok(vm.new_pyobj(Self::try_from_object(vm, seq)?))
|
||||
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
let seq: PyObjectRef = args.bind(vm)?;
|
||||
struct_sequence_new(cls, seq, vm)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,5 +3,5 @@ mod structseq;
|
||||
mod zoo;
|
||||
|
||||
pub use slot::*;
|
||||
pub use structseq::PyStructSequence;
|
||||
pub use structseq::{PyStructSequence, PyStructSequenceData, struct_sequence_new};
|
||||
pub(crate) use zoo::TypeZoo;
|
||||
@@ -1,50 +1,193 @@
|
||||
use crate::{
|
||||
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
builtins::{PyBaseExceptionRef, PyStrRef, PyTuple, PyTupleRef, PyType},
|
||||
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, atomic_func,
|
||||
builtins::{
|
||||
PyBaseExceptionRef, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, type_::PointerSlot,
|
||||
},
|
||||
class::{PyClassImpl, StaticType},
|
||||
function::{Either, PyComparisonValue},
|
||||
iter::PyExactSizeIterator,
|
||||
protocol::{PyMappingMethods, PySequenceMethods},
|
||||
sliceable::{SequenceIndex, SliceableSequenceOp},
|
||||
types::PyComparisonOp,
|
||||
vm::Context,
|
||||
};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
#[pyclass]
|
||||
pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {
|
||||
const REQUIRED_FIELD_NAMES: &'static [&'static str];
|
||||
const OPTIONAL_FIELD_NAMES: &'static [&'static str];
|
||||
/// Create a new struct sequence instance from a sequence.
|
||||
///
|
||||
/// The class must have `n_sequence_fields` and `n_fields` attributes set
|
||||
/// (done automatically by `PyStructSequence::extend_pyclass`).
|
||||
pub fn struct_sequence_new(cls: PyTypeRef, seq: PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
// = structseq_new
|
||||
|
||||
fn into_tuple(self, vm: &VirtualMachine) -> PyTuple;
|
||||
|
||||
fn into_struct_sequence(self, vm: &VirtualMachine) -> PyTupleRef {
|
||||
self.into_tuple(vm)
|
||||
.into_ref_with_type(vm, Self::static_type().to_owned())
|
||||
.unwrap()
|
||||
#[cold]
|
||||
fn length_error(
|
||||
tp_name: &str,
|
||||
min_len: usize,
|
||||
max_len: usize,
|
||||
len: usize,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyBaseExceptionRef {
|
||||
if min_len == max_len {
|
||||
vm.new_type_error(format!(
|
||||
"{tp_name}() takes a {min_len}-sequence ({len}-sequence given)"
|
||||
))
|
||||
} else if len < min_len {
|
||||
vm.new_type_error(format!(
|
||||
"{tp_name}() takes an at least {min_len}-sequence ({len}-sequence given)"
|
||||
))
|
||||
} else {
|
||||
vm.new_type_error(format!(
|
||||
"{tp_name}() takes an at most {max_len}-sequence ({len}-sequence given)"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_elements_from(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
|
||||
#[cold]
|
||||
fn sequence_length_error(
|
||||
name: &str,
|
||||
len: usize,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyBaseExceptionRef {
|
||||
vm.new_type_error(format!("{name} takes a sequence of length {len}"))
|
||||
}
|
||||
let min_len: usize = cls
|
||||
.get_attr(identifier!(vm.ctx, n_sequence_fields))
|
||||
.ok_or_else(|| vm.new_type_error("missing n_sequence_fields attribute"))?
|
||||
.try_into_value(vm)?;
|
||||
let max_len: usize = cls
|
||||
.get_attr(identifier!(vm.ctx, n_fields))
|
||||
.ok_or_else(|| vm.new_type_error("missing n_fields attribute"))?
|
||||
.try_into_value(vm)?;
|
||||
|
||||
let seq: Vec<PyObjectRef> = seq.try_into_value(vm)?;
|
||||
let len = seq.len();
|
||||
|
||||
if len < min_len || len > max_len {
|
||||
return Err(length_error(&cls.slot_name(), min_len, max_len, len, vm));
|
||||
}
|
||||
|
||||
// Copy items and pad with None
|
||||
let mut items = seq;
|
||||
items.resize_with(max_len, || vm.ctx.none());
|
||||
|
||||
PyTuple::new_unchecked(items.into_boxed_slice())
|
||||
.into_ref_with_type(vm, cls)
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn get_visible_len(obj: &PyObject, vm: &VirtualMachine) -> PyResult<usize> {
|
||||
obj.class()
|
||||
.get_attr(identifier!(vm.ctx, n_sequence_fields))
|
||||
.ok_or_else(|| vm.new_type_error("missing n_sequence_fields"))?
|
||||
.try_into_value(vm)
|
||||
}
|
||||
|
||||
/// Sequence methods for struct sequences.
|
||||
/// Uses n_sequence_fields to determine visible length.
|
||||
static STRUCT_SEQUENCE_AS_SEQUENCE: LazyLock<PySequenceMethods> =
|
||||
LazyLock::new(|| PySequenceMethods {
|
||||
length: atomic_func!(|seq, vm| get_visible_len(seq.obj, vm)),
|
||||
concat: atomic_func!(|seq, other, vm| {
|
||||
// Convert to visible-only tuple, then use regular tuple concat
|
||||
let n_seq = get_visible_len(seq.obj, vm)?;
|
||||
let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap();
|
||||
let visible: Vec<_> = tuple.iter().take(n_seq).cloned().collect();
|
||||
let visible_tuple = PyTuple::new_ref(visible, &vm.ctx);
|
||||
// Use tuple's concat implementation
|
||||
visible_tuple.as_object().to_sequence().concat(other, vm)
|
||||
}),
|
||||
repeat: atomic_func!(|seq, n, vm| {
|
||||
// Convert to visible-only tuple, then use regular tuple repeat
|
||||
let n_seq = get_visible_len(seq.obj, vm)?;
|
||||
let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap();
|
||||
let visible: Vec<_> = tuple.iter().take(n_seq).cloned().collect();
|
||||
let visible_tuple = PyTuple::new_ref(visible, &vm.ctx);
|
||||
// Use tuple's repeat implementation
|
||||
visible_tuple.as_object().to_sequence().repeat(n, vm)
|
||||
}),
|
||||
item: atomic_func!(|seq, i, vm| {
|
||||
let n_seq = get_visible_len(seq.obj, vm)?;
|
||||
let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap();
|
||||
let idx = if i < 0 {
|
||||
let pos_i = n_seq as isize + i;
|
||||
if pos_i < 0 {
|
||||
return Err(vm.new_index_error("tuple index out of range"));
|
||||
}
|
||||
pos_i as usize
|
||||
} else {
|
||||
i as usize
|
||||
};
|
||||
if idx >= n_seq {
|
||||
return Err(vm.new_index_error("tuple index out of range"));
|
||||
}
|
||||
Ok(tuple[idx].clone())
|
||||
}),
|
||||
contains: atomic_func!(|seq, needle, vm| {
|
||||
let n_seq = get_visible_len(seq.obj, vm)?;
|
||||
let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap();
|
||||
for item in tuple.iter().take(n_seq) {
|
||||
if item.rich_compare_bool(needle, PyComparisonOp::Eq, vm)? {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}),
|
||||
..PySequenceMethods::NOT_IMPLEMENTED
|
||||
});
|
||||
|
||||
/// Mapping methods for struct sequences.
|
||||
/// Handles subscript (indexing) with visible length bounds.
|
||||
static STRUCT_SEQUENCE_AS_MAPPING: LazyLock<PyMappingMethods> =
|
||||
LazyLock::new(|| PyMappingMethods {
|
||||
length: atomic_func!(|mapping, vm| get_visible_len(mapping.obj, vm)),
|
||||
subscript: atomic_func!(|mapping, needle, vm| {
|
||||
let n_seq = get_visible_len(mapping.obj, vm)?;
|
||||
let tuple = mapping.obj.downcast_ref::<PyTuple>().unwrap();
|
||||
let visible_elements = &tuple.as_slice()[..n_seq];
|
||||
|
||||
match SequenceIndex::try_from_borrowed_object(vm, needle, "tuple")? {
|
||||
SequenceIndex::Int(i) => visible_elements.getitem_by_index(vm, i),
|
||||
SequenceIndex::Slice(slice) => visible_elements
|
||||
.getitem_by_slice(vm, slice)
|
||||
.map(|x| vm.ctx.new_tuple(x).into()),
|
||||
}
|
||||
}),
|
||||
..PyMappingMethods::NOT_IMPLEMENTED
|
||||
});
|
||||
|
||||
/// Trait for Data structs that back a PyStructSequence.
|
||||
///
|
||||
/// This trait is implemented by `#[pystruct_sequence_data]` on the Data struct.
|
||||
/// It provides field information, tuple conversion, and element parsing.
|
||||
pub trait PyStructSequenceData: Sized {
|
||||
/// Names of required fields (in order). Shown in repr.
|
||||
const REQUIRED_FIELD_NAMES: &'static [&'static str];
|
||||
|
||||
/// Names of optional/skipped fields (in order, after required fields).
|
||||
const OPTIONAL_FIELD_NAMES: &'static [&'static str];
|
||||
|
||||
/// Number of unnamed fields (visible but index-only access).
|
||||
const UNNAMED_FIELDS_LEN: usize = 0;
|
||||
|
||||
/// Convert this Data struct into a PyTuple.
|
||||
fn into_tuple(self, vm: &VirtualMachine) -> PyTuple;
|
||||
|
||||
/// Construct this Data struct from tuple elements.
|
||||
/// Default implementation returns an error.
|
||||
/// Override with `#[pystruct_sequence_data(try_from_object)]` to enable.
|
||||
fn try_from_elements(_elements: Vec<PyObjectRef>, vm: &VirtualMachine) -> PyResult<Self> {
|
||||
Err(vm.new_type_error("This struct sequence does not support construction from elements"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for Python struct sequence types.
|
||||
///
|
||||
/// This trait is implemented by the `#[pystruct_sequence]` macro on the Python type struct.
|
||||
/// It connects to the Data struct and provides Python-level functionality.
|
||||
#[pyclass]
|
||||
pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {
|
||||
/// The Data struct that provides field definitions.
|
||||
type Data: PyStructSequenceData;
|
||||
|
||||
/// Convert a Data struct into a PyStructSequence instance.
|
||||
fn from_data(data: Self::Data, vm: &VirtualMachine) -> PyTupleRef {
|
||||
let typ = Self::static_type();
|
||||
// if !obj.fast_isinstance(typ) {
|
||||
// return Err(vm.new_type_error(format!(
|
||||
// "{} is not a subclass of {}",
|
||||
// obj.class().name(),
|
||||
// typ.name(),
|
||||
// )));
|
||||
// }
|
||||
let seq: Vec<PyObjectRef> = obj.try_into_value(vm)?;
|
||||
if seq.len() < Self::REQUIRED_FIELD_NAMES.len() {
|
||||
return Err(sequence_length_error(
|
||||
&typ.name(),
|
||||
Self::REQUIRED_FIELD_NAMES.len(),
|
||||
vm,
|
||||
));
|
||||
}
|
||||
Ok(seq)
|
||||
data.into_tuple(vm)
|
||||
.into_ref_with_type(vm, typ.to_owned())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[pyslot]
|
||||
@@ -53,20 +196,21 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {
|
||||
.downcast_ref::<PyTuple>()
|
||||
.ok_or_else(|| vm.new_type_error("unexpected payload for __repr__"))?;
|
||||
|
||||
let field_names = Self::Data::REQUIRED_FIELD_NAMES;
|
||||
let format_field = |(value, name): (&PyObjectRef, _)| {
|
||||
let s = value.repr(vm)?;
|
||||
Ok(format!("{name}={s}"))
|
||||
};
|
||||
let (body, suffix) =
|
||||
if let Some(_guard) = rustpython_vm::recursion::ReprGuard::enter(vm, zelf.as_ref()) {
|
||||
if Self::REQUIRED_FIELD_NAMES.len() == 1 {
|
||||
if field_names.len() == 1 {
|
||||
let value = zelf.first().unwrap();
|
||||
let formatted = format_field((value, Self::REQUIRED_FIELD_NAMES[0]))?;
|
||||
let formatted = format_field((value, field_names[0]))?;
|
||||
(formatted, ",")
|
||||
} else {
|
||||
let fields: PyResult<Vec<_>> = zelf
|
||||
.iter()
|
||||
.zip(Self::REQUIRED_FIELD_NAMES.iter().copied())
|
||||
.zip(field_names.iter().copied())
|
||||
.map(format_field)
|
||||
.collect();
|
||||
(fields?.join(", "), "")
|
||||
@@ -88,9 +232,23 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {
|
||||
vm.new_tuple((zelf.class().to_owned(), (vm.ctx.new_tuple(zelf.to_vec()),)))
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn __getitem__(zelf: PyRef<PyTuple>, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
let n_seq = get_visible_len(zelf.as_ref(), vm)?;
|
||||
let visible_elements = &zelf.as_slice()[..n_seq];
|
||||
|
||||
match SequenceIndex::try_from_borrowed_object(vm, &needle, "tuple")? {
|
||||
SequenceIndex::Int(i) => visible_elements.getitem_by_index(vm, i),
|
||||
SequenceIndex::Slice(slice) => visible_elements
|
||||
.getitem_by_slice(vm, slice)
|
||||
.map(|x| vm.ctx.new_tuple(x).into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[extend_class]
|
||||
fn extend_pyclass(ctx: &Context, class: &'static Py<PyType>) {
|
||||
for (i, &name) in Self::REQUIRED_FIELD_NAMES.iter().enumerate() {
|
||||
// Getters for named visible fields (indices 0 to REQUIRED_FIELD_NAMES.len() - 1)
|
||||
for (i, &name) in Self::Data::REQUIRED_FIELD_NAMES.iter().enumerate() {
|
||||
// cast i to a u8 so there's less to store in the getter closure.
|
||||
// Hopefully there's not struct sequences with >=256 elements :P
|
||||
let i = i as u8;
|
||||
@@ -103,15 +261,125 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {
|
||||
);
|
||||
}
|
||||
|
||||
// Getters for hidden/skipped fields (indices after visible fields)
|
||||
let visible_count = Self::Data::REQUIRED_FIELD_NAMES.len() + Self::Data::UNNAMED_FIELDS_LEN;
|
||||
for (i, &name) in Self::Data::OPTIONAL_FIELD_NAMES.iter().enumerate() {
|
||||
let idx = (visible_count + i) as u8;
|
||||
class.set_attr(
|
||||
ctx.intern_str(name),
|
||||
ctx.new_readonly_getset(name, class, move |zelf: &PyTuple| {
|
||||
zelf[idx as usize].to_owned()
|
||||
})
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
class.set_attr(
|
||||
identifier!(ctx, __match_args__),
|
||||
ctx.new_tuple(
|
||||
Self::REQUIRED_FIELD_NAMES
|
||||
Self::Data::REQUIRED_FIELD_NAMES
|
||||
.iter()
|
||||
.map(|&name| ctx.new_str(name).into())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
|
||||
// special fields:
|
||||
// n_sequence_fields = visible fields (named + unnamed)
|
||||
// n_fields = all fields (visible + hidden/skipped)
|
||||
// n_unnamed_fields
|
||||
let n_unnamed_fields = Self::Data::UNNAMED_FIELDS_LEN;
|
||||
let n_sequence_fields = Self::Data::REQUIRED_FIELD_NAMES.len() + n_unnamed_fields;
|
||||
let n_fields = n_sequence_fields + Self::Data::OPTIONAL_FIELD_NAMES.len();
|
||||
class.set_attr(
|
||||
identifier!(ctx, n_sequence_fields),
|
||||
ctx.new_int(n_sequence_fields).into(),
|
||||
);
|
||||
class.set_attr(identifier!(ctx, n_fields), ctx.new_int(n_fields).into());
|
||||
class.set_attr(
|
||||
identifier!(ctx, n_unnamed_fields),
|
||||
ctx.new_int(n_unnamed_fields).into(),
|
||||
);
|
||||
|
||||
// Override as_sequence and as_mapping slots to use visible length
|
||||
class.slots.as_sequence.store(Some(PointerSlot::from(
|
||||
&*STRUCT_SEQUENCE_AS_SEQUENCE as &'static PySequenceMethods,
|
||||
)));
|
||||
class.slots.as_mapping.store(Some(PointerSlot::from(
|
||||
&*STRUCT_SEQUENCE_AS_MAPPING as &'static PyMappingMethods,
|
||||
)));
|
||||
|
||||
// Override iter slot to return only visible elements
|
||||
class.slots.iter.store(Some(struct_sequence_iter));
|
||||
|
||||
// Override hash slot to hash only visible elements
|
||||
class.slots.hash.store(Some(struct_sequence_hash));
|
||||
|
||||
// Override richcompare slot to compare only visible elements
|
||||
class
|
||||
.slots
|
||||
.richcompare
|
||||
.store(Some(struct_sequence_richcompare));
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator function for struct sequences - returns only visible elements
|
||||
fn struct_sequence_iter(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
let tuple = zelf
|
||||
.downcast_ref::<PyTuple>()
|
||||
.ok_or_else(|| vm.new_type_error("expected tuple"))?;
|
||||
let n_seq = get_visible_len(&zelf, vm)?;
|
||||
let visible: Vec<_> = tuple.iter().take(n_seq).cloned().collect();
|
||||
let visible_tuple = PyTuple::new_ref(visible, &vm.ctx);
|
||||
visible_tuple
|
||||
.as_object()
|
||||
.to_owned()
|
||||
.get_iter(vm)
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
/// Hash function for struct sequences - hashes only visible elements
|
||||
fn struct_sequence_hash(
|
||||
zelf: &PyObject,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<crate::common::hash::PyHash> {
|
||||
let tuple = zelf
|
||||
.downcast_ref::<PyTuple>()
|
||||
.ok_or_else(|| vm.new_type_error("expected tuple"))?;
|
||||
let n_seq = get_visible_len(zelf, vm)?;
|
||||
// Create a visible-only tuple and hash it
|
||||
let visible: Vec<_> = tuple.iter().take(n_seq).cloned().collect();
|
||||
let visible_tuple = PyTuple::new_ref(visible, &vm.ctx);
|
||||
visible_tuple.as_object().hash(vm)
|
||||
}
|
||||
|
||||
/// Rich comparison for struct sequences - compares only visible elements
|
||||
fn struct_sequence_richcompare(
|
||||
zelf: &PyObject,
|
||||
other: &PyObject,
|
||||
op: PyComparisonOp,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Either<PyObjectRef, PyComparisonValue>> {
|
||||
let zelf_tuple = zelf
|
||||
.downcast_ref::<PyTuple>()
|
||||
.ok_or_else(|| vm.new_type_error("expected tuple"))?;
|
||||
|
||||
// If other is not a tuple, return NotImplemented
|
||||
let Some(other_tuple) = other.downcast_ref::<PyTuple>() else {
|
||||
return Ok(Either::B(PyComparisonValue::NotImplemented));
|
||||
};
|
||||
|
||||
let zelf_len = get_visible_len(zelf, vm)?;
|
||||
// For other, try to get visible len; if it fails (not a struct sequence), use full length
|
||||
let other_len = get_visible_len(other, vm).unwrap_or(other_tuple.len());
|
||||
|
||||
let zelf_visible = &zelf_tuple.as_slice()[..zelf_len];
|
||||
let other_visible = &other_tuple.as_slice()[..other_len];
|
||||
|
||||
// Use the same comparison logic as regular tuples
|
||||
zelf_visible
|
||||
.iter()
|
||||
.richcompare(other_visible.iter(), op, vm)
|
||||
.map(|v| Either::B(PyComparisonValue::Implemented(v)))
|
||||
}
|
||||
@@ -245,6 +245,9 @@ declare_const_name! {
|
||||
items,
|
||||
keys,
|
||||
modules,
|
||||
n_fields,
|
||||
n_sequence_fields,
|
||||
n_unnamed_fields,
|
||||
namereplace,
|
||||
replace,
|
||||
strict,
|
||||
|
||||
@@ -465,7 +465,7 @@ impl VirtualMachine {
|
||||
let exc_type = e.class().to_owned();
|
||||
let exc_traceback = e.__traceback__().to_pyobject(self); // TODO: actual traceback
|
||||
let exc_value = e.into();
|
||||
let args = stdlib::sys::UnraisableHookArgs {
|
||||
let args = stdlib::sys::UnraisableHookArgsData {
|
||||
exc_type,
|
||||
exc_value,
|
||||
exc_traceback,
|
||||
|
||||
Reference in new issue
Block a user