8000 Introduce slot wrapper to __init__ by youknowone · Pull Request #4884 · RustPython/RustPython · GitHub
[go: up one dir, main page]

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4941,7 +4941,6 @@ def __init__(self):
for o in gc.get_objects():
self.assertIsNot(type(o), X)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_object_new_and_init_with_parameters(self):
# See issue #1683368
class OverrideNeither:
Expand Down
3 changes: 1 addition & 2 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ class NotFuture: pass

coro.close(); gen_coro.close() # silence warnings

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_isroutine(self):
# method
self.assertTrue(inspect.isroutine(git.argue))
Expand Down Expand Up @@ -1483,8 +1484,6 @@ def test_getfullargspec_definition_order_preserved_on_kwonly(self):
l = list(signature.kwonlyargs)
self.assertEqual(l, unsorted_keyword_only_parameters)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_classify_newstyle(self):
class A(object):

Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ def test_internal_sizes(self):
self.assertGreater(object.__basicsize__, 0)
self.assertGreater(tuple.__itemsize__, 0)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_slot_wrapper_types(self):
self.assertIsInstance(object.__init__, types.WrapperDescriptorType)
self.assertIsInstance(object.__str__, types.WrapperDescriptorType)
Expand All @@ -611,6 +612,7 @@ def test_dunder_get_signature(self):
# gh-93021: Second parameter is optional
self.assertIs(sig.parameters["owner"].default, None)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_method_wrapper_types(self):
self.assertIsInstance(object().__init__, types.MethodWrapperType)
self.assertIsInstance(object().__str__, types.MethodWrapperType)
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_weakref.py
Original file line number Diff line number Diff line change
Expand Up @@ -906,8 +906,6 @@ def __del__(self):

w = Target()

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_init(self):
# Issue 3634
# <weakref to class>.__init__() doesn't check errors correctly
Expand Down
204 changes: 202 additions & 2 deletions crates/vm/src/builtins/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
builtins::{PyTypeRef, builtin_func::PyNativeMethod, type_},
class::PyClassImpl,
common::hash::PyHash,
function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue},
types::{Callable, GetDescriptor, Representable},
types::{
Callable, Comparable, GetDescriptor, Hashable, InitFunc, PyComparisonOp, Representable,
},
};
use rustpython_common::lock::PyRwLock;

Expand Down Expand Up @@ -219,7 +222,7 @@ impl std::fmt::Debug for PyMemberDef {
}
}

// PyMemberDescrObject in CPython
// = PyMemberDescrObject
#[pyclass(name = "member_descriptor", module = false)]
#[derive(Debug)]
pub struct PyMemberDescriptor {
Expand Down Expand Up @@ -382,4 +385,201 @@ impl GetDescriptor for PyMemberDescriptor {
pub fn init(ctx: &Context) {
PyMemberDescriptor::extend_class(ctx, ctx.types.member_descriptor_type);
PyMethodDescriptor::extend_class(ctx, ctx.types.method_descriptor_type);
PySlotWrapper::extend_class(ctx, ctx.types.wrapper_descriptor_type);
PyMethodWrapper::extend_class(ctx, ctx.types.method_wrapper_type);
}

// PySlotWrapper - wrapper_descriptor

/// wrapper_descriptor: wraps a slot function as a Python method
// = PyWrapperDescrObject
#[pyclass(name = "wrapper_descriptor", module = false)]
#[derive(Debug)]
pub struct PySlotWrapper {
pub typ: &'static Py<PyType>,
pub name: &'static PyStrInterned,
pub wrapped: InitFunc,
pub doc: Option<&'static str>,
}

impl PyPayload for PySlotWrapper {
fn class(ctx: &Context) -> &'static Py<PyType> {
ctx.types.wrapper_descriptor_type
}
}

impl GetDescriptor for PySlotWrapper {
fn descr_get(
zelf: PyObjectRef,
obj: Option<PyObjectRef>,
_cls: Option<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult {
match obj {
None => Ok(zelf),
Some(obj) if vm.is_none(&obj) => Ok(zelf),
Some(obj) => {
let zelf = zelf.downcast::<Self>().unwrap();
Ok(PyMethodWrapper { wrapper: zelf, obj }.into_pyobject(vm))
}
}
}
}

impl Callable for PySlotWrapper {
type Args = FuncArgs;

fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
// list.__init__(l, [1,2,3]) form
let (obj, rest): (PyObjectRef, FuncArgs) = args.bind(vm)?;

if !obj.fast_isinstance(zelf.typ) {
return Err(vm.new_type_error(format!(
"descriptor '{}' requires a '{}' object but received a '{}'",
zelf.name.as_str(),
zelf.typ.name(),
obj.class().name()
)));
}

(zelf.wrapped)(obj, rest, vm)?;
Ok(vm.ctx.none())
}
}

#[pyclass(
with(GetDescriptor, Callable, Representable),
flags(DISALLOW_INSTANTIATION)
)]
impl PySlotWrapper {
#[pygetset]
fn __name__(&self) -> &'static PyStrInterned {
self.name
}

