Skip to content

Commit c9bf8df

Browse files
authored
copyslot for init (#6515)
1 parent ae39b13 commit c9bf8df

File tree

13 files changed

+216
-129
lines changed

13 files changed

+216
-129
lines changed

Lib/test/test_codecs.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3189,7 +3189,6 @@ def test_readline(self):
31893189
sout = reader.readline()
31903190
self.assertEqual(sout, b"\x80")
31913191

3192-
@unittest.expectedFailure # TODO: RUSTPYTHON
31933192
def test_buffer_api_usage(self):
31943193
# We check all the transform codecs accept memoryview input
31953194
# for encoding and decoding

Lib/test/test_memoryio.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -724,8 +724,6 @@ class CBytesIOTest(PyBytesIOTest):
724724
ioclass = io.BytesIO
725725
UnsupportedOperation = io.UnsupportedOperation
726726

727-
# TODO: RUSTPYTHON
728-
@unittest.expectedFailure
729727
def test_bytes_array(self):
730728
super().test_bytes_array()
731729

@@ -739,8 +737,6 @@ def test_flags(self):
739737
def test_getbuffer(self):
740738
super().test_getbuffer()
741739

742-
# TODO: RUSTPYTHON
743-
@unittest.expectedFailure
744740
def test_init(self):
745741
super().test_init()
746742

@@ -770,8 +766,6 @@ def test_relative_seek(self):
770766
def test_seek(self):
771767
super().test_seek()
772768

773-
# TODO: RUSTPYTHON
774-
@unittest.expectedFailure
775769
def test_subclassing(self):
776770
super().test_subclassing()
777771

@@ -884,8 +878,6 @@ def test_detach(self):
884878
def test_flags(self):
885879
super().test_flags()
886880

887-
# TODO: RUSTPYTHON
888-
@unittest.expectedFailure
889881
def test_init(self):
890882
super().test_init()
891883

Lib/test/test_pickle.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,6 @@ def test_oob_buffers(self): # TODO(RUSTPYTHON): Remove this test when it passes
180180
def test_oob_buffers_writable_to_readonly(self): # TODO(RUSTPYTHON): Remove this test when it passes
181181
return super().test_oob_buffers_writable_to_readonly()
182182

183-
# TODO: RUSTPYTHON
184-
@unittest.expectedFailure
185-
def test_optional_frames(self): # TODO(RUSTPYTHON): Remove this test when it passes
186-
return super().test_optional_frames()
187-
188183
# TODO: RUSTPYTHON
189184
@unittest.expectedFailure
190185
def test_buffers_error(self): # TODO(RUSTPYTHON): Remove this test when it passes
@@ -220,11 +215,6 @@ def test_oob_buffers(self): # TODO(RUSTPYTHON): Remove this test when it passes
220215
def test_oob_buffers_writable_to_readonly(self): # TODO(RUSTPYTHON): Remove this test when it passes
221216
return super().test_oob_buffers_writable_to_readonly()
222217

223-
# TODO: RUSTPYTHON
224-
@unittest.expectedFailure
225-
def test_optional_frames(self): # TODO(RUSTPYTHON): Remove this test when it passes
226-
return super().test_optional_frames()
227-
228218

229219
class InMemoryPickleTests(AbstractPickleTests, AbstractUnpickleTests,
230220
BigmemPickleTests, unittest.TestCase):
@@ -309,11 +299,6 @@ def test_in_band_buffers(self): # TODO(RUSTPYTHON): Remove this test when it pas
309299
def test_oob_buffers(self): # TODO(RUSTPYTHON): Remove this test when it passes
310300
return super().test_oob_buffers()
311301

312-
# TODO: RUSTPYTHON
313-
@unittest.expectedFailure
314-
def test_optional_frames(self): # TODO(RUSTPYTHON): Remove this test when it passes
315-
return super().test_optional_frames()
316-
317302
class PersistentPicklerUnpicklerMixin(object):
318303

319304
def dumps(self, arg, proto=None):

Lib/test/test_pickletools.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,6 @@ def test_oob_buffers(self): # TODO(RUSTPYTHON): Remove this test when it passes
9797
def test_oob_buffers_writable_to_readonly(self): # TODO(RUSTPYTHON): Remove this test when it passes
9898
return super().test_oob_buffers_writable_to_readonly()
9999

100-
# TODO: RUSTPYTHON
101-
@unittest.expectedFailure
102-
def test_optional_frames(self): # TODO(RUSTPYTHON): Remove this test when it passes
103-
return super().test_optional_frames()
104-
105100
# TODO: RUSTPYTHON
106101
@unittest.expectedFailure
107102
def test_py_methods(self): # TODO(RUSTPYTHON): Remove this test when it passes

Lib/test/test_urllib2.py

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -641,8 +641,6 @@ def test_raise(self):
641641
self.assertRaises(urllib.error.URLError, o.open, req)
642642
self.assertEqual(o.calls, [(handlers[0], "http_open", (req,), {})])
643643

644-
# TODO: RUSTPYTHON
645-
@unittest.expectedFailure
646644
def test_http_error(self):
647645
# XXX http_error_default
648646
# http errors are a special case
@@ -666,8 +664,6 @@ def test_http_error(self):
666664
self.assertEqual((handler, method_name), got[:2])
667665
self.assertEqual(args, got[2])
668666

669-
# TODO: RUSTPYTHON
670-
@unittest.expectedFailure
671667
def test_processors(self):
672668
# *_request / *_response methods get called appropriately
673669
o = OpenerDirector()
@@ -874,8 +870,6 @@ def test_file(self):
874870
self.assertEqual(req.type, "ftp")
875871
self.assertEqual(req.type == "ftp", ftp)
876872

877-
# TODO: RUSTPYTHON
878-
@unittest.expectedFailure
879873
def test_http(self):
880874

881875
h = urllib.request.AbstractHTTPHandler()
@@ -1136,8 +1130,6 @@ def test_fixpath_in_weirdurls(self):
11361130
self.assertEqual(newreq.host, 'www.python.org')
11371131
self.assertEqual(newreq.selector, '')
11381132

1139-
# TODO: RUSTPYTHON
1140-
@unittest.expectedFailure
11411133
def test_errors(self):
11421134
h = urllib.request.HTTPErrorProcessor()
11431135
o = h.parent = MockOpener()
@@ -1163,8 +1155,6 @@ def test_errors(self):
11631155
self.assertEqual(o.proto, "http") # o.error called
11641156
self.assertEqual(o.args, (req, r, 502, "Bad gateway", {}))
11651157

1166-
# TODO: RUSTPYTHON
1167-
@unittest.expectedFailure
11681158
def test_cookies(self):
11691159
cj = MockCookieJar()
11701160
h = urllib.request.HTTPCookieProcessor(cj)
@@ -1291,8 +1281,6 @@ def test_relative_redirect(self):
12911281
MockHeaders({"location": valid_url}))
12921282
self.assertEqual(o.req.get_full_url(), valid_url)
12931283

