__slots__ xor __dict__ , name mangling (#6392)

* __slots__ xor __dict__

* mangle_name for `__` prefixed members
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2025-12-10 23:30:10 +09:00
1 parent 98fff96f1c
commit 1b17587585
13 files changed
+43 -32

No files matched your search

-2
View File
@@ -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()
-2
View File
@@ -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__ = ()
-2
View File
@@ -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__ = ()
-4
View File
@@ -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:
-4
View File
@@ -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:
-2
View File
@@ -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)
-1
View File
@@ -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(
-1
View File
@@ -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__ = ()
-2
View File
@@ -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):
-2
View File
@@ -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]):
+9 -3
View File
@@ -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.
+28 -6
View File
@@ -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::*;
+6 -1
View File
@@ -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)