Skip to content
Merged
13 changes: 12 additions & 1 deletion crates/derive-impl/src/from_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,25 @@ fn generate_field((i, field): (usize, &Field)) -> Result<TokenStream> {
.or(name_string)
.ok_or_else(|| err_span!(field, "field in tuple struct must have name attribute"))?;

let convert = match attr.kind {
ParameterKind::PositionalOnly => quote! {
|x| ::rustpython_vm::convert::TryFromObject::try_from_argument(vm, x)
},
ParameterKind::PositionalOrKeyword | ParameterKind::KeywordOnly => quote! {
|x| ::rustpython_vm::convert::TryFromObject::try_from_argument(vm, x)
.map_err(|err| err.with_argument_name(#pyname))
},
ParameterKind::Flatten => unreachable!(),
};

let middle = if let Some(error_msg) = &attr.error_msg {
quote! {
.map(|x| ::rustpython_vm::convert::TryFromObject::try_from_object(vm, x)
.map_err(|_| vm.new_type_error(#error_msg))).transpose()?
}
} else {
quote! {
.map(|x| ::rustpython_vm::convert::TryFromObject::try_from_object(vm, x)).transpose()?
.map(#convert).transpose()?
}
};

Expand Down
2 changes: 1 addition & 1 deletion crates/derive-impl/src/from_args/integer_alias_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn integer_alias_literal_defaults_stay_native() {
assert!(!tokens.contains("ToPyObject"));
assert!(
tokens.contains(
&quote!(::rustpython_vm::convert::TryFromObject::try_from_object(
&quote!(::rustpython_vm::convert::TryFromObject::try_from_argument(
vm, x
))
.to_string()
Expand Down
36 changes: 13 additions & 23 deletions crates/stdlib/src/_asyncio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub(crate) mod _asyncio {
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
builtins::{
PyBaseException, PyBaseExceptionRef, PyDict, PyGenericAlias, PyList, PyListRef,
PyModule, PySet, PyTraceback, PyTuple, PyType, PyTypeRef,
PyModule, PySet, PyTraceback, PyTuple, PyType, PyTypeRef, alloc_default,
},
extend_module,
function::{FuncArgs, KwArgs, OptionalArg, PySetterValue},
Expand Down Expand Up @@ -960,23 +960,18 @@ pub(crate) mod _asyncio {

#[pyattr]
#[pyclass(name = "FutureIter", module = "_asyncio", traverse)]
#[derive(Debug, PyPayload)]
#[derive(Debug, Default, PyPayload)]
struct PyFutureIter {
future: PyRwLock<Option<PyObjectRef>>,
}

impl Constructor for PyFutureIter {
type Args = ();

fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
Ok(Self {
future: PyRwLock::new(None),
})
#[pyclass(with(IterNext, Iterable), flags(HEAPTYPE))]
impl PyFutureIter {
#[pyslot]
fn slot_alloc(cls: PyTypeRef, nitems: usize, vm: &VirtualMachine) -> PyResult {
alloc_default::<Self>(cls, nitems, vm)
}
}

#[pyclass(with(Constructor, IterNext, Iterable))]
impl PyFutureIter {
#[pymethod]
fn send(zelf: &Py<Self>, _value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
let future = zelf.future.read().clone();
Expand Down Expand Up @@ -2931,23 +2926,18 @@ pub(crate) mod _asyncio {

#[pyattr]
#[pyclass(name, traverse)]
#[derive(Debug, PyPayload)]
#[derive(Debug, Default, PyPayload)]
struct TaskStepMethWrapper {
task: PyRwLock<Option<PyObjectRef>>,
}

impl Constructor for TaskStepMethWrapper {
type Args = ();

fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
Ok(Self {
task: PyRwLock::new(None),
})
#[pyclass(with(Callable, Representable), flags(HEAPTYPE))]
impl TaskStepMethWrapper {
#[pyslot]
fn slot_alloc(cls: PyTypeRef, nitems: usize, vm: &VirtualMachine) -> PyResult {
alloc_default::<Self>(cls, nitems, vm)
}
}

#[pyclass(with(Constructor, Callable, Representable))]
impl TaskStepMethWrapper {
fn new(task: PyObjectRef) -> Self {
Self {
task: PyRwLock::new(Some(task)),
Expand Down
2 changes: 1 addition & 1 deletion crates/vm/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub(crate) mod namespace;
pub use namespace::PyNamespace;
pub(crate) mod object;
pub use object::PyBaseObject;
pub use object::{object_generic_set_dict, object_get_dict};
pub use object::{alloc_default, object_generic_set_dict, object_get_dict};
pub(crate) mod property;
pub use property::PyProperty;
#[path = "bool.rs"]
Expand Down
32 changes: 20 additions & 12 deletions crates/vm/src/builtins/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
use itertools::Itertools;

#[pyclass(module = false, name = "object")]
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct PyBaseObject;

impl PyPayload for PyBaseObject {
Expand Down Expand Up @@ -64,7 +64,9 @@ impl Constructor for PyBaseObject {
{
// Both __new__ and __init__ are object's versions,
// so the type accepts no arguments
return Err(vm.new_type_error(format!("{}() takes no arguments", cls.name())));
return Err(
vm.new_type_error(format!("{}() takes no arguments", cls.slot_name()))
);
}
// If tp_init != object_init, then the type has custom __init__
// which might accept arguments, so we allow it
Expand Down Expand Up @@ -93,28 +95,34 @@ impl Constructor for PyBaseObject {
}
}

generic_alloc(cls, 0, vm)
// Every type inherits tp_alloc via set_alloc; object keeps generic_alloc.
let alloc = cls.slots.alloc.load().unwrap_or(generic_alloc);
alloc(cls, 0, vm)
}

fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
unimplemented!("use slot_new")
}
}

#[expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")]
pub(crate) fn generic_alloc(cls: PyTypeRef, _nitems: usize, vm: &VirtualMachine) -> PyResult {
/// Allocate `T::default()` with the same HAS_DICT handling as object tp_alloc.
pub fn alloc_default<T: PyPayload + Default + core::fmt::Debug>(
cls: PyTypeRef,
_nitems: usize,
vm: &VirtualMachine,
) -> PyResult {
// Only create dict if the class has HAS_DICT flag (i.e., __slots__ was not defined
// or __dict__ is in __slots__)
let dict = if cls
.slots
.flags
.has_feature(crate::types::PyTypeFlags::HAS_DICT)
{
let dict = if cls.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
Some(vm.ctx.new_dict())
} else {
None
};
Ok(crate::PyRef::new_ref(PyBaseObject, cls, dict).into())
Ok(crate::PyRef::new_ref(T::default(), cls, dict).into())
}

pub(crate) fn generic_alloc(cls: PyTypeRef, nitems: usize, vm: &VirtualMachine) -> PyResult {
alloc_default::<PyBaseObject>(cls, nitems, vm)
}

impl Initializer for PyBaseObject {
Expand Down Expand Up @@ -151,7 +159,7 @@ impl Initializer for PyBaseObject {
{
return Err(vm.new_type_error(format!(
"{}.__init__() takes exactly one argument (the instance to initialize)",
typ.name()
typ.slot_name()
)));
}

Expand Down
34 changes: 34 additions & 0 deletions crates/vm/src/convert/try_from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use num_traits::ToPrimitive;
use crate::{
Py, VirtualMachine,
builtins::PyFloat,
function::ArgumentError,
object::{AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult},
};

Expand All @@ -14,6 +15,13 @@ use crate::{
pub trait TryFromObject: Sized {
/// Attempt to convert a Python object to a value of this type.
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self>;

/// Convert a call argument. `PyRef<T>` turns an isinstance failure into
/// `_PyArg_BadArgument`; other types keep `try_from_object`.
#[inline]
fn try_from_argument(vm: &VirtualMachine, obj: PyObjectRef) -> Result<Self, ArgumentError> {
Self::try_from_object(vm, obj).map_err(Into::into)
}
}

/// Rust-side only version of TryFromObject to reduce unnecessary Rc::clone
Expand Down Expand Up @@ -86,6 +94,22 @@ where
Err(vm.new_downcast_type_error(class, &obj))
}
}

#[inline]
fn try_from_argument(vm: &VirtualMachine, obj: PyObjectRef) -> Result<Self, ArgumentError> {
let class = T::class(&vm.ctx);
if obj.fast_isinstance(class) {
T::try_downcast_from(&obj, vm)?;
Ok(unsafe { obj.downcast_unchecked() })
} else {
Err(ArgumentError::BadArgument {
expected: class,
or_none: false,
found: obj,
name: None,
})
}
}
}

impl TryFromObject for PyObjectRef {
Expand All @@ -103,6 +127,16 @@ impl<T: TryFromObject> TryFromObject for Option<T> {
T::try_from_object(vm, obj).map(Some)
}
}

fn try_from_argument(vm: &VirtualMachine, obj: PyObjectRef) -> Result<Self, ArgumentError> {
if vm.is_none(&obj) {
Ok(None)
} else {
T::try_from_argument(vm, obj)
.map(Some)
.map_err(ArgumentError::with_or_none)
}
}
}

impl<'a, T: 'a + TryFromObject> TryFromBorrowedObject<'a> for Vec<T> {
Expand Down
Loading
Loading