1294-
# TODO: RUSTPYTHON
1295-
@unittest.expectedFailure
12961284
def test_cookie_redirect(self):
12971285
# cookies shouldn't leak into redirected requests
12981286
from http.cookiejar import CookieJar
@@ -1308,8 +1296,6 @@ def test_cookie_redirect(self):
13081296
o.open("http://www.example.com/")
13091297
self.assertFalse(hh.req.has_header("Cookie"))
13101298

1311-
# TODO: RUSTPYTHON
1312-
@unittest.expectedFailure
13131299
def test_redirect_fragment(self):
13141300
redirected_url = 'http://www.example.com/index.html#OK\r\n\r\n'
13151301
hh = MockHTTPHandler(302, 'Location: ' + redirected_url)
@@ -1374,8 +1360,6 @@ def http_open(self, req):
13741360
request = handler.last_buf
13751361
self.assertTrue(request.startswith(expected), repr(request))
13761362

1377-
# TODO: RUSTPYTHON
1378-
@unittest.expectedFailure
13791363
def test_proxy(self):
13801364
u = "proxy.example.com:3128"
13811365
for d in dict(http=u), dict(HTTP=u):
@@ -1420,8 +1404,6 @@ def test_proxy_no_proxy_all(self):
14201404
self.assertEqual(req.host, "www.python.org")
14211405
del os.environ['no_proxy']
14221406

1423-
# TODO: RUSTPYTHON
1424-
@unittest.expectedFailure
14251407
def test_proxy_https(self):
14261408
o = OpenerDirector()
14271409
ph = urllib.request.ProxyHandler(dict(https="proxy.example.com:3128"))
@@ -1509,8 +1491,6 @@ def check_basic_auth(self, headers, realm):
15091491
"http://acme.example.com/protected",
15101492
"http://acme.example.com/protected")
15111493

1512-
# TODO: RUSTPYTHON
1513-
@unittest.expectedFailure
15141494
def test_basic_auth(self):
15151495
realm = "realm2@example.com"
15161496
realm2 = "realm2@example.com"
@@ -1556,8 +1536,6 @@ def test_basic_auth(self):
15561536
for challenge in challenges]
15571537
self.check_basic_auth(headers, realm)
15581538

1559-
# TODO: RUSTPYTHON
1560-
@unittest.expectedFailure
15611539
def test_proxy_basic_auth(self):
15621540
opener = OpenerDirector()
15631541
ph = urllib.request.ProxyHandler(dict(http="proxy.example.com:3128"))
@@ -1575,8 +1553,6 @@ def test_proxy_basic_auth(self):
15751553
"proxy.example.com:3128",
15761554
)
15771555

