@@ -428,30 +428,51 @@ impl PyObject {
428428 if let ( Ok ( obj) , Ok ( cls) ) = ( self . try_to_ref :: < PyType > ( vm) , cls. try_to_ref :: < PyType > ( vm) ) {
429429 Ok ( obj. fast_issubclass ( cls) )
430430 } else {
431+ // Check if derived is a class
431432 self . check_cls ( self , vm, || {
432433 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)
441447 }
442448 }
443449
444450 /// Determines if `self` is a subclass of `cls`, either directly, indirectly or virtually
445451 /// via the __subclasscheck__ magic method.
452+ /// PyObject_IsSubclass/object_issubclass
446453 pub fn is_subclass ( & self , cls : & PyObject , vm : & VirtualMachine ) -> PyResult < bool > {
454+ // PyType_CheckExact(cls)
447455 if cls. class ( ) . is ( vm. ctx . types . type_type ) {
448456 if self . is ( cls) {
449457 return Ok ( true ) ;
450458 }
451459 return self . recursive_issubclass ( cls, vm) ;
452460 }
453461
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) {
455476 for typ in tuple {
456477 if vm. with_recursion ( "in __subclasscheck__" , || self . is_subclass ( typ, vm) ) ? {
457478 return Ok ( true ) ;
@@ -460,6 +481,7 @@ impl PyObject {
460481 return Ok ( false ) ;
461482 }
462483
484+ // Check for __subclasscheck__ method
463485 if let Some ( meth) = vm. get_special_method ( cls, identifier ! ( vm, __subclasscheck__) ) ? {
464486 let ret = vm. with_recursion ( "in __subclasscheck__" , || {
465487 meth. invoke ( ( self . to_owned ( ) , ) , vm)
@@ -470,51 +492,84 @@ impl PyObject {
470492 self . recursive_issubclass ( cls, vm)
471493 }
472494
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+ }
484512 }
485- } else {
486- false
487513 }
514+ Ok ( retval)
488515 } else {
516+ // Not a type object, check if it's a valid class
489517 self . check_cls ( cls, vm, || {
490518 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 {}" ,
492520 cls. class( )
493521 )
494522 } ) ?;
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+ }
498533 } else {
499- i_cls . abstract_issubclass ( cls , vm ) ?
534+ Ok ( false )
500535 }
501- } ;
502- Ok ( r)
536+ }
503537 }
504538
505539 /// Determines if `self` is an instance of `cls`, either directly, indirectly or virtually via
506540 /// the __instancecheck__ magic method.
541+ // This is object_recursive_isinstance from CPython's Objects/abstract.c
507542 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
510545 if self . class ( ) . is ( cls) {
511546 return Ok ( true ) ;
512547 }
513548
549+ // PyType_CheckExact(cls) optimization
514550 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+ }
516570 }
517571
572+ // Check if cls is a tuple
518573 if let Ok ( tuple) = cls. try_to_ref :: < PyTuple > ( vm) {
519574 for typ in tuple {
520575 if vm. with_recursion ( "in __instancecheck__" , || self . is_instance ( typ, vm) ) ? {
@@ -524,14 +579,16 @@ impl PyObject {
524579 return Ok ( false ) ;
525580 }
526581
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)
530586 } ) ?;
531- return ret . try_to_bool ( vm) ;
587+ return res . try_to_bool ( vm) ;
532588 }
533589
534- self . abstract_isinstance ( cls, vm)
590+ // Fall back to object_isinstance (without going through __instancecheck__ again)
591+ self . real_is_instance ( cls, vm)
535592 }
536593
537594 pub fn hash ( & self , vm : & VirtualMachine ) -> PyResult < PyHash > {
0 commit comments