mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
__slots__ xor __dict__ , name mangling (#6392)
* __slots__ xor __dict__ * mangle_name for `__` prefixed members
This commit is contained in:
1 parent
98fff96f1c
commit
1b17587585
13 files changed
+43
-32
No files matched your search
Vendored
-2
@@ -5455,8 +5455,6 @@ class Oddballs(unittest.TestCase):
|
||||
self.assertEqual(as_datetime, datetime_sc)
|
||||
self.assertEqual(datetime_sc, as_datetime)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_extra_attributes(self):
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
utcnow = datetime.utcnow()
|
||||
|
||||
Vendored
-2
@@ -24,8 +24,6 @@ class TestAbstractContextManager(unittest.TestCase):
|
||||
manager = DefaultEnter()
|
||||
self.assertIs(manager.__enter__(), manager)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_slots(self):
|
||||
class DefaultContextManager(AbstractContextManager):
|
||||
__slots__ = ()
|
||||
|
||||
Vendored
-2
@@ -27,8 +27,6 @@ class TestAbstractAsyncContextManager(unittest.IsolatedAsyncioTestCase):
|
||||
async with manager as context:
|
||||
self.assertIs(manager, context)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
async def test_slots(self):
|
||||
class DefaultAsyncContextManager(AbstractAsyncContextManager):
|
||||
__slots__ = ()
|
||||
|
||||
Vendored
-4
@@ -2776,8 +2776,6 @@ class TestFrozen(unittest.TestCase):
|
||||
|
||||
|
||||
class TestSlots(unittest.TestCase):
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_simple(self):
|
||||
@dataclass
|
||||
class C:
|
||||
@@ -2819,8 +2817,6 @@ class TestSlots(unittest.TestCase):
|
||||
# We can add a new field to the derived instance.
|
||||
d.z = 10
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_generated_slots(self):
|
||||
@dataclass(slots=True)
|
||||
class C:
|
||||
|
||||
Vendored
-4
@@ -2923,10 +2923,6 @@ class CPythonAPItests(PythonAPItests, unittest.TestCase):
|
||||
class PyPythonAPItests(PythonAPItests, unittest.TestCase):
|
||||
decimal = P
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_complex(self): # TODO(RUSTPYTHON): Remove this test when it pass
|
||||
return super().test_complex()
|
||||
|
||||
class ContextAPItests:
|
||||
|
||||
|
||||
Vendored
-2
@@ -1137,8 +1137,6 @@ class FractionTest(unittest.TestCase):
|
||||
self.assertTypedEquals(dr, copy(dr))
|
||||
self.assertTypedEquals(dr, deepcopy(dr))
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_slots(self):
|
||||
# Issue 4998
|
||||
r = F(13, 7)
|
||||
|
||||
Vendored
-1
@@ -3278,7 +3278,6 @@ class TestCachedProperty(unittest.TestCase):
|
||||
self.assertEqual(item.get_cost(), 4)
|
||||
self.assertEqual(item.cached_cost, 3)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_object_with_slots(self):
|
||||
item = CachedCostItemWithSlots()
|
||||
with self.assertRaisesRegex(
|
||||
|
||||
Vendored
-1
@@ -5332,7 +5332,6 @@ class TestPEP519(unittest.TestCase):
|
||||
def test_pathlike_class_getitem(self):
|
||||
self.assertIsInstance(os.PathLike[bytes], types.GenericAlias)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_pathlike_subclass_slots(self):
|
||||
class A(os.PathLike):
|
||||
__slots__ = ()
|
||||
|
||||
Vendored
-2
@@ -2889,8 +2889,6 @@ class TestNormalDist:
|
||||
# inaccurate. There isn't much we can do about this short of
|
||||
# implementing our own implementations from scratch.
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_slots(self):
|
||||
nd = self.module.NormalDist(300, 23)
|
||||
with self.assertRaises(TypeError):
|
||||
|
||||
Vendored
-2
@@ -5269,7 +5269,6 @@ class GenericTests(BaseTestCase):
|
||||
for t in things:
|
||||
self.assertEqual(weakref.ref(t)(), t)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON - __slots__ with Generic doesn't prevent new attributes
|
||||
def test_parameterized_slots(self):
|
||||
T = TypeVar('T')
|
||||
class C(Generic[T]):
|
||||
@@ -5289,7 +5288,6 @@ class GenericTests(BaseTestCase):
|
||||
self.assertEqual(get_type_hints(foo, globals(), locals())['x'], C[C])
|
||||
self.assertEqual(copy(C[int]), deepcopy(C[int]))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON - __slots__ with Generic doesn't prevent new attributes
|
||||
def test_parameterized_slots_dict(self):
|
||||
T = TypeVar('T')
|
||||
class D(Generic[T]):
|
||||
|
||||
@@ -66,10 +66,16 @@ impl Constructor for PyBaseObject {
|
||||
}
|
||||
|
||||
// more or less __new__ operator
|
||||
let dict = if cls.is(vm.ctx.types.object_type) {
|
||||
None
|
||||
} else {
|
||||
// Only create dict if the class has HAS_DICT flag (i.e., __slots__ was not defined
|
||||
// or __dict__ is in __slots__)
|
||||
let dict = if cls
|
||||
.slots
|
||||
.flags
|
||||
.has_feature(crate::types::PyTypeFlags::HAS_DICT)
|
||||
{
|
||||
Some(vm.ctx.new_dict())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Ensure that all abstract methods are implemented before instantiating instance.
|
||||
|
||||
@@ -307,7 +307,12 @@ impl PyType {
|
||||
) -> Result<PyRef<Self>, String> {
|
||||
let mro = Self::resolve_mro(&bases)?;
|
||||
|
||||
if base.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
|
||||
// Inherit HAS_DICT from any base in MRO that has it
|
||||
// (not just the first base, as any base with __dict__ means subclass needs it too)
|
||||
if mro
|
||||
.iter()
|
||||
.any(|b| b.slots.flags.has_feature(PyTypeFlags::HAS_DICT))
|
||||
{
|
||||
slots.flags |= PyTypeFlags::HAS_DICT
|
||||
}
|
||||
|
||||
@@ -1180,25 +1185,27 @@ impl Constructor for PyType {
|
||||
|
||||
if let Some(ref slots) = heaptype_slots {
|
||||
let mut offset = base_member_count;
|
||||
let class_name = typ.name().to_string();
|
||||
for member in slots.as_slice() {
|
||||
// Apply name mangling for private attributes (__x -> _ClassName__x)
|
||||
let mangled_name = mangle_name(&class_name, member.as_str());
|
||||
let member_def = PyMemberDef {
|
||||
name: member.to_string(),
|
||||
name: mangled_name.clone(),
|
||||
kind: MemberKind::ObjectEx,
|
||||
getter: MemberGetter::Offset(offset),
|
||||
setter: MemberSetter::Offset(offset),
|
||||
doc: None,
|
||||
};
|
||||
let attr_name = vm.ctx.intern_str(mangled_name.as_str());
|
||||
let member_descriptor: PyRef<PyMemberDescriptor> =
|
||||
vm.ctx.new_pyref(PyMemberDescriptor {
|
||||
common: PyDescriptorOwned {
|
||||
typ: typ.clone(),
|
||||
name: vm.ctx.intern_str(member.as_str()),
|
||||
name: attr_name,
|
||||
qualname: PyRwLock::new(None),
|
||||
},
|
||||
member: member_def,
|
||||
});
|
||||
|
||||
let attr_name = vm.ctx.intern_str(member.as_str());
|
||||
// __slots__ attributes always get a member descriptor
|
||||
// (this overrides any inherited attribute from MRO)
|
||||
typ.set_attr(attr_name, member_descriptor.into());
|
||||
@@ -1223,7 +1230,10 @@ impl Constructor for PyType {
|
||||
// since they inherit it from type
|
||||
|
||||
// Add __dict__ descriptor after type creation to ensure correct __objclass__
|
||||
if !base_is_type {
|
||||
// Only add if:
|
||||
// 1. base is not type (type subclasses inherit __dict__ from type)
|
||||
// 2. the class has HAS_DICT flag (i.e., __slots__ was not defined or __dict__ is in __slots__)
|
||||
if !base_is_type && typ.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
|
||||
let __dict__ = identifier!(vm, __dict__);
|
||||
if !typ.attributes.read().contains_key(&__dict__) {
|
||||
unsafe {
|
||||
@@ -1785,6 +1795,18 @@ fn best_base<'a>(bases: &'a [PyTypeRef], vm: &VirtualMachine) -> PyResult<&'a Py
|
||||
Ok(base.unwrap())
|
||||
}
|
||||
|
||||
/// Apply Python name mangling for private attributes.
|
||||
/// `__x` becomes `_ClassName__x` if inside a class.
|
||||
fn mangle_name(class_name: &str, name: &str) -> String {
|
||||
// Only mangle names starting with __ and not ending with __
|
||||
if !name.starts_with("__") || name.ends_with("__") || name.contains('.') {
|
||||
return name.to_string();
|
||||
}
|
||||
// Strip leading underscores from class name
|
||||
let class_name = class_name.trim_start_matches('_');
|
||||
format!("_{}{}", class_name, name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// spell-checker:ignore typevarobject funcobj
|
||||
use crate::{
|
||||
AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
AsObject, Context, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
builtins::{PyTupleRef, PyTypeRef, pystr::AsPyStr},
|
||||
common::lock::PyMutex,
|
||||
function::{FuncArgs, IntoFuncArgs, PyComparisonValue},
|
||||
@@ -972,6 +972,11 @@ pub struct Generic {}
|
||||
|
||||
#[pyclass(flags(BASETYPE))]
|
||||
impl Generic {
|
||||
#[pyattr]
|
||||
fn __slots__(ctx: &Context) -> PyTupleRef {
|
||||
ctx.empty_tuple.clone()
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
call_typing_args_kwargs("_generic_class_getitem", cls, args, vm)
|
||||
|
||||
Reference in new issue
Block a user