Skip to content

Commit 6ffb2d8

Browse files
committed
introduce slot_wrapper
1 parent 014622a commit 6ffb2d8

File tree

9 files changed

+293
-18
lines changed

9 files changed

+293
-18
lines changed

Lib/test/test_descr.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4941,7 +4941,6 @@ def __init__(self):
49414941
for o in gc.get_objects():
49424942
self.assertIsNot(type(o), X)
49434943

4944-
@unittest.expectedFailure # TODO: RUSTPYTHON
49454944
def test_object_new_and_init_with_parameters(self):
49464945
# See issue #1683368
49474946
class OverrideNeither:

Lib/test/test_weakref.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -906,8 +906,6 @@ def __del__(self):
906906

907907
w = Target()
908908

909-
# TODO: RUSTPYTHON
910-
@unittest.expectedFailure
911909
def test_init(self):
912910
# Issue 3634
913911
# <weakref to class>.__init__() doesn't check errors correctly

crates/vm/src/builtins/descriptor.rs

Lines changed: 202 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ use crate::{
33
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
44
builtins::{PyTypeRef, builtin_func::PyNativeMethod, type_},
55
class::PyClassImpl,
6+
common::hash::PyHash,
67
function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue},
7-
types::{Callable, GetDescriptor, Representable},
8+
types::{
9+
Callable, Comparable, GetDescriptor, Hashable, InitFunc, PyComparisonOp, Representable,
10+
},
811
};
912
use rustpython_common::lock::PyRwLock;
1013

@@ -219,7 +222,7 @@ impl std::fmt::Debug for PyMemberDef {
219222
}
220223
}
221224

