Skip to content

Commit 96b23d3

Browse files
committed
Fix object.__init__ excess-arg checks for heap types
1 parent 578bc62 commit 96b23d3

3 files changed

Lines changed: 18 additions & 8 deletions

File tree

‎Lib/test/test_class.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,6 @@ def __setattr__(self, name, value) -> None:
741741
with self.assertRaisesRegex(AttributeError, error_msg):
742742
del B().z
743743

744-
@unittest.expectedFailure # TODO: RUSTPYTHON
745744
def testConstructorErrorMessages(self):
746745
# bpo-31506: Improves the error message logic for object_new & object_init
747746

‎Lib/test/test_descr.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1853,7 +1853,6 @@ class C(list):
18531853
__new__ = object.__new__
18541854
self.assertRaises(TypeError, C)
18551855

1856-
@unittest.expectedFailure # TODO: RUSTPYTHON
18571856
def test_object_new(self):
18581857
class A(object):
18591858
pass

‎crates/vm/src/builtins/object.rs‎

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,24 +125,36 @@ impl Initializer for PyBaseObject {
125125
}
126126

127127
let typ = zelf.class();
128-
let object_type = &vm.ctx.types.object_type;
128+
let object_type = vm.ctx.types.object_type;
129129

130130
let typ_init = typ.slots.init.load().map(|f| f as usize);
131131
let object_init = object_type.slots.init.load().map(|f| f as usize);
132132

133133
// if (type->tp_init != object_init) → first error
134-
if typ_init != object_init {
134+
// Note: typ_init can be None due to SLOTDEFINED check in copyslot_if_none,
135+
// which means the slot was not explicitly defined. Treat None as equivalent
136+
// to object's init (i.e., inherited but not overridden).
137+
if typ_init.is_some() && typ_init != object_init {
135138
return Err(vm.new_type_error(
136139
"object.__init__() takes exactly one argument (the instance to initialize)"
137140
.to_owned(),
138141
));
139142
}
140143

141-
let typ_new = typ.slots.new.load().map(|f| f as usize);
142-
let object_new = object_type.slots.new.load().map(|f| f as usize);
143-
144144
// if (type->tp_new == object_new) → second error
145-
if typ_new == object_new {
145+
// Can't compare slot pointers for __new__ because update_one_slot
146+
// unconditionally sets new_wrapper for all heap types. Instead, check
147+
// if any non-object class in the MRO defines __new__ in its own dict.
148+
// This is safe because add_operators skips __new__ — only types that
149+
// explicitly define __new__ have it in their dict.
150+
let has_custom_new = typ.mro.read().iter().any(|cls| {
151+
!cls.is(object_type)
152+
&& cls
153+
.attributes
154+
.read()
155+
.contains_key(identifier!(vm, __new__))
156+
});
157+
if !has_custom_new {
146158
return Err(vm.new_type_error(format!(
147159
"{}.__init__() takes exactly one argument (the instance to initialize)",
148160
typ.name()

0 commit comments

Comments
 (0)