@@ -428,30 +428,51 @@ impl PyObject {
428
428
if let ( Ok ( obj) , Ok ( cls) ) = ( self . try_to_ref :: < PyType > ( vm) , cls. try_to_ref :: < PyType > ( vm) ) {
429
429
Ok ( obj. fast_issubclass ( cls) )
430
430
} else {
431
+ // Check if derived is a class
431
432
self . check_cls ( self , vm, || {
432
433
format ! ( "issubclass() arg 1 must be a class, not {}" , self . class( ) )
433
- } )
434
- . and ( self . check_cls ( cls, vm, || {
435
- format ! (
436
- "issubclass() arg 2 must be a class, a tuple of classes, or a union, not {}" ,
437
- cls. class( )
438
- )
439
- } ) )
440
- . and ( self . abstract_issubclass ( cls, vm) )
434
+ } ) ?;
435
+
436
+ // Check if cls is a class, tuple, or union
437
+ if !cls. class ( ) . is ( vm. ctx . types . union_type ) {
438
+ self . check_cls ( cls, vm, || {
439
+ format ! (
440
+ "issubclass() arg 2 must be a class, a tuple of classes, or a union, not {}" ,
441
+ cls. class( )
442
+ )
443
+ } ) ?;
444
+ }
445
+
446
+ self . abstract_issubclass ( cls, vm)
441
447
}
442
448
}
443
449
444
450
/// Determines if `self` is a subclass of `cls`, either directly, indirectly or virtually
445
451
/// via the __subclasscheck__ magic method.
452
+ /// PyObject_IsSubclass/object_issubclass
446
453
pub fn is_subclass ( & self , cls : & PyObject , vm : & VirtualMachine ) -> PyResult < bool > {
454
+ // PyType_CheckExact(cls)
447
455
if cls. class ( ) . is ( vm. ctx . types . type_type ) {
448
456
if self . is ( cls) {
449
457
return Ok ( true ) ;
450
458
}
451
459
return self . recursive_issubclass ( cls, vm) ;
452
460
}
453
461
454
- if let Ok ( tuple) = cls. try_to_value :: < & Py < PyTuple > > ( vm) {
462
+ // Check for Union type - CPython handles this before tuple
463
+ let cls_to_check = if cls. class ( ) . is ( vm. ctx . types . union_type ) {
464
+ // Get the __args__ attribute which contains the union members
465
+ if let Ok ( args) = cls. get_attr ( identifier ! ( vm, __args__) , vm) {
466
+ args
467
+ } else {
468
+ cls. to_owned ( )
469
+ }
470
+ } else {
471
+ cls. to_owned ( )
472
+ } ;
473
+
474
+ // Check if cls_to_check is a tuple
475
+ if let Ok ( tuple) = cls_to_check. try_to_value :: < & Py < PyTuple > > ( vm) {
455
476
for typ in tuple {
456
477
if vm. with_recursion ( "in __subclasscheck__" , || self . is_subclass ( typ, vm) ) ? {
457
478
return Ok ( true ) ;
@@ -460,6 +481,7 @@ impl PyObject {
460
481
return Ok ( false ) ;
461
482
}
462
483
484
+ // Check for __subclasscheck__ method
463
485
if let Some ( meth) = vm. get_special_method ( cls, identifier ! ( vm, __subclasscheck__) ) ? {
464
486
let ret = vm. with_recursion ( "in __subclasscheck__" , || {
465
487
meth. invoke ( ( self . to_owned ( ) , ) , vm)
@@ -470,51 +492,84 @@ impl PyObject {
470
492
self . recursive_issubclass ( cls, vm)
471
493
}
472
494
473
- fn abstract_isinstance ( & self , cls : & PyObject , vm : & VirtualMachine ) -> PyResult < bool > {
474
- let r = if let Ok ( typ) = cls. try_to_ref :: < PyType > ( vm) {
475
- if self . class ( ) . fast_issubclass ( typ) {
476
- true
477
- } else if let Ok ( i_cls) =
478
- PyTypeRef :: try_from_object ( vm, self . get_attr ( identifier ! ( vm, __class__) , vm) ?)
479
- {
480
- if i_cls. is ( self . class ( ) ) {
481
- false
482
- } else {
483
- i_cls. fast_issubclass ( typ)
495
+ /// Real isinstance check without going through __instancecheck__
496
+ /// This is equivalent to CPython's _PyObject_RealIsInstance/object_isinstance
497
+ pub fn real_is_instance ( & self , cls : & PyObject , vm : & VirtualMachine ) -> PyResult < bool > {
498
+ if let Ok ( typ) = cls. try_to_ref :: < PyType > ( vm) {
499
+ // PyType_Check(cls) - cls is a type object
500
+ let mut retval = self . fast_isinstance ( typ) ;
501
+
502
+ if !retval {
503
+ // Check __class__ attribute, only masking AttributeError
504
+ if let Some ( i_cls) =
505
+ vm. get_attribute_opt ( self . to_owned ( ) , identifier ! ( vm, __class__) ) ?
506
+ {
507
+ if let Ok ( i_cls_type) = PyTypeRef :: try_from_object ( vm, i_cls) {
508
+ if !i_cls_type. is ( self . class ( ) ) {
509
+ retval = i_cls_type. fast_issubclass ( typ) ;
510
+ }
511
+ }
484
512
}
485
- } else {
486
- false
487
513
}
514
+ Ok ( retval)
488
515
} else {
516
+ // Not a type object, check if it's a valid class
489
517
self . check_cls ( cls, vm, || {
490
518
format ! (
491
- "isinstance() arg 2 must be a type or tuple of types, not {}" ,
519
+ "isinstance() arg 2 must be a type, a tuple of types, or a union , not {}" ,
492
520
cls. class( )
493
521
)
494
522
} ) ?;
495
- let i_cls: PyObjectRef = self . get_attr ( identifier ! ( vm, __class__) , vm) ?;
496
- if vm. is_none ( & i_cls) {
497
- false
523
+
524
+ // Get __class__ attribute and check, only masking AttributeError
525
+ if let Some ( i_cls) =
526
+ vm. get_attribute_opt ( self . to_owned ( ) , identifier ! ( vm, __class__) ) ?
527
+ {
528
+ if vm. is_none ( & i_cls) {
529
+ Ok ( false )
530
+ } else {
531
+ i_cls. abstract_issubclass ( cls, vm)
532
+ }
498
533
} else {
499
- i_cls . abstract_issubclass ( cls , vm ) ?
534
+ Ok ( false )
500
535
}
501
- } ;
502
- Ok ( r)
536
+ }
503
537
}
504
538
505
539
/// Determines if `self` is an instance of `cls`, either directly, indirectly or virtually via
506
540
/// the __instancecheck__ magic method.
541
+ // This is object_recursive_isinstance from CPython's Objects/abstract.c
507
542
pub fn is_instance ( & self , cls : & PyObject , vm : & VirtualMachine ) -> PyResult < bool > {
508
- // cpython first does an exact check on the type, although documentation doesn't state that
509
- // https://github.com/python/cpython/blob/a24107b04c1277e3c1105f98aff5bfa3a98b33a0/Objects/abstract.c#L2408
543
+ // PyObject_TypeCheck(inst, (PyTypeObject *)cls)
544
+ // This is an exact check of the type
510
545
if self . class ( ) . is ( cls) {
511
546
return Ok ( true ) ;
512
547
}
513
548
549
+ // PyType_CheckExact(cls) optimization
514
550
if cls. class ( ) . is ( vm. ctx . types . type_type ) {
515
- return self . abstract_isinstance ( cls, vm) ;
551
+ // When cls is exactly a type (not a subclass), use real_is_instance
552
+ // to avoid going through __instancecheck__ (matches CPython behavior)
553
+ return self . real_is_instance ( cls, vm) ;
554
+ }
555
+
556
+ // Check for Union type (e.g., int | str) - CPython checks this before tuple
557
+ if cls. class ( ) . is ( vm. ctx . types . union_type ) {
558
+ if let Ok ( args) = cls. get_attr ( identifier ! ( vm, __args__) , vm) {
559
+ if let Ok ( tuple) = args. try_to_ref :: < PyTuple > ( vm) {
560
+ for typ in tuple {
561
+ if vm
562
+ . with_recursion ( "in __instancecheck__" , || self . is_instance ( typ, vm) ) ?
563
+ {
564
+ return Ok ( true ) ;
565
+ }
566
+ }
567
+ return Ok ( false ) ;
568
+ }
569
+ }
516
570
}
517
571
572
+ // Check if cls is a tuple
518
573
if let Ok ( tuple) = cls. try_to_ref :: < PyTuple > ( vm) {
519
574
for typ in tuple {
520
575
if vm. with_recursion ( "in __instancecheck__" , || self . is_instance ( typ, vm) ) ? {
@@ -524,14 +579,16 @@ impl PyObject {
524
579
return Ok ( false ) ;
525
580
}
526
581
527
- if let Some ( meth) = vm. get_special_method ( cls, identifier ! ( vm, __instancecheck__) ) ? {
528
- let ret = vm. with_recursion ( "in __instancecheck__" , || {
529
- meth. invoke ( ( self . to_owned ( ) , ) , vm)
582
+ // Check for __instancecheck__ method
583
+ if let Some ( checker) = vm. get_special_method ( cls, identifier ! ( vm, __instancecheck__) ) ? {
584
+ let res = vm. with_recursion ( "in __instancecheck__" , || {
585
+ checker. invoke ( ( self . to_owned ( ) , ) , vm)
530
586
} ) ?;
531
- return ret . try_to_bool ( vm) ;
587
+ return res . try_to_bool ( vm) ;
532
588
}
533
589
534
- self . abstract_isinstance ( cls, vm)
590
+ // Fall back to object_isinstance (without going through __instancecheck__ again)
591
+ self . real_is_instance ( cls, vm)
535
592
}
536
593
537
594
pub fn hash ( & self , vm : & VirtualMachine ) -> PyResult < PyHash > {
0 commit comments