222-
// PyMemberDescrObject in CPython
225+
// = PyMemberDescrObject
223226
#[pyclass(name = "member_descriptor", module = false)]
224227
#[derive(Debug)]
225228
pub struct PyMemberDescriptor {
@@ -382,4 +385,201 @@ impl GetDescriptor for PyMemberDescriptor {
382385
pub fn init(ctx: &Context) {
383386
PyMemberDescriptor::extend_class(ctx, ctx.types.member_descriptor_type);
384387
PyMethodDescriptor::extend_class(ctx, ctx.types.method_descriptor_type);
388+
PySlotWrapper::extend_class(ctx, ctx.types.wrapper_descriptor_type);
389+
PyMethodWrapper::extend_class(ctx, ctx.types.method_wrapper_type);
390+
}
391+
392+
// PySlotWrapper - wrapper_descriptor
393+
394+
/// wrapper_descriptor: wraps a slot function as a Python method
395+
// = PyWrapperDescrObject
396+
#[pyclass(name = "wrapper_descriptor", module = false)]
397+
#[derive(Debug)]
398+
pub struct PySlotWrapper {
399+
pub typ: &'static Py<PyType>,
400+
pub name: &'static PyStrInterned,
401+
pub wrapped: InitFunc,
402+
pub doc: Option<&'static str>,
403+
}
404+
405+
impl PyPayload for PySlotWrapper {
406+
fn class(ctx: &Context) -> &'static Py<PyType> {
407+
ctx.types.wrapper_descriptor_type
408+
}
409+
}
410+
411+
impl GetDescriptor for PySlotWrapper {
412+
fn descr_get(
413+
zelf: PyObjectRef,
414+
obj: Option<PyObjectRef>,
415+
_cls: Option<PyObjectRef>,
416+
vm: &VirtualMachine,
417+
) -> PyResult {
418+
match obj {
419+
None => Ok(zelf),
420+
Some(obj) if vm.is_none(&obj) => Ok(zelf),
421+
Some(obj) => {
422+
let zelf = zelf.downcast::<Self>().unwrap();
423+
Ok(PyMethodWrapper { wrapper: zelf, obj }.into_pyobject(vm))
424+
}
425+
}
426+
}
427+
}
428+
429+
impl Callable for PySlotWrapper {
430+
type Args = FuncArgs;
431+
432+
fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
433+
// list.__init__(l, [1,2,3]) form
434+
let (obj, rest): (PyObjectRef, FuncArgs) = args.bind(vm)?;
435+
436+
if !obj.fast_isinstance(zelf.typ) {
437+
return Err(vm.new_type_error(format!(
438+
"descriptor '{}' requires a '{}' object but received a '{}'",
439+
zelf.name.as_str(),
440+
zelf.typ.name(),
441+
obj.class().name()
442+
)));
443+
}
444+
445+
(zelf.wrapped)(obj, rest, vm)?;
446+
Ok(vm.ctx.none())
447+
}
448+
}
449+
450+
#[pyclass(
451+
with(GetDescriptor, Callable, Representable),
452+
flags(DISALLOW_INSTANTIATION)
453+
)]
454+
impl PySlotWrapper {
455+
#[pygetset]
456+
fn __name__(&self) -> &'static PyStrInterned {
457+
self.name
458+
}
459+
460+
#[pygetset]
461+
fn __qualname__(&self) -> String {
462+
format!("{}.{}", self.typ.name(), self.name)
463+
}
464+
465+
#[pygetset]
466+
fn __objclass__(&self) -> PyTypeRef {
467+
self.typ.to_owned()
468+
}
469+
470+
#[pygetset]
471+
fn __doc__(&self) -> Option<&'static str> {
472+
self.doc
473+
}
474+
}
475+
476+
impl Representable for PySlotWrapper {
477+
#[inline]
478+
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
479+
Ok(format!(
480+
"<slot wrapper '{}' of '{}' objects>",
481+
zelf.name.as_str(),
482+
zelf.typ.name()
483+
))
484+
}
485+
}
486+
487+
// PyMethodWrapper - method-wrapper
488+
489+
/// method-wrapper: a slot wrapper bound to an instance
490+
/// Returned when accessing l.__init__ on an instance
491+
#[pyclass(name = "method-wrapper", module = false, traverse)]
492+
#[derive(Debug)]
493+
pub struct PyMethodWrapper {
494+
pub wrapper: PyRef<PySlotWrapper>,
495+
#[pytraverse(skip)]
496+
pub obj: PyObjectRef,
497+
}
498+
499+
impl PyPayload for PyMethodWrapper {
500+
fn class(ctx: &Context) -> &'static Py<PyType> {
501+
ctx.types.method_wrapper_type
502+
}
503+
}
504+
505+
impl Callable for PyMethodWrapper {
506+
type Args = FuncArgs;
507+
508+
fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
509+
(zelf.wrapper.wrapped)(zelf.obj.clone(), args, vm)?;
510+
Ok(vm.ctx.none())
511+
}
512+
}
513+
514+
#[pyclass(
515+
with(Callable, Representable, Hashable, Comparable),
516+
flags(DISALLOW_INSTANTIATION)
517+
)]
518+
impl PyMethodWrapper {
519+
#[pygetset]
520+
fn __self__(&self) -> PyObjectRef {
521+
self.obj.clone()
522+
}
523+
524+
#[pygetset]
525+
fn __name__(&self) -> &'static PyStrInterned {
526+
self.wrapper.name
527+
}
528+
529+
#[pygetset]
530+
fn __objclass__(&self) -> PyTypeRef {
531+
self.wrapper.typ.to_owned()
532+
}
533+
534+
#[pymethod]
535+
fn __reduce__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
536+
let builtins_getattr = vm.builtins.get_attr("getattr", vm)?;
537+
Ok(vm
538+
.ctx
539+
.new_tuple(vec![
540+
builtins_getattr,
541+
vm.ctx
542+
.new_tuple(vec![
543+
zelf.obj.clone(),
544+
vm.ctx.new_str(zelf.wrapper.name.as_str()).into(),
545+
])
546+
.into(),
547+
])
548+
.into())
549+
}
550+
}
551+
552+
impl Representable for PyMethodWrapper {
553+
#[inline]
554+
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
555+
Ok(format!(
556+
"<method-wrapper '{}' of {} object at {:#x}>",
557+
zelf.wrapper.name.as_str(),
558+
zelf.obj.class().name(),
559+
zelf.obj.get_id()
560+
))
561+
}
562+
}
563+
564+
impl Hashable for PyMethodWrapper {
565+
fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
566+
let obj_hash = zelf.obj.hash(vm)?;
567+
let wrapper_hash = zelf.wrapper.as_object().get_id() as PyHash;
568+
Ok(obj_hash ^ wrapper_hash)
569+
}
570+
}
571+
572+
impl Comparable for PyMethodWrapper {
573+
fn cmp(
574+
zelf: &Py<Self>,
575+
other: &PyObject,
576+
op: PyComparisonOp,
577+
vm: &VirtualMachine,
578+
) -> PyResult<crate::function::PyComparisonValue> {
579+
op.eq_only(|| {
580+
let other = class_or_notimplemented!(Self, other);
581+
let eq = zelf.wrapper.is(&other.wrapper) && vm.bool_eq(&zelf.obj, &other.obj)?;
582+
Ok(eq.into())
583+
})
584+
}
385585
}