#[pygetset]
fn __qualname__(&self) -> String {
format!("{}.{}", self.typ.name(), self.name)
}

#[pygetset]
fn __objclass__(&self) -> PyTypeRef {
self.typ.to_owned()
}

#[pygetset]
fn __doc__(&self) -> Option<&'static str> {
self.doc
}
}

impl Representable for PySlotWrapper {
#[inline]
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
Ok(format!(
"<slot wrapper '{}' of '{}' objects>",
zelf.name.as_str(),
zelf.typ.name()
))
}
}

// PyMethodWrapper - method-wrapper

/// method-wrapper: a slot wrapper bound to an instance
/// Returned when accessing l.__init__ on an instance
#[pyclass(name = "method-wrapper", module = false, traverse)]
#[derive(Debug)]
pub struct PyMethodWrapper {
pub wrapper: PyRef<PySlotWrapper>,
#[pytraverse(skip)]
pub obj: PyObjectRef,
}

impl PyPayload for PyMethodWrapper {
fn class(ctx: &Context) -> &'static Py<PyType> {
ctx.types.method_wrapper_type
}
}

impl Callable for PyMethodWrapper {
type Args = FuncArgs;

fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
(zelf.wrapper.wrapped)(zelf.obj.clone(), args, vm)?;
Ok(vm.ctx.none())
}
}

#[pyclass(
with(Callable, Representable, Hashable, Comparable),
flags(DISALLOW_INSTANTIATION)
)]
impl PyMethodWrapper {
#[pygetset]
fn __self__(&self) -> PyObjectRef {
self.obj.clone()
}

#[pygetset]
fn __name__(&self) -> &'static PyStrInterned {
self.wrapper.name
}

#[pygetset]
fn __objclass__(&self) -> PyTypeRef {
self.wrapper.typ.to_owned()
}

#[pymethod]
fn __reduce__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
let builtins_getattr = vm.builtins.get_attr("getattr", vm)?;
Ok(vm
.ctx
.new_tuple(vec![
builtins_getattr,
vm.ctx
.new_tuple(vec![
zelf.obj.clone(),
vm.ctx.new_str(zelf.wrapper.name.as_str()).into(),
])
.into(),
])
.into())
}
}

impl Representable for PyMethodWrapper {
#[inline]
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
Ok(format!(
"<method-wrapper '{}' of {} object at {:#x}>",
zelf.wrapper.name.as_str(),
zelf.obj.class().name(),
zelf.obj.get_id()
))
}
}

impl Hashable for PyMethodWrapper {
fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
let obj_hash = zelf.obj.hash(vm)?;
let wrapper_hash = zelf.wrapper.as_object().get_id() as PyHash;
Ok(obj_hash ^ wrapper_hash)
}
}

