diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 0443e177c..c99a5874d 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -102,6 +102,14 @@ pub fn pystruct_sequence(input: proc_macro::TokenStream) -> proc_macro::TokenStr result_to_tokens(pystructseq::impl_pystruct_sequence(input)) } +#[proc_macro_derive(TryIntoPyStructSequence)] +pub fn pystruct_sequence_try_from_object( + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + result_to_tokens(pystructseq::impl_pystruct_sequence_try_from_object(input)) +} + #[proc_macro] pub fn py_compile(input: proc_macro::TokenStream) -> proc_macro::TokenStream { result_to_tokens(compile_bytecode::impl_py_compile(input.into())) diff --git a/derive/src/pystructseq.rs b/derive/src/pystructseq.rs index 08cbe6980..dce8c4768 100644 --- a/derive/src/pystructseq.rs +++ b/derive/src/pystructseq.rs @@ -1,8 +1,8 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::{DeriveInput, Result}; +use syn::{DeriveInput, Ident, Result}; -pub(crate) fn impl_pystruct_sequence(input: DeriveInput) -> Result { +fn field_names(input: &DeriveInput) -> Result> { let fields = if let syn::Data::Struct(ref struc) = input.data { &struc.fields } else { @@ -24,9 +24,14 @@ pub(crate) fn impl_pystruct_sequence(input: DeriveInput) -> Result ), }; + Ok(field_names) +} + +pub(crate) fn impl_pystruct_sequence(input: DeriveInput) -> Result { + let field_names = field_names(&input)?; let ty = &input.ident; let ret = quote! { - impl ::rustpython_vm::PyStructSequence for #ty { + impl ::rustpython_vm::types::PyStructSequence for #ty { const FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#field_names)),*]; fn into_tuple(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::builtins::PyTuple { let items = vec![#(::rustpython_vm::convert::ToPyObject::to_pyobject( @@ -38,7 +43,26 @@ pub(crate) fn impl_pystruct_sequence(input: DeriveInput) -> Result } impl ::rustpython_vm::convert::ToPyObject for #ty { fn to_pyobject(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::PyObjectRef { - ::rustpython_vm::PyStructSequence::into_struct_sequence(self, vm).into() + ::rustpython_vm::types::PyStructSequence::into_struct_sequence(self, vm).into() + } + } + }; + Ok(ret) +} + +pub(crate) fn impl_pystruct_sequence_try_from_object(input: DeriveInput) -> Result { + let field_names = field_names(&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 { + const LEN: usize = #ty::FIELD_NAMES.len(); + let seq = Self::try_elements_from::(seq, vm)?; + // TODO: this is possible to be written without iterator + let mut iter = seq.into_iter(); + Ok(Self {#( + #field_names: iter.next().unwrap().clone().try_into_value(vm)? + ),*}) } } }; diff --git a/stdlib/src/resource.rs b/stdlib/src/resource.rs index 046d86e5f..70d641974 100644 --- a/stdlib/src/resource.rs +++ b/stdlib/src/resource.rs @@ -5,7 +5,8 @@ mod resource { use crate::vm::{ convert::{ToPyException, ToPyObject}, stdlib::os, - PyObject, PyObjectRef, PyResult, PyStructSequence, TryFromBorrowedObject, VirtualMachine, + types::PyStructSequence, + PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, VirtualMachine, }; use std::{io, mem}; diff --git a/vm/src/builtins/pystr.rs b/vm/src/builtins/pystr.rs index 26f4a0f1f..1c9d0303f 100644 --- a/vm/src/builtins/pystr.rs +++ b/vm/src/builtins/pystr.rs @@ -77,6 +77,12 @@ impl PyStrKindData { } } +impl TryFromBorrowedObject for String { + fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult { + obj.try_value_with(|pystr: &PyStr| Ok(pystr.as_str().to_owned()), vm) + } +} + /// str(object='') -> str /// str(bytes_or_buffer[, encoding[, errors]]) -> str /// diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 862aba2f4..8fbdaa623 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -88,12 +88,12 @@ mod pyobject { // pub use self::Executor; pub use self::convert::{TryFromBorrowedObject, TryFromObject}; // pyobject items -pub use self::pyobject::{ - AsObject, PyContext, PyMethod, PyRefExact, PyResult, PyStructSequence, PyValue, -}; +pub use self::pyobject::{AsObject, PyContext, PyMethod, PyRefExact, PyResult, PyValue}; // pyobjectrc items pub use self::pyobject::{PyObject, PyObjectRef, PyObjectView, PyObjectWeak, PyRef, PyWeakRef}; +pub use self::types::PyStructSequence; pub use self::vm::{InitParameter, Interpreter, PySettings, VirtualMachine}; + pub use rustpython_bytecode as bytecode; pub use rustpython_common as common; #[cfg(feature = "rustpython-compiler")] diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index e3ce56451..0385283b1 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -622,66 +622,20 @@ pub trait PyObjectPayload: Any + fmt::Debug + PyThreadingConstraint + 'static {} impl PyObjectPayload for T {} -#[pyimpl] -pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { - const FIELD_NAMES: &'static [&'static str]; +pub trait PyObjectWrap +where + Self: AsObject, +{ + fn into_object(self) -> PyObjectRef; +} - 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().clone()) - .unwrap() - } - - #[pymethod(magic)] - fn repr(zelf: PyRef, vm: &VirtualMachine) -> PyResult { - let format_field = |(value, name): (&PyObjectRef, _)| { - let s = value.repr(vm)?; - Ok(format!("{}={}", name, s)) - }; - let (body, suffix) = - if let Some(_guard) = rustpython_vm::vm::ReprGuard::enter(vm, zelf.as_object()) { - if Self::FIELD_NAMES.len() == 1 { - let value = zelf.as_slice().first().unwrap(); - let formatted = format_field((value, Self::FIELD_NAMES[0]))?; - (formatted, ",") - } else { - let fields: PyResult> = zelf - .as_slice() - .iter() - .zip(Self::FIELD_NAMES.iter().copied()) - .map(format_field) - .collect(); - (fields?.join(", "), "") - } - } else { - (String::new(), "...") - }; - Ok(format!("{}({}{})", Self::TP_NAME, body, suffix)) - } - - #[pymethod(magic)] - fn reduce(zelf: PyRef, vm: &VirtualMachine) -> PyTupleRef { - vm.new_tuple(( - zelf.class().clone(), - (vm.ctx.new_tuple(zelf.as_slice().to_vec()),), - )) - } - - #[extend_class] - fn extend_pyclass(ctx: &PyContext, class: &PyTypeRef) { - for (i, &name) in Self::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; - class.set_str_attr( - name, - ctx.new_readonly_getset(name, class.clone(), move |zelf: &PyTuple| { - zelf.fast_getitem(i.into()) - }), - ); - } +impl From for PyObjectRef +where + T: PyObjectWrap, +{ + #[inline(always)] + fn from(py_ref: T) -> Self { + py_ref.into_object() } } diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index acab029cc..e1d7289b7 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -413,10 +413,10 @@ pub(super) mod _os { function::{ArgBytesLike, FuncArgs, OptionalArg}, protocol::PyIterReturn, suppress_iph, - types::{IterNext, IterNextIterable}, + types::{IterNext, IterNextIterable, PyStructSequence}, utils::Either, vm::{ReprGuard, VirtualMachine}, - AsObject, PyObjectRef, PyRef, PyResult, PyStructSequence, PyValue, TryFromObject, + AsObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, }; use crossbeam_utils::atomic::AtomicCell; use itertools::Itertools; diff --git a/vm/src/stdlib/pwd.rs b/vm/src/stdlib/pwd.rs index cea7ba6f1..bb9d5ca21 100644 --- a/vm/src/stdlib/pwd.rs +++ b/vm/src/stdlib/pwd.rs @@ -5,7 +5,8 @@ mod pwd { use crate::{ builtins::{PyIntRef, PyStrRef}, convert::{ToPyException, ToPyObject}, - AsObject, PyObjectRef, PyResult, PyStructSequence, VirtualMachine, + types::PyStructSequence, + AsObject, PyObjectRef, PyResult, VirtualMachine, }; use nix::unistd::{self, User}; use std::ptr::NonNull; diff --git a/vm/src/stdlib/sys.rs b/vm/src/stdlib/sys.rs index 938fa77f3..c6cc06d6d 100644 --- a/vm/src/stdlib/sys.rs +++ b/vm/src/stdlib/sys.rs @@ -13,9 +13,10 @@ mod sys { frame::FrameRef, function::{FuncArgs, OptionalArg, PosArgs}, stdlib::builtins, + types::PyStructSequence, version, vm::{PySettings, VirtualMachine}, - PyObjectRef, PyRef, PyRefExact, PyResult, PyStructSequence, + PyObjectRef, PyRef, PyRefExact, PyResult, }; use num_traits::ToPrimitive; use std::{env, mem, path}; diff --git a/vm/src/stdlib/time.rs b/vm/src/stdlib/time.rs index 9c7cf7493..3641c5bc2 100644 --- a/vm/src/stdlib/time.rs +++ b/vm/src/stdlib/time.rs @@ -21,8 +21,9 @@ mod time { use crate::{ builtins::{PyStrRef, PyTypeRef}, function::{FuncArgs, OptionalArg}, + types::PyStructSequence, utils::Either, - PyObjectRef, PyResult, PyStructSequence, TryFromObject, VirtualMachine, + PyObjectRef, PyResult, TryFromObject, VirtualMachine, }; use chrono::{ naive::{NaiveDate, NaiveDateTime, NaiveTime}, @@ -315,7 +316,7 @@ mod time { #[pyattr] #[pyclass(name = "struct_time")] - #[derive(PyStructSequence)] + #[derive(PyStructSequence, TryIntoPyStructSequence)] #[allow(dead_code)] struct PyStructTime { tm_year: PyObjectRef, @@ -375,29 +376,6 @@ mod time { } } - impl TryFromObject for PyStructTime { - fn try_from_object(vm: &VirtualMachine, seq: PyObjectRef) -> PyResult { - let seq: Vec<_> = seq.try_to_value(vm)?; - if seq.len() != 9 { - return Err( - vm.new_type_error("time.struct_time() takes a sequence of length 9".to_owned()) - ); - } - let mut i = seq.into_iter(); - Ok(PyStructTime { - tm_year: i.next().unwrap(), - tm_mon: i.next().unwrap(), - tm_mday: i.next().unwrap(), - tm_hour: i.next().unwrap(), - tm_min: i.next().unwrap(), - tm_sec: i.next().unwrap(), - tm_wday: i.next().unwrap(), - tm_yday: i.next().unwrap(), - tm_isdst: i.next().unwrap(), - }) - } - } - #[allow(unused_imports)] #[cfg(unix)] use super::unix::*; diff --git a/vm/src/types/mod.rs b/vm/src/types/mod.rs index 96f4e52ac..56d925bfa 100644 --- a/vm/src/types/mod.rs +++ b/vm/src/types/mod.rs @@ -1,5 +1,7 @@ mod slot; +mod structseq; mod zoo; pub use slot::*; +pub use structseq::PyStructSequence; pub(crate) use zoo::TypeZoo; diff --git a/vm/src/types/structseq.rs b/vm/src/types/structseq.rs new file mode 100644 index 000000000..d56de4fb7 --- /dev/null +++ b/vm/src/types/structseq.rs @@ -0,0 +1,92 @@ +use crate::{ + builtins::{PyTuple, PyTupleRef, PyTypeRef}, + pyclass::{PyClassImpl, StaticType}, + pyobject::PyContext, + AsObject, PyObjectRef, PyRef, PyResult, PyValue, VirtualMachine, +}; + +#[pyimpl] +pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { + const FIELD_NAMES: &'static [&'static str]; + + 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().clone()) + .unwrap() + } + + fn try_elements_from( + obj: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<[PyObjectRef; FIELD_LEN]> { + 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 = obj.try_into_value(vm)?; + let seq: [PyObjectRef; FIELD_LEN] = seq.try_into().map_err(|_| { + vm.new_type_error(format!( + "{} takes a sequence of length {}", + typ.name(), + FIELD_LEN + )) + })?; + Ok(seq) + } + + #[pymethod(magic)] + fn repr(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + let format_field = |(value, name): (&PyObjectRef, _)| { + let s = value.repr(vm)?; + Ok(format!("{}={}", name, s)) + }; + let (body, suffix) = + if let Some(_guard) = rustpython_vm::vm::ReprGuard::enter(vm, zelf.as_object()) { + if Self::FIELD_NAMES.len() == 1 { + let value = zelf.as_slice().first().unwrap(); + let formatted = format_field((value, Self::FIELD_NAMES[0]))?; + (formatted, ",") + } else { + let fields: PyResult> = zelf + .as_slice() + .iter() + .zip(Self::FIELD_NAMES.iter().copied()) + .map(format_field) + .collect(); + (fields?.join(", "), "") + } + } else { + (String::new(), "...") + }; + Ok(format!("{}({}{})", Self::TP_NAME, body, suffix)) + } + + #[pymethod(magic)] + fn reduce(zelf: PyRef, vm: &VirtualMachine) -> PyTupleRef { + vm.new_tuple(( + zelf.class().clone(), + (vm.ctx.new_tuple(zelf.as_slice().to_vec()),), + )) + } + + #[extend_class] + fn extend_pyclass(ctx: &PyContext, class: &PyTypeRef) { + for (i, &name) in Self::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; + class.set_str_attr( + name, + ctx.new_readonly_getset(name, class.clone(), move |zelf: &PyTuple| { + zelf.fast_getitem(i.into()) + }), + ); + } + } +} diff --git a/vm/src/version.rs b/vm/src/version.rs index f29870277..cd26435df 100644 --- a/vm/src/version.rs +++ b/vm/src/version.rs @@ -1,7 +1,7 @@ /* Several function to retrieve version information. */ -use crate::PyStructSequence; +use crate::types::PyStructSequence; use chrono::prelude::DateTime; use chrono::Local; use std::time::{Duration, UNIX_EPOCH};