Skip to content

Commit 3a803e3

Browse files
committed
introduce slot_wrapper
1 parent 25125e5 commit 3a803e3

File tree

11 files changed

+284
-118
lines changed

11 files changed

+284
-118
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: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -118,41 +118,45 @@ impl Constructor for PyBaseObject {
118118
impl Initializer for PyBaseObject {
119119
type Args = FuncArgs;
120120

121+
// object_init: excess_args validation (matches CPython's object_init)
121122
fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
122123
let typ = zelf.class();
123-
if typ.slots.init.load().map_or(0, |f| f as usize)
124-
== vm
125-
.ctx
126-
.types
127-
.object_type
128-
.slots
129-
.init
130-
.load()
131-
.map_or(0, |f| f as usize)
132-
{
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 {
133151
return Ok(());
134152
}
153+
154+
// Otherwise, reject excess args
135155
if !args.is_empty() {
136-
// if typ.slots.init.load().map_or(0, |f| f as usize) != vm.ctx.types.object_type.slots.init.load().map_or(0, |f| f as usize) {
137-
// return Err(vm.new_type_error(format!(
138-
// "object.__init__() takes exactly one argument (the instance to initialize)",
139-
// )));
140-
// }
141-
if typ.slots.new.load().map_or(0, |f| f as usize)
142-
== vm
143-
.ctx
144-
.types
145-
.object_type
146-
.slots
147-
.new
148-
.load()
149-
.map_or(0, |f| f as usize)
150-
{
151-
return Err(vm.new_type_error(format!(
152-
"{:.200}.__init__() takes exactly one argument (the instance to initialize)",
153-
typ.name()
154-
)));
155-
}
156+
return Err(vm.new_type_error(format!(
157+
"{}.__init__() takes exactly one argument (the instance to initialize)",
158+
typ.name()
159+
)));
156160
}
157161
Ok(())
158162
}
@@ -493,15 +497,7 @@ impl PyBaseObject {
493497

494498
#[pymethod]
495499
fn __init__(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
496-
if zelf.class().is(vm.ctx.types.object_type) {
497-
return Ok(());
498-
}
499-
eprintln!("{:?}", zelf.class().name());
500-
let init = zelf
501-
.class()
502-
.mro_find_map(|cls| cls.slots.init.load())
503-
.unwrap();
504-
(init)(zelf, args, vm)
500+
<Self as Initializer>::slot_init(zelf, args, vm)
505501
}
506502

507503
#[pygetset]

crates/vm/src/builtins/super.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -174,17 +174,13 @@ impl GetAttr for PySuper {
174174
.collect();
175175
for cls in &mro {
176176
if let Some(descr) = cls.get_direct_attr(name) {
177-
// Only pass 'obj' param if this is instance-mode super (See https://bugs.python.org/issue743267)
178-
let obj = if obj.is(&start_type) {
179-
None
180-
} else {
181-
println!("getattr {}.{} as {}", obj.class().name(), name, cls.name());
182-
183-
Some(obj)
184-
};
185-
let cls = cls.as_object().to_owned();
186177
return vm
187-
.call_get_descriptor_specific(&descr, obj, Some(cls))
178+
.call_get_descriptor_specific(
179+
&descr,
180+
// Only pass 'obj' param if this is instance-mode super (See https://bugs.python.org/issue743267)
181+
if obj.is(&start_type) { None } else { Some(obj) },
182+
Some(start_type.as_object().to_owned()),
183+
)
188184
.unwrap_or(Ok(descr));
189185
}
190186
}

crates/vm/src/builtins/type.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -424,9 +424,6 @@ impl PyType {
424424
let mut slot_name_set = std::collections::HashSet::new();
425425

426426
for cls in self.mro.read().iter() {
427-
if !cls.slots.flags.contains(PyTypeFlags::HEAPTYPE) {
428-
continue;
429-
}
430427
for &name in cls.attributes.read().keys() {
431428
if name.as_bytes().starts_with(b"__") && name.as_bytes().ends_with(b"__") {
432429
slot_name_set.insert(name);

0 commit comments

Comments
 (0)