crates/vm/src/builtins/object.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,46 @@ impl Constructor for PyBaseObject {
118118
impl Initializer for PyBaseObject {
119119
type Args = FuncArgs;
120120

121-
fn slot_init(_zelf: PyObjectRef, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> {
121+
// object_init: excess_args validation
122+
fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
123+
let typ = zelf.class();
124+
let object_type = &vm.ctx.types.object_type;
125+
126+
let typ_init = typ.slots.init.load().map(|f| f as usize);
127+
let object_init = object_type.slots.init.load().map(|f| f as usize);
128+
let typ_new = typ.slots.new.load().map(|f| f as usize);
129+
let object_new = object_type.slots.new.load().map(|f| f as usize);
130+
131+
// For heap types (Python classes), check if __new__ is defined anywhere in MRO
132+
// (before object) because heap types always have slots.new = new_wrapper via MRO
133+
let is_heap_type = typ
134+
.slots
135+
.flags
136+
.contains(crate::types::PyTypeFlags::HEAPTYPE);
137+
let new_overridden = if is_heap_type {
138+
// Check if __new__ is defined in any base class (excluding object)
139+
let new_id = identifier!(vm, __new__);
140+
typ.mro_collect()
141+
.into_iter()
142+
.take_while(|t| !std::ptr::eq(t.as_ref(), *object_type))
143+
.any(|t| t.attributes.read().contains_key(new_id))
144+
} else {
145+
// For built-in types, use slot comparison
146+
typ_new != object_new
147+
};
148+
149+
// If both __init__ and __new__ are overridden, allow excess args
150+
if typ_init != object_init && new_overridden {
151+
return Ok(());
152+
}
153+
154+
// Otherwise, reject excess args
155+
if !args.is_empty() {
156+
return Err(vm.new_type_error(format!(
157+
"{}.__init__() takes exactly one argument (the instance to initialize)",
158+
typ.name()
159+
)));
160+
}
122161
Ok(())
123162
}
124163

@@ -456,6 +495,11 @@ impl PyBaseObject {
456495
obj.str(vm)
457496
}
458497

498+
#[pymethod]
499+
fn __init__(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
500+
<Self as Initializer>::slot_init(zelf, args, vm)
501+
}
502+
459503
#[pygetset]
460504
fn __class__(obj: PyObjectRef) -> PyTypeRef {
461505
obj.class().to_owned()

crates/vm/src/builtins/weakref.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ use crate::common::{
44
hash::{self, PyHash},
55
};
66
use crate::{
7-
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
7+
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
88
class::PyClassImpl,
99
function::{FuncArgs, OptionalArg},
10-
types::{Callable, Comparable, Constructor, Hashable, PyComparisonOp, Representable},
10+
types::{
11+
Callable, Comparable, Constructor, Hashable, Initializer, PyComparisonOp, Representable,
12+
},
1113
};
1214

1315
pub use crate::object::PyWeak;
@@ -49,8 +51,24 @@ impl Constructor for PyWeak {
4951
}
5052
}
5153

54+
impl Initializer for PyWeak {
55+
type Args = WeakNewArgs;
56+
57+
// weakref_tp_init: accepts args but does nothing (all init done in slot_new)
58+
fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
59+
Ok(())
60+
}
61+
}
62+
5263
#[pyclass(
53-
with(Callable, Hashable, Comparable, Constructor, Representable),
64+
with(
65+
Callable,
66+
Hashable,
67+
Comparable,
68+
Constructor,
69+
Initializer,
70+
Representable
71+
),
5472
flags(BASETYPE)
5573
)]
5674
impl PyWeak {

crates/vm/src/class.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//! Utilities to define a new Python class
22
33
use crate::{
4-
builtins::{PyBaseObject, PyType, PyTypeRef},
4+
PyPayload,
5+
builtins::{PyBaseObject, PyType, PyTypeRef, descriptor::PySlotWrapper},
56
function::PyMethodDef,
67
object::Py,
78
types::{PyTypeFlags, PyTypeSlots, hash_not_implemented},
@@ -135,6 +136,20 @@ pub trait PyClassImpl: PyClassDef {
135136
}
136137
}
137138

139+
// Add __init__ slot wrapper if slot exists and not already in dict
140+
if let Some(init_func) = class.slots.init.load() {
141+
let init_name = identifier!(ctx, __init__);
142+
if !class.attributes.read().contains_key(init_name) {
143+
let wrapper = PySlotWrapper {
144+
typ: class,
145+
name: ctx.intern_str("__init__"),
146+
wrapped: init_func,
147+
doc: Some("Initialize self. See help(type(self)) for accurate signature."),
148+
};
149+
class.set_attr(init_name, wrapper.into_ref(ctx).into());
150+
}
151+
}
152+
138153
if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize {
139154
class.set_attr(ctx.names.__hash__, ctx.none.clone().into());
140155
}

crates/vm/src/stdlib/io.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,17 +1464,12 @@ mod _io {
14641464
#[pyslot]
14651465
fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
14661466
let zelf: PyRef<Self> = zelf.try_into_value(vm)?;
1467-
zelf.__init__(args, vm)
1468-
}
1469-
1470-
#[pymethod]
1471-
fn __init__(&self, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
14721467
let (raw, BufferSize { buffer_size }): (PyObjectRef, _) =
14731468
args.bind(vm).map_err(|e| {
14741469
let msg = format!("{}() {}", Self::CLASS_NAME, *e.__str__(vm));
14751470
vm.new_exception_msg(e.class().to_owned(), msg)
14761471
})?;
1477-
self.init(raw, BufferSize { buffer_size }, vm)
1472+
zelf.init(raw, BufferSize { buffer_size }, vm)
14781473
}
14791474

14801475
fn init(

0 commit comments

Comments
 (0)