1578-
# TODO: RUSTPYTHON
1579-
@unittest.expectedFailure
15801556
def test_basic_and_digest_auth_handlers(self):
15811557
# HTTPDigestAuthHandler raised an exception if it couldn't handle a 40*
15821558
# response (http://python.org/sf/1479302), where it should instead
@@ -1684,8 +1660,6 @@ def _test_basic_auth(self, opener, auth_handler, auth_header,
16841660
self.assertEqual(len(http_handler.requests), 1)
16851661
self.assertFalse(http_handler.requests[0].has_header(auth_header))
16861662

1687-
# TODO: RUSTPYTHON
1688-
@unittest.expectedFailure
16891663
def test_basic_prior_auth_auto_send(self):
16901664
# Assume already authenticated if is_authenticated=True
16911665
# for APIs like Github that don't return 401
@@ -1713,8 +1687,6 @@ def test_basic_prior_auth_auto_send(self):
17131687
# expect request to be sent with auth header
17141688
self.assertTrue(http_handler.has_auth_header)
17151689

1716-
# TODO: RUSTPYTHON
1717-
@unittest.expectedFailure
17181690
def test_basic_prior_auth_send_after_first_success(self):
17191691
# Auto send auth header after authentication is successful once
17201692

Lib/test/test_zipfile.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,8 +1746,6 @@ def test_empty_file_raises_BadZipFile(self):
17461746
fp.write("short file")
17471747
self.assertRaises(zipfile.BadZipFile, zipfile.ZipFile, TESTFN)
17481748

1749-
# TODO: RUSTPYTHON
1750-
@unittest.expectedFailure
17511749
def test_negative_central_directory_offset_raises_BadZipFile(self):
17521750
# Zip file containing an empty EOCD record
17531751
buffer = bytearray(b'PK\x05\x06' + b'\0'*18)

crates/derive-impl/src/pyclass.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,8 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
223223
const METHOD_DEFS: &'static [::rustpython_vm::function::PyMethodDef] = &#method_defs;
224224

225225
fn extend_slots(slots: &mut ::rustpython_vm::types::PyTypeSlots) {
226-
#impl_ty::__extend_slots(slots);
227226
#with_slots
227+
#impl_ty::__extend_slots(slots);
228228
}
229229
}
230230
}
@@ -1672,9 +1672,24 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<Extrac
16721672
#extend_class(ctx, class);
16731673
});
16741674
with_method_defs.push(method_defs);
1675-
with_slots.push(quote_spanned! { item_span =>
1676-
#extend_slots(slots);
1677-
});
1675+
// For Initializer and Constructor traits, directly set the slot
1676+
// instead of calling __extend_slots. This ensures that the trait
1677+
// impl's override (e.g., slot_init in impl Initializer) is used,
1678+
// not the trait's default implementation.
1679+
let slot_code = if path.is_ident("Initializer") {
1680+
quote_spanned! { item_span =>
1681+
slots.init.store(Some(<Self as ::rustpython_vm::types::Initializer>::slot_init as _));
1682+
}
1683+
} else if path.is_ident("Constructor") {
1684+
quote_spanned! { item_span =>
1685+
slots.new.store(Some(<Self as ::rustpython_vm::types::Constructor>::slot_new as _));
1686+
}
1687+
} else {
1688+
quote_spanned! { item_span =>
1689+
#extend_slots(slots);
1690+
}
1691+
};
1692+
with_slots.push(slot_code);
16781693
}
16791694
} else if path.is_ident("flags") {
16801695
for meta in nested {

crates/vm/src/builtins/object.rs

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -120,44 +120,36 @@ impl Initializer for PyBaseObject {
120120

121121
// object_init: excess_args validation
122122
fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
123+
if args.is_empty() {
124+
return Ok(());
125+
}
126+
123127
let typ = zelf.class();
124128
let object_type = &vm.ctx.types.object_type;
125129

126130
let typ_init = typ.slots.init.load().map(|f| f as usize);
127131
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);
130132

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(());
133+
// if (type->tp_init != object_init) → first error
134+
if typ_init != object_init {
135+
return Err(vm.new_type_error(
136+
"object.__init__() takes exactly one argument (the instance to initialize)"
137+
.to_owned(),
138+
));
152139
}
153140

154-
// Otherwise, reject excess args
155-
if !args.is_empty() {
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+
144+
// if (type->tp_new == object_new) → second error
145+
if typ_new == object_new {
156146
return Err(vm.new_type_error(format!(
157147
"{}.__init__() takes exactly one argument (the instance to initialize)",
158148
typ.name()
159149
)));
160150
}
151+
152+
// Both conditions false → OK (e.g., tuple, dict with custom __new__)
161153
Ok(())
162154
}
163155

@@ -591,6 +583,13 @@ pub fn object_set_dict(obj: PyObjectRef, dict: PyDictRef, vm: &VirtualMachine) -
591583
}
592584

593585
pub fn init(ctx: &Context) {
586+
// Manually set init slot - derive macro doesn't generate extend_slots
587+
// for trait impl that overrides #[pyslot] method
588+
ctx.types
589+
.object_type
590+
.slots
591+
.init
592+
.store(Some(<PyBaseObject as Initializer>::slot_init));
594593
PyBaseObject::extend_class(ctx, ctx.types.object_type);
595594
}
596595

0 commit comments

Comments
 (0)