@@ -410,43 +410,39 @@ impl PyObject {
410
410
}
411
411
412
412
fn abstract_issubclass ( & self , cls : & PyObject , vm : & VirtualMachine ) -> PyResult < bool > {
413
- // # Safety: The lifetime of `derived` is forced to be ignored
414
- let bases = unsafe {
415
- let mut derived = self ;
416
- // First loop: handle single inheritance without recursion
417
- loop {
418
- if derived. is ( cls) {
419
- return Ok ( true ) ;
420
- }
413
+ // Store the current derived class to check
414
+ let mut bases: PyTupleRef ;
415
+ let mut derived = self ;
421
416
422
- let Some ( bases) = derived. abstract_get_bases ( vm) ? else {
423
- return Ok ( false ) ;
424
- } ;
425
- let n = bases. len ( ) ;
426
- match n {
427
- 0 => return Ok ( false ) ,
428
- 1 => {
429
- // Avoid recursion in the single inheritance case
430
- // # safety
431
- // Intention:
432
- // ```
433
- // derived = bases.as_slice()[0].as_object();
434
- // ```
435
- // Though type-system cannot guarantee, derived does live long enough in the loop.
436
- derived = & * ( bases. as_slice ( ) [ 0 ] . as_object ( ) as * const _ ) ;
437
- continue ;
438
- }
439
- _ => {
440
- // Multiple inheritance - break out to handle recursively
441
- break bases;
442
- }
417
+ // First loop: handle single inheritance without recursion
418
+ let bases = loop {
419
+ if derived. is ( cls) {
420
+ return Ok ( true ) ;
421
+ }
422
+
423
+ let Some ( derived_bases) = derived. abstract_get_bases ( vm) ? else {
424
+ return Ok ( false ) ;
425
+ } ;
426
+
427
+ let n = derived_bases. len ( ) ;
428
+ match n {
429
+ 0 => return Ok ( false ) ,
430
+ 1 => {
431
+ // Avoid recursion in the single inheritance case
432
+ // Get the next derived class and continue the loop
433
+ bases = derived_bases;
434
+ derived = & bases. as_slice ( ) [ 0 ] ;
435
+ continue ;
436
+ }
437
+ _ => {
438
+ // Multiple inheritance - handle recursively
439
+ break derived_bases;
443
440
}
444
441
}
445
442
} ;
446
443
447
- // Second loop: handle multiple inheritance with recursion
448
- // At this point we know n >= 2
449
444
let n = bases. len ( ) ;
445
+ // At this point we know n >= 2
450
446
debug_assert ! ( n >= 2 ) ;
451
447
452
448
for i in 0 ..n {
@@ -528,10 +524,10 @@ impl PyObject {
528
524
return Ok ( false ) ;
529
525
}
530
526
531
- // Check for __subclasscheck__ method
532
- if let Some ( checker) = vm . get_special_method ( cls , identifier ! ( vm, __subclasscheck__) ) ? {
527
+ // Check for __subclasscheck__ method using lookup_special (matches CPython)
528
+ if let Some ( checker) = cls . lookup_special ( identifier ! ( vm, __subclasscheck__) , vm ) {
533
529
let res = vm. with_recursion ( "in __subclasscheck__" , || {
534
- checker. invoke ( ( derived. to_owned ( ) , ) , vm)
530
+ checker. call ( ( derived. to_owned ( ) , ) , vm)
535
531
} ) ?;
536
532
return res. try_to_bool ( vm) ;
537
533
}
@@ -542,18 +538,17 @@ impl PyObject {
542
538
/// Real isinstance check without going through __instancecheck__
543
539
/// This is equivalent to CPython's _PyObject_RealIsInstance/object_isinstance
544
540
pub fn real_is_instance ( & self , cls : & PyObject , vm : & VirtualMachine ) -> PyResult < bool > {
545
- if let Ok ( typ ) = cls. try_to_ref :: < PyType > ( vm) {
541
+ if let Ok ( cls ) = cls. try_to_ref :: < PyType > ( vm) {
546
542
// PyType_Check(cls) - cls is a type object
547
- let mut retval = self . fast_isinstance ( typ) ;
548
-
543
+ let mut retval = self . class ( ) . is_subtype ( cls) ;
549
544
if !retval {
550
545
// Check __class__ attribute, only masking AttributeError
551
546
if let Some ( i_cls) =
552
547
vm. get_attribute_opt ( self . to_owned ( ) , identifier ! ( vm, __class__) ) ?
553
548
{
554
549
if let Ok ( i_cls_type) = PyTypeRef :: try_from_object ( vm, i_cls) {
555
550
if !i_cls_type. is ( self . class ( ) ) {
556
- retval = i_cls_type. fast_issubclass ( typ ) ;
551
+ retval = i_cls_type. is_subtype ( cls ) ;
557
552
}
558
553
}
559
554
}
@@ -572,11 +567,7 @@ impl PyObject {
572
567
if let Some ( i_cls) =
573
568
vm. get_attribute_opt ( self . to_owned ( ) , identifier ! ( vm, __class__) ) ?
574
569
{
575
- if vm. is_none ( & i_cls) {
576
- Ok ( false )
577
- } else {
578
- i_cls. abstract_issubclass ( cls, vm)
579
- }
570
+ i_cls. abstract_issubclass ( cls, vm)
580
571
} else {
581
572
Ok ( false )
582
573
}
@@ -624,10 +615,10 @@ impl PyObject {
624
615
return Ok ( false ) ;
625
616
}
626
617
627
- // Check for __instancecheck__ method
628
- if let Some ( checker) = vm . get_special_method ( cls , identifier ! ( vm, __instancecheck__) ) ? {
618
+ // Check for __instancecheck__ method using lookup_special (matches CPython)
619
+ if let Some ( checker) = cls . lookup_special ( identifier ! ( vm, __instancecheck__) , vm ) {
629
620
let res = vm. with_recursion ( "in __instancecheck__" , || {
630
- checker. invoke ( ( self . to_owned ( ) , ) , vm)
621
+ checker. call ( ( self . to_owned ( ) , ) , vm)
631
622
} ) ?;
632
623
return res. try_to_bool ( vm) ;
633
624
}
@@ -754,4 +745,24 @@ impl PyObject {
754
745
755
746
Err ( vm. new_type_error ( format ! ( "'{}' does not support item deletion" , self . class( ) ) ) )
756
747
}
748
+
749
+ /// Equivalent to CPython's _PyObject_LookupSpecial
750
+ /// Looks up a special method in the type's MRO without checking instance dict.
751
+ /// Returns None if not found (masking AttributeError like CPython).
752
+ pub fn lookup_special ( & self , attr : & Py < PyStr > , vm : & VirtualMachine ) -> Option < PyObjectRef > {
753
+ let obj_cls = self . class ( ) ;
754
+
755
+ // Use PyType::lookup_ref (equivalent to CPython's _PyType_LookupRef)
756
+ let res = obj_cls. lookup_ref ( attr, vm) ?;
757
+
758
+ // If it's a descriptor, call its __get__ method
759
+ let descr_get = res. class ( ) . mro_find_map ( |cls| cls. slots . descr_get . load ( ) ) ;
760
+ if let Some ( descr_get) = descr_get {
761
+ let obj_cls = obj_cls. to_owned ( ) . into ( ) ;
762
+ // CPython ignores exceptions in _PyObject_LookupSpecial and returns NULL
763
+ descr_get ( res, Some ( self . to_owned ( ) ) , Some ( obj_cls) , vm) . ok ( )
764
+ } else {
765
+ Some ( res)
766
+ }
767
+ }
757
768
}
0 commit comments