impl Comparable for PyMethodWrapper {
fn cmp(
zelf: &Py<Self>,
other: &PyObject,
op: PyComparisonOp,
vm: &VirtualMachine,
) -> PyResult<crate::function::PyComparisonValue> {
op.eq_only(|| {
let other = class_or_notimplemented!(Self, other);
let eq = zelf.wrapper.is(&other.wrapper) && vm.bool_eq(&zelf.obj, &other.obj)?;
Ok(eq.into())
})
}
}
41 changes: 40 additions & 1 deletion crates/vm/src/builtins/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,46 @@ impl Constructor for PyBaseObject {
impl Initializer for PyBaseObject {
type Args = FuncArgs;

fn slot_init(_zelf: PyObjectRef, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> {
// object_init: excess_args validation
fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
let typ = zelf.class();
let object_type = &vm.ctx.types.object_type;

let typ_init = typ.slots.init.load().map(|f| f as usize);
let object_init = object_type.slots.init.load().map(|f| f as usize);
let typ_new = typ.slots.new.load().map(|f| f as usize);
let object_new = object_type.slots.new.load().map(|f| f as usize);

// For heap types (Python classes), check if __new__ is defined anywhere in MRO
// (before object) because heap types always have slots.new = new_wrapper via MRO
let is_heap_type = typ
.slots
.flags
.contains(crate::types::PyTypeFlags::HEAPTYPE);
let new_overridden = if is_heap_type {
// Check if __new__ is defined in any base class (excluding object)
let new_id = identifier!(vm, __new__);
typ.mro_collect()
.into_iter()
.take_while(|t| !std::ptr::eq(t.as_ref(), *object_type))
.any(|t| t.attributes.read().contains_key(new_id))
} else {
// For built-in types, use slot comparison
typ_new != object_new
};

// If both __init__ and __new__ are overridden, allow excess args
if typ_init != object_init && new_overridden {
return Ok(());
}

// Otherwise, reject excess args
if !args.is_empty() {
return Err(vm.new_type_error(format!(
"{}.__init__() takes exactly one argument (the instance to initialize)",
typ.name()
)));
}
Ok(())
}

Expand Down
24 changes: 21 additions & 3 deletions crates/vm/src/builtins/weakref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ use crate::common::{
hash::{self, PyHash},
};
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
class::PyClassImpl,
function::{FuncArgs, OptionalArg},
types::{Callable, Comparable, Constructor, Hashable, PyComparisonOp, Representable},
types::{
Callable, Comparable, Constructor, Hashable, Initializer, PyComparisonOp, Representable,
},
};

pub use crate::object::PyWeak;
Expand Down Expand Up @@ -49,8 +51,24 @@ impl Constructor for PyWeak {
}
}

impl Initializer for PyWeak {
type Args = WeakNewArgs;

// weakref_tp_init: accepts args but does nothing (all init done in slot_new)
fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
Ok(())
}
}

#[pyclass(
with(Callable, Hashable, Comparable, Constructor, Representable),
with(
Callable,
Hashable,
Comparable,
Constructor,
Initializer,
Representable
),
flags(BASETYPE)
)]
impl PyWeak {
Expand Down
17 changes: 16 additions & 1 deletion crates/vm/src/class.rs
7044
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! Utilities to define a new Python class

use crate::{
builtins::{PyBaseObject, PyType, PyTypeRef},
PyPayload,
builtins::{PyBaseObject, PyType, PyTypeRef, descriptor::PySlotWrapper},
function::PyMethodDef,
object::Py,
types::{PyTypeFlags, PyTypeSlots, hash_not_implemented},
Expand Down Expand Up @@ -135,6 +136,20 @@ pub trait PyClassImpl: PyClassDef {
}
}

// Add __init__ slot wrapper if slot exists and not already in dict
if let Some(init_func) = class.slots.init.load() {
let init_name = identifier!(ctx, __init__);
if !class.attributes.read().contains_key(init_name) {
let wrapper = PySlotWrapper {
typ: class,
name: ctx.intern_str("__init__"),
wrapped: init_func,
doc: Some("Initialize self. See help(type(self)) for accurate signature."),
};
class.set_attr(init_name, wrapper.into_ref(ctx).into());
}
}

if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize {
class.set_attr(ctx.names.__hash__, ctx.none.clone().into());
}
Expand Down
Loading
Loading
0