mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
copyslot for init (#6515)
This commit is contained in:
1 parent
ae39b132e4
commit
c9bf8df19c
13 files changed
+216
-129
No files matched your search
Vendored
-1
@@ -3189,7 +3189,6 @@ class TransformCodecTest(unittest.TestCase):
|
||||
sout = reader.readline()
|
||||
self.assertEqual(sout, b"\x80")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_buffer_api_usage(self):
|
||||
# We check all the transform codecs accept memoryview input
|
||||
# for encoding and decoding
|
||||
|
||||
Vendored
-8
@@ -724,8 +724,6 @@ class CBytesIOTest(PyBytesIOTest):
|
||||
ioclass = io.BytesIO
|
||||
UnsupportedOperation = io.UnsupportedOperation
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_bytes_array(self):
|
||||
super().test_bytes_array()
|
||||
|
||||
@@ -739,8 +737,6 @@ class CBytesIOTest(PyBytesIOTest):
|
||||
def test_getbuffer(self):
|
||||
super().test_getbuffer()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_init(self):
|
||||
super().test_init()
|
||||
|
||||
@@ -770,8 +766,6 @@ class CBytesIOTest(PyBytesIOTest):
|
||||
def test_seek(self):
|
||||
super().test_seek()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_subclassing(self):
|
||||
super().test_subclassing()
|
||||
|
||||
@@ -884,8 +878,6 @@ class CStringIOTest(PyStringIOTest):
|
||||
def test_flags(self):
|
||||
super().test_flags()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_init(self):
|
||||
super().test_init()
|
||||
|
||||
|
||||
Vendored
-15
@@ -180,11 +180,6 @@ class PyPicklerTests(AbstractPickleTests, unittest.TestCase):
|
||||
def test_oob_buffers_writable_to_readonly(self): # TODO(RUSTPYTHON): Remove this test when it passes
|
||||
return super().test_oob_buffers_writable_to_readonly()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_optional_frames(self): # TODO(RUSTPYTHON): Remove this test when it passes
|
||||
return super().test_optional_frames()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_buffers_error(self): # TODO(RUSTPYTHON): Remove this test when it passes
|
||||
@@ -220,11 +215,6 @@ class PyPicklerTests(AbstractPickleTests, unittest.TestCase):
|
||||
def test_oob_buffers_writable_to_readonly(self): # TODO(RUSTPYTHON): Remove this test when it passes
|
||||
return super().test_oob_buffers_writable_to_readonly()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_optional_frames(self): # TODO(RUSTPYTHON): Remove this test when it passes
|
||||
return super().test_optional_frames()
|
||||
|
||||
|
||||
class InMemoryPickleTests(AbstractPickleTests, AbstractUnpickleTests,
|
||||
BigmemPickleTests, unittest.TestCase):
|
||||
@@ -309,11 +299,6 @@ class InMemoryPickleTests(AbstractPickleTests, AbstractUnpickleTests,
|
||||
def test_oob_buffers(self): # TODO(RUSTPYTHON): Remove this test when it passes
|
||||
return super().test_oob_buffers()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_optional_frames(self): # TODO(RUSTPYTHON): Remove this test when it passes
|
||||
return super().test_optional_frames()
|
||||
|
||||
class PersistentPicklerUnpicklerMixin(object):
|
||||
|
||||
def dumps(self, arg, proto=None):
|
||||
|
||||
Vendored
-5
@@ -97,11 +97,6 @@ class OptimizedPickleTests(AbstractPickleTests, unittest.TestCase):
|
||||
def test_oob_buffers_writable_to_readonly(self): # TODO(RUSTPYTHON): Remove this test when it passes
|
||||
return super().test_oob_buffers_writable_to_readonly()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_optional_frames(self): # TODO(RUSTPYTHON): Remove this test when it passes
|
||||
return super().test_optional_frames()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_py_methods(self): # TODO(RUSTPYTHON): Remove this test when it passes
|
||||
|
||||
Vendored
-28
@@ -641,8 +641,6 @@ class OpenerDirectorTests(unittest.TestCase):
|
||||
self.assertRaises(urllib.error.URLError, o.open, req)
|
||||
self.assertEqual(o.calls, [(handlers[0], "http_open", (req,), {})])
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_http_error(self):
|
||||
# XXX http_error_default
|
||||
# http errors are a special case
|
||||
@@ -666,8 +664,6 @@ class OpenerDirectorTests(unittest.TestCase):
|
||||
self.assertEqual((handler, method_name), got[:2])
|
||||
self.assertEqual(args, got[2])
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_processors(self):
|
||||
# *_request / *_response methods get called appropriately
|
||||
o = OpenerDirector()
|
||||
@@ -874,8 +870,6 @@ class HandlerTests(unittest.TestCase):
|
||||
self.assertEqual(req.type, "ftp")
|
||||
self.assertEqual(req.type == "ftp", ftp)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_http(self):
|
||||
|
||||
h = urllib.request.AbstractHTTPHandler()
|
||||
@@ -1136,8 +1130,6 @@ class HandlerTests(unittest.TestCase):
|
||||
self.assertEqual(newreq.host, 'www.python.org')
|
||||
self.assertEqual(newreq.selector, '')
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_errors(self):
|
||||
h = urllib.request.HTTPErrorProcessor()
|
||||
o = h.parent = MockOpener()
|
||||
@@ -1163,8 +1155,6 @@ class HandlerTests(unittest.TestCase):
|
||||
self.assertEqual(o.proto, "http") # o.error called
|
||||
self.assertEqual(o.args, (req, r, 502, "Bad gateway", {}))
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_cookies(self):
|
||||
cj = MockCookieJar()
|
||||
h = urllib.request.HTTPCookieProcessor(cj)
|
||||
@@ -1291,8 +1281,6 @@ class HandlerTests(unittest.TestCase):
|
||||
MockHeaders({"location": valid_url}))
|
||||
self.assertEqual(o.req.get_full_url(), valid_url)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_cookie_redirect(self):
|
||||
# cookies shouldn't leak into redirected requests
|
||||
from http.cookiejar import CookieJar
|
||||
@@ -1308,8 +1296,6 @@ class HandlerTests(unittest.TestCase):
|
||||
o.open("http://www.example.com/")
|
||||
self.assertFalse(hh.req.has_header("Cookie"))
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_redirect_fragment(self):
|
||||
redirected_url = 'http://www.example.com/index.html#OK\r\n\r\n'
|
||||
hh = MockHTTPHandler(302, 'Location: ' + redirected_url)
|
||||
@@ -1374,8 +1360,6 @@ class HandlerTests(unittest.TestCase):
|
||||
request = handler.last_buf
|
||||
self.assertTrue(request.startswith(expected), repr(request))
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_proxy(self):
|
||||
u = "proxy.example.com:3128"
|
||||
for d in dict(http=u), dict(HTTP=u):
|
||||
@@ -1420,8 +1404,6 @@ class HandlerTests(unittest.TestCase):
|
||||
self.assertEqual(req.host, "www.python.org")
|
||||
del os.environ['no_proxy']
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_proxy_https(self):
|
||||
o = OpenerDirector()
|
||||
ph = urllib.request.ProxyHandler(dict(https="proxy.example.com:3128"))
|
||||
@@ -1509,8 +1491,6 @@ class HandlerTests(unittest.TestCase):
|
||||
"http://acme.example.com/protected",
|
||||
"http://acme.example.com/protected")
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_basic_auth(self):
|
||||
realm = "realm2@example.com"
|
||||
realm2 = "realm2@example.com"
|
||||
@@ -1556,8 +1536,6 @@ class HandlerTests(unittest.TestCase):
|
||||
for challenge in challenges]
|
||||
self.check_basic_auth(headers, realm)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_proxy_basic_auth(self):
|
||||
opener = OpenerDirector()
|
||||
ph = urllib.request.ProxyHandler(dict(http="proxy.example.com:3128"))
|
||||
@@ -1575,8 +1553,6 @@ class HandlerTests(unittest.TestCase):
|
||||
"proxy.example.com:3128",
|
||||
)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_basic_and_digest_auth_handlers(self):
|
||||
# HTTPDigestAuthHandler raised an exception if it couldn't handle a 40*
|
||||
# response (http://python.org/sf/1479302), where it should instead
|
||||
@@ -1684,8 +1660,6 @@ class HandlerTests(unittest.TestCase):
|
||||
self.assertEqual(len(http_handler.requests), 1)
|
||||
self.assertFalse(http_handler.requests[0].has_header(auth_header))
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_basic_prior_auth_auto_send(self):
|
||||
# Assume already authenticated if is_authenticated=True
|
||||
# for APIs like Github that don't return 401
|
||||
@@ -1713,8 +1687,6 @@ class HandlerTests(unittest.TestCase):
|
||||
# expect request to be sent with auth header
|
||||
self.assertTrue(http_handler.has_auth_header)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_basic_prior_auth_send_after_first_success(self):
|
||||
# Auto send auth header after authentication is successful once
|
||||
|
||||
|
||||
Vendored
-2
@@ -1746,8 +1746,6 @@ class OtherTests(unittest.TestCase):
|
||||
fp.write("short file")
|
||||
self.assertRaises(zipfile.BadZipFile, zipfile.ZipFile, TESTFN)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_negative_central_directory_offset_raises_BadZipFile(self):
|
||||
# Zip file containing an empty EOCD record
|
||||
buffer = bytearray(b'PK\x05\x06' + b'\0'*18)
|
||||
|
||||
@@ -223,8 +223,8 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
|
||||
const METHOD_DEFS: &'static [::rustpython_vm::function::PyMethodDef] = &#method_defs;
|
||||
|
||||
fn extend_slots(slots: &mut ::rustpython_vm::types::PyTypeSlots) {
|
||||
#impl_ty::__extend_slots(slots);
|
||||
#with_slots
|
||||
#impl_ty::__extend_slots(slots);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1672,9 +1672,24 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<Extrac
|
||||
#extend_class(ctx, class);
|
||||
});
|
||||
with_method_defs.push(method_defs);
|
||||
with_slots.push(quote_spanned! { item_span =>
|
||||
#extend_slots(slots);
|
||||
});
|
||||
// For Initializer and Constructor traits, directly set the slot
|
||||
// instead of calling __extend_slots. This ensures that the trait
|
||||
// impl's override (e.g., slot_init in impl Initializer) is used,
|
||||
// not the trait's default implementation.
|
||||
let slot_code = if path.is_ident("Initializer") {
|
||||
quote_spanned! { item_span =>
|
||||
slots.init.store(Some(<Self as ::rustpython_vm::types::Initializer>::slot_init as _));
|
||||
}
|
||||
} else if path.is_ident("Constructor") {
|
||||
quote_spanned! { item_span =>
|
||||
slots.new.store(Some(<Self as ::rustpython_vm::types::Constructor>::slot_new as _));
|
||||
}
|
||||
} else {
|
||||
quote_spanned! { item_span =>
|
||||
#extend_slots(slots);
|
||||
}
|
||||
};
|
||||
with_slots.push(slot_code);
|
||||
}
|
||||
} else if path.is_ident("flags") {
|
||||
for meta in nested {
|
||||
|
||||
@@ -120,44 +120,36 @@ impl Initializer for PyBaseObject {
|
||||
|
||||
// object_init: excess_args validation
|
||||
fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
|
||||
if args.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// if (type->tp_init != object_init) → first error
|
||||
if typ_init != object_init {
|
||||
return Err(vm.new_type_error(
|
||||
"object.__init__() takes exactly one argument (the instance to initialize)"
|
||||
.to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
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() {
|
||||
// if (type->tp_new == object_new) → second error
|
||||
if typ_new == object_new {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{}.__init__() takes exactly one argument (the instance to initialize)",
|
||||
typ.name()
|
||||
)));
|
||||
}
|
||||
|
||||
// Both conditions false → OK (e.g., tuple, dict with custom __new__)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -591,6 +583,13 @@ pub fn object_set_dict(obj: PyObjectRef, dict: PyDictRef, vm: &VirtualMachine) -
|
||||
}
|
||||
|
||||
pub fn init(ctx: &Context) {
|
||||
// Manually set init slot - derive macro doesn't generate extend_slots
|
||||
// for trait impl that overrides #[pyslot] method
|
||||
ctx.types
|
||||
.object_type
|
||||
.slots
|
||||
.init
|
||||
.store(Some(<PyBaseObject as Initializer>::slot_init));
|
||||
PyBaseObject::extend_class(ctx, ctx.types.object_type);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ use crate::{
|
||||
object::{Traverse, TraverseFn},
|
||||
protocol::{PyIterReturn, PyNumberMethods},
|
||||
types::{
|
||||
AsNumber, Callable, Constructor, GetAttr, PyTypeFlags, PyTypeSlots, Representable, SetAttr,
|
||||
TypeDataRef, TypeDataRefMut, TypeDataSlot,
|
||||
AsNumber, Callable, Constructor, GetAttr, Initializer, PyTypeFlags, PyTypeSlots,
|
||||
Representable, SetAttr, TypeDataRef, TypeDataRefMut, TypeDataSlot,
|
||||
},
|
||||
};
|
||||
use indexmap::{IndexMap, map::Entry};
|
||||
@@ -412,8 +412,10 @@ impl PyType {
|
||||
}
|
||||
|
||||
pub(crate) fn init_slots(&self, ctx: &Context) {
|
||||
// Inherit slots from direct bases (not MRO)
|
||||
for base in self.bases.read().iter() {
|
||||
// Inherit slots from MRO
|
||||
// Note: self.mro does NOT include self, so we iterate all elements
|
||||
let mro: Vec<_> = self.mro.read().iter().cloned().collect();
|
||||
for base in mro.iter() {
|
||||
self.inherit_slots(base);
|
||||
}
|
||||
|
||||
@@ -460,7 +462,7 @@ impl PyType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Inherit slots from base type. typeobject.c: inherit_slots
|
||||
/// Inherit slots from base type. inherit_slots
|
||||
pub(crate) fn inherit_slots(&self, base: &Self) {
|
||||
macro_rules! copyslot {
|
||||
($slot:ident) => {
|
||||
@@ -472,6 +474,28 @@ impl PyType {
|
||||
};
|
||||
}
|
||||
|
||||
// Copy init slot only if base actually defines it (not just inherited)
|
||||
// This is needed for multiple inheritance where a later base might
|
||||
// have a more specific init slot
|
||||
macro_rules! copyslot_defined {
|
||||
($slot:ident) => {
|
||||
if self.slots.$slot.load().is_none() {
|
||||
if let Some(base_val) = base.slots.$slot.load() {
|
||||
// SLOTDEFINED: base->SLOT && (basebase == NULL || base->SLOT != basebase->SLOT)
|
||||
let basebase = base.base.as_ref();
|
||||
let slot_defined = match basebase {
|
||||
None => true,
|
||||
Some(bb) => bb.slots.$slot.load().map(|v| v as usize)
|
||||
!= Some(base_val as usize),
|
||||
};
|
||||
if slot_defined {
|
||||
self.slots.$slot.store(Some(base_val));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Core slots
|
||||
copyslot!(hash);
|
||||
copyslot!(call);
|
||||
@@ -484,9 +508,8 @@ impl PyType {
|
||||
copyslot!(iternext);
|
||||
copyslot!(descr_get);
|
||||
copyslot!(descr_set);
|
||||
// Note: init is NOT inherited here because object_init has special
|
||||
// handling in CPython (checks if type->tp_init != object_init).
|
||||
// TODO: implement proper init inheritance with object_init check
|
||||
// init uses SLOTDEFINED check for multiple inheritance support
|
||||
copyslot_defined!(init);
|
||||
copyslot!(del);
|
||||
// new is handled by set_new()
|
||||
// as_buffer is inherited at type creation time (not AtomicCell)
|
||||
@@ -832,7 +855,16 @@ impl Py<PyType> {
|
||||
}
|
||||
|
||||
#[pyclass(
|
||||
with(Py, Constructor, GetAttr, SetAttr, Callable, AsNumber, Representable),
|
||||
with(
|
||||
Py,
|
||||
Constructor,
|
||||
Initializer,
|
||||
GetAttr,
|
||||
SetAttr,
|
||||
Callable,
|
||||
AsNumber,
|
||||
Representable
|
||||
),
|
||||
flags(BASETYPE)
|
||||
)]
|
||||
impl PyType {
|
||||
@@ -1532,6 +1564,26 @@ fn get_doc_from_internal_doc<'a>(name: &str, internal_doc: &'a str) -> &'a str {
|
||||
internal_doc
|
||||
}
|
||||
|
||||
impl Initializer for PyType {
|
||||
type Args = FuncArgs;
|
||||
|
||||
// type_init
|
||||
fn slot_init(_zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
|
||||
// type.__init__() takes 1 or 3 arguments
|
||||
if args.args.len() == 1 && !args.kwargs.is_empty() {
|
||||
return Err(vm.new_type_error("type.__init__() takes no keyword arguments".to_owned()));
|
||||
}
|
||||
if args.args.len() != 1 && args.args.len() != 3 {
|
||||
return Err(vm.new_type_error("type.__init__() takes 1 or 3 arguments".to_owned()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
|
||||
unreachable!("slot_init is defined")
|
||||
}
|
||||
}
|
||||
|
||||
impl GetAttr for PyType {
|
||||
fn getattro(zelf: &Py<Self>, name_str: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
|
||||
#[cold]
|
||||
|
||||
@@ -353,21 +353,13 @@ pub(super) mod types {
|
||||
impl Initializer for PyBaseExceptionGroup {
|
||||
type Args = FuncArgs;
|
||||
|
||||
fn slot_init(
|
||||
_zelf: PyObjectRef,
|
||||
_args: ::rustpython_vm::function::FuncArgs,
|
||||
_vm: &::rustpython_vm::VirtualMachine,
|
||||
) -> ::rustpython_vm::PyResult<()> {
|
||||
// CPython's BaseExceptionGroup.__init__ just calls BaseException.__init__
|
||||
// which stores args as-is. Since __new__ already set up the correct args
|
||||
// (message, exceptions_tuple), we don't need to do anything here.
|
||||
// This also allows subclasses to pass extra arguments to __new__ without
|
||||
// __init__ complaining about argument count.
|
||||
fn slot_init(_zelf: PyObjectRef, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> {
|
||||
// No-op: __new__ already set up the correct args (message, exceptions_tuple)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init(_zelf: PyRef<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
|
||||
unreachable!("slot_init is defined")
|
||||
unreachable!("slot_init is overridden")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+39
-18
@@ -3679,23 +3679,31 @@ mod _io {
|
||||
}
|
||||
|
||||
impl Constructor for StringIO {
|
||||
type Args = FuncArgs;
|
||||
|
||||
fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
_base: Default::default(),
|
||||
buffer: PyRwLock::new(BufferedIO::new(Cursor::new(Vec::new()))),
|
||||
closed: AtomicCell::new(false),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Initializer for StringIO {
|
||||
type Args = StringIONewArgs;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn py_new(
|
||||
_cls: &Py<PyType>,
|
||||
fn init(
|
||||
zelf: PyRef<Self>,
|
||||
Self::Args { object, newline }: Self::Args,
|
||||
_vm: &VirtualMachine,
|
||||
) -> PyResult<Self> {
|
||||
) -> PyResult<()> {
|
||||
let raw_bytes = object
|
||||
.flatten()
|
||||
.map_or_else(Vec::new, |v| v.as_bytes().to_vec());
|
||||
|
||||
Ok(Self {
|
||||
_base: Default::default(),
|
||||
buffer: PyRwLock::new(BufferedIO::new(Cursor::new(raw_bytes))),
|
||||
closed: AtomicCell::new(false),
|
||||
})
|
||||
*zelf.buffer.write() = BufferedIO::new(Cursor::new(raw_bytes));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3709,7 +3717,7 @@ mod _io {
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(flags(BASETYPE, HAS_DICT), with(Constructor))]
|
||||
#[pyclass(flags(BASETYPE, HAS_DICT), with(Constructor, Initializer))]
|
||||
impl StringIO {
|
||||
#[pymethod]
|
||||
const fn readable(&self) -> bool {
|
||||
@@ -3814,22 +3822,35 @@ mod _io {
|
||||
}
|
||||
|
||||
impl Constructor for BytesIO {
|
||||
type Args = OptionalArg<Option<PyBytesRef>>;
|
||||
|
||||
fn py_new(_cls: &Py<PyType>, object: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
|
||||
let raw_bytes = object
|
||||
.flatten()
|
||||
.map_or_else(Vec::new, |input| input.as_bytes().to_vec());
|
||||
type Args = FuncArgs;
|
||||
|
||||
fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
_base: Default::default(),
|
||||
buffer: PyRwLock::new(BufferedIO::new(Cursor::new(raw_bytes))),
|
||||
buffer: PyRwLock::new(BufferedIO::new(Cursor::new(Vec::new()))),
|
||||
closed: AtomicCell::new(false),
|
||||
exports: AtomicCell::new(0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Initializer for BytesIO {
|
||||
type Args = OptionalArg<Option<ArgBytesLike>>;
|
||||
|
||||
fn init(zelf: PyRef<Self>, object: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
|
||||
if zelf.exports.load() > 0 {
|
||||
return Err(vm.new_buffer_error(
|
||||
"Existing exports of data: object cannot be re-sized".to_owned(),
|
||||
));
|
||||
}
|
||||
let raw_bytes = object
|
||||
.flatten()
|
||||
.map_or_else(Vec::new, |input| input.borrow_buf().to_vec());
|
||||
*zelf.buffer.write() = BufferedIO::new(Cursor::new(raw_bytes));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl BytesIO {
|
||||
fn buffer(&self, vm: &VirtualMachine) -> PyResult<PyRwLockWriteGuard<'_, BufferedIO>> {
|
||||
if !self.closed.load() {
|
||||
@@ -3840,7 +3861,7 @@ mod _io {
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(flags(BASETYPE, HAS_DICT), with(PyRef, Constructor))]
|
||||
#[pyclass(flags(BASETYPE, HAS_DICT), with(PyRef, Constructor, Initializer))]
|
||||
impl BytesIO {
|
||||
#[pymethod]
|
||||
const fn readable(&self) -> bool {
|
||||
|
||||
@@ -632,13 +632,48 @@ impl PyType {
|
||||
update_slot!(descr_set, descr_set_wrapper);
|
||||
}
|
||||
_ if name == identifier!(ctx, __init__) => {
|
||||
toggle_slot!(init, init_wrapper);
|
||||
// Special handling: check if this type or any base has a real __init__ method
|
||||
// If only slot wrappers exist, use the inherited slot from copyslot!
|
||||
if ADD {
|
||||
// First check if this type has __init__ in its own dict
|
||||
let has_own_init = self.attributes.read().contains_key(name);
|
||||
if has_own_init {
|
||||
// This type defines __init__ - use wrapper
|
||||
self.slots.init.store(Some(init_wrapper));
|
||||
} else if self.has_real_method_in_mro(name, ctx) {
|
||||
// A base class defines a real __init__ method - use wrapper
|
||||
self.slots.init.store(Some(init_wrapper));
|
||||
}
|
||||
// else: keep inherited slot from copyslot!
|
||||
} else {
|
||||
let inherited = self
|
||||
.mro
|
||||
.read()
|
||||
.iter()
|
||||
.skip(1)
|
||||
.find_map(|cls| cls.slots.init.load());
|
||||
self.slots.init.store(inherited);
|
||||
}
|
||||
}
|
||||
_ if name == identifier!(ctx, __new__) => {
|
||||
toggle_slot!(new, new_wrapper);
|
||||
}
|
||||
_ if name == identifier!(ctx, __del__) => {
|
||||
toggle_slot!(del, del_wrapper);
|
||||
// Same special handling as __init__
|
||||
if ADD {
|
||||
let has_own_del = self.attributes.read().contains_key(name);
|
||||
if has_own_del || self.has_real_method_in_mro(name, ctx) {
|
||||
self.slots.del.store(Some(del_wrapper));
|
||||
}
|
||||
} else {
|
||||
let inherited = self
|
||||
.mro
|
||||
.read()
|
||||
.iter()
|
||||
.skip(1)
|
||||
.find_map(|cls| cls.slots.del.load());
|
||||
self.slots.del.store(inherited);
|
||||
}
|
||||
}
|
||||
_ if name == identifier!(ctx, __bool__) => {
|
||||
toggle_sub_slot!(as_number, boolean, bool_wrapper);
|
||||
@@ -890,6 +925,36 @@ impl PyType {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if there's a real method (not a slot wrapper) for `name` anywhere in the MRO.
|
||||
/// If a real method exists, we should use a wrapper function. Otherwise, use the inherited slot.
|
||||
fn has_real_method_in_mro(&self, name: &'static PyStrInterned, ctx: &Context) -> bool {
|
||||
use crate::builtins::descriptor::PySlotWrapper;
|
||||
|
||||
// Check the entire MRO (including self) for the method
|
||||
for cls in self.mro.read().iter() {
|
||||
if let Some(attr) = cls.attributes.read().get(name).cloned() {
|
||||
// Found the method - check if it's a slot wrapper
|
||||
if attr.class().is(ctx.types.wrapper_descriptor_type) {
|
||||
if let Some(wrapper) = attr.downcast_ref::<PySlotWrapper>() {
|
||||
// It's a slot wrapper - check if it belongs to this class
|
||||
let wrapper_typ: *const _ = wrapper.typ;
|
||||
let cls_ptr: *const _ = cls.as_ref();
|
||||
if wrapper_typ == cls_ptr {
|
||||
// Slot wrapper defined on this exact class - use inherited slot
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Inherited slot wrapper - continue checking MRO
|
||||
} else {
|
||||
// Real method found - use wrapper function
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// No real method found in MRO - use inherited slot
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for types that can be constructed via Python's `__new__` method.
|
||||
|
||||
@@ -199,8 +199,10 @@ impl TypeZoo {
|
||||
/// Fill attributes of builtin types.
|
||||
#[cold]
|
||||
pub(crate) fn extend(context: &Context) {
|
||||
type_::init(context);
|
||||
// object must be initialized before type to set object.slots.init,
|
||||
// which type will inherit via inherit_slots()
|
||||
object::init(context);
|
||||
type_::init(context);
|
||||
list::init(context);
|
||||
set::init(context);
|
||||
tuple::init(context);
|
||||
|
||||
Reference in new issue
Block a user