@@ -948,6 +948,11 @@ impl PyType {
948948 self . slots . basicsize
949949 }
950950
951+ #[ pygetset]
952+ fn __itemsize__ ( & self ) -> usize {
953+ self . slots . itemsize
954+ }
955+
951956 #[ pygetset]
952957 pub fn __name__ ( & self , vm : & VirtualMachine ) -> PyStrRef {
953958 self . name_inner (
@@ -1347,65 +1352,144 @@ impl Constructor for PyType {
13471352 attributes. insert ( identifier ! ( vm, __hash__) , vm. ctx . none . clone ( ) . into ( ) ) ;
13481353 }
13491354
1350- let ( heaptype_slots, add_dict) : ( Option < PyRef < PyTuple < PyStrRef > > > , bool ) =
1351- if let Some ( x) = attributes. get ( identifier ! ( vm, __slots__) ) {
1352- // Check if __slots__ is bytes - not allowed
1353- if x. class ( ) . is ( vm. ctx . types . bytes_type ) {
1354- return Err ( vm. new_type_error (
1355- "__slots__ items must be strings, not 'bytes'" . to_owned ( ) ,
1356- ) ) ;
1357- }
1355+ let ( heaptype_slots, add_dict) : ( Option < PyRef < PyTuple < PyStrRef > > > , bool ) = if let Some ( x) =
1356+ attributes. get ( identifier ! ( vm, __slots__) )
1357+ {
1358+ // Check if __slots__ is bytes - not allowed
1359+ if x. class ( ) . is ( vm. ctx . types . bytes_type ) {
1360+ return Err (
1361+ vm. new_type_error ( "__slots__ items must be strings, not 'bytes'" . to_owned ( ) )
1362+ ) ;
1363+ }
13581364
1359- let slots = if x. class ( ) . is ( vm. ctx . types . str_type ) {
1360- let x = unsafe { x. downcast_unchecked_ref :: < PyStr > ( ) } ;
1361- PyTuple :: new_ref_typed ( vec ! [ x. to_owned( ) ] , & vm. ctx )
1362- } else {
1363- let iter = x. get_iter ( vm) ?;
1364- let elements = {
1365- let mut elements = Vec :: new ( ) ;
1366- while let PyIterReturn :: Return ( element) = iter. next ( vm) ? {
1367- // Check if any slot item is bytes
1368- if element. class ( ) . is ( vm. ctx . types . bytes_type ) {
1369- return Err ( vm. new_type_error (
1370- "__slots__ items must be strings, not 'bytes'" . to_owned ( ) ,
1371- ) ) ;
1372- }
1373- elements. push ( element) ;
1365+ let slots = if x. class ( ) . is ( vm. ctx . types . str_type ) {
1366+ let x = unsafe { x. downcast_unchecked_ref :: < PyStr > ( ) } ;
1367+ PyTuple :: new_ref_typed ( vec ! [ x. to_owned( ) ] , & vm. ctx )
1368+ } else {
1369+ let iter = x. get_iter ( vm) ?;
1370+ let elements = {
1371+ let mut elements = Vec :: new ( ) ;
1372+ while let PyIterReturn :: Return ( element) = iter. next ( vm) ? {
1373+ // Check if any slot item is bytes
1374+ if element. class ( ) . is ( vm. ctx . types . bytes_type ) {
1375+ return Err ( vm. new_type_error (
1376+ "__slots__ items must be strings, not 'bytes'" . to_owned ( ) ,
1377+ ) ) ;
13741378 }
1375- elements
1376- } ;
1377- let tuple = elements. into_pytuple ( vm) ;
1378- tuple. try_into_typed ( vm) ?
1379+ elements. push ( element) ;
1380+ }
1381+ elements
13791382 } ;
1383+ let tuple = elements. into_pytuple ( vm) ;
1384+ tuple. try_into_typed ( vm) ?
1385+ } ;
1386+
1387+ // Check if base has itemsize > 0 - can't add arbitrary slots to variable-size types
1388+ // Types like int, bytes, tuple have itemsize > 0 and don't allow custom slots
1389+ // But types like weakref.ref have itemsize = 0 and DO allow slots
1390+ let has_custom_slots = slots
1391+ . iter ( )
1392+ . any ( |s| s. as_str ( ) != "__dict__" && s. as_str ( ) != "__weakref__" ) ;
1393+ if has_custom_slots && base. slots . itemsize > 0 {
1394+ return Err ( vm. new_type_error ( format ! (
1395+ "nonempty __slots__ not supported for subtype of '{}'" ,
1396+ base. name( )
1397+ ) ) ) ;
1398+ }
13801399
1381- // Validate that all slots are valid identifiers
1382- for slot in slots. iter ( ) {
1383- if !slot. isidentifier ( ) {
1384- return Err ( vm. new_type_error ( "__slots__ must be identifiers" . to_owned ( ) ) ) ;
1400+ // Validate slot names and track duplicates
1401+ let mut seen_dict = false ;
1402+ let mut seen_weakref = false ;
1403+ for slot in slots. iter ( ) {
1404+ // Use isidentifier for validation (handles Unicode properly)
1405+ if !slot. isidentifier ( ) {
1406+ return Err ( vm. new_type_error ( "__slots__ must be identifiers" . to_owned ( ) ) ) ;
1407+ }
1408+
1409+ let slot_name = slot. as_str ( ) ;
1410+
1411+ // Check for duplicate __dict__
1412+ if slot_name == "__dict__" {
1413+ if seen_dict {
1414+ return Err ( vm. new_type_error (
1415+ "__dict__ slot disallowed: we already got one" . to_owned ( ) ,
1416+ ) ) ;
13851417 }
1418+ seen_dict = true ;
13861419 }
13871420
1388- // Check if __dict__ is in slots
1389- let dict_name = "__dict__" ;
1390- let has_dict = slots. iter ( ) . any ( |s| s. as_str ( ) == dict_name) ;
1391-
1392- // Filter out __dict__ from slots
1393- let filtered_slots = if has_dict {
1394- let filtered: Vec < PyStrRef > = slots
1395- . iter ( )
1396- . filter ( |s| s. as_str ( ) != dict_name)
1397- . cloned ( )
1398- . collect ( ) ;
1399- PyTuple :: new_ref_typed ( filtered, & vm. ctx )
1421+ // Check for duplicate __weakref__
1422+ if slot_name == "__weakref__" {
1423+ if seen_weakref {
1424+ return Err ( vm. new_type_error (
1425+ "__weakref__ slot disallowed: we already got one" . to_owned ( ) ,
1426+ ) ) ;
1427+ }
1428+ seen_weakref = true ;
1429+ }
1430+
1431+ // Check if slot name conflicts with class attributes
1432+ if attributes. contains_key ( vm. ctx . intern_str ( slot_name) ) {
1433+ return Err ( vm. new_value_error ( format ! (
1434+ "'{}' in __slots__ conflicts with a class variable" ,
1435+ slot_name
1436+ ) ) ) ;
1437+ }
1438+ }
1439+
1440+ // Check if base class already has __dict__ - can't redefine it
1441+ if seen_dict && base. slots . flags . has_feature ( PyTypeFlags :: HAS_DICT ) {
1442+ return Err (
1443+ vm. new_type_error ( "__dict__ slot disallowed: we already got one" . to_owned ( ) )
1444+ ) ;
1445+ }
1446+
1447+ // Check if base class already has __weakref__ - can't redefine it
1448+ // A base has weakref support if:
1449+ // 1. It's a heap type without explicit __slots__ (automatic weakref), OR
1450+ // 2. It's a heap type with __weakref__ in its __slots__
1451+ if seen_weakref {
1452+ let base_has_weakref = if let Some ( ref ext) = base. heaptype_ext {
1453+ match & ext. slots {
1454+ // Heap type without __slots__ - has automatic weakref
1455+ None => true ,
1456+ // Heap type with __slots__ - check if __weakref__ is in slots
1457+ Some ( base_slots) => base_slots. iter ( ) . any ( |s| s. as_str ( ) == "__weakref__" ) ,
1458+ }
14001459 } else {
1401- slots
1460+ // Builtin type - check if it has __weakref__ descriptor
1461+ let weakref_name = vm. ctx . intern_str ( "__weakref__" ) ;
1462+ base. attributes . read ( ) . contains_key ( weakref_name)
14021463 } ;
14031464
1404- ( Some ( filtered_slots) , has_dict)
1465+ if base_has_weakref {
1466+ return Err ( vm. new_type_error (
1467+ "__weakref__ slot disallowed: we already got one" . to_owned ( ) ,
1468+ ) ) ;
1469+ }
1470+ }
1471+
1472+ // Check if __dict__ is in slots
1473+ let dict_name = "__dict__" ;
1474+ let has_dict = slots. iter ( ) . any ( |s| s. as_str ( ) == dict_name) ;
1475+
1476+ // Filter out __dict__ from slots
1477+ let filtered_slots = if has_dict {
1478+ let filtered: Vec < PyStrRef > = slots
1479+ . iter ( )
1480+ . filter ( |s| s. as_str ( ) != dict_name)
1481+ . cloned ( )
1482+ . collect ( ) ;
1483+ PyTuple :: new_ref_typed ( filtered, & vm. ctx )
14051484 } else {
1406- ( None , false )
1485+ slots
14071486 } ;
14081487
1488+ ( Some ( filtered_slots) , has_dict)
1489+ } else {
1490+ ( None , false )
1491+ } ;
1492+
14091493 // FIXME: this is a temporary fix. multi bases with multiple slots will break object
14101494 let base_member_count = bases
14111495 . iter ( )
@@ -2094,12 +2178,16 @@ fn solid_base<'a>(typ: &'a Py<PyType>, vm: &VirtualMachine) -> &'a Py<PyType> {
20942178 vm. ctx . types . object_type
20952179 } ;
20962180
2097- // TODO: requires itemsize comparison too
2098- if typ. __basicsize__ ( ) != base. __basicsize__ ( ) {
2099- typ
2100- } else {
2101- base
2102- }
2181+ // Check for extra instance variables (CPython's extra_ivars)
2182+ let t_size = typ. __basicsize__ ( ) ;
2183+ let b_size = base. __basicsize__ ( ) ;
2184+ let t_itemsize = typ. slots . itemsize ;
2185+ let b_itemsize = base. slots . itemsize ;
2186+
2187+ // Has extra ivars if: sizes differ AND (has items OR t_size > b_size)
2188+ let has_extra_ivars = t_size != b_size && ( t_itemsize > 0 || b_itemsize > 0 || t_size > b_size) ;
2189+
2190+ if has_extra_ivars { typ } else { base }
21032191}
21042192
21052193fn best_base < ' a > ( bases : & ' a [ PyTypeRef ] , vm : & VirtualMachine ) -> PyResult < & ' a Py < PyType > > {
0 commit comments