Skip to content

Commit 5c082e8

Browse files
committed
tempt
1 parent c396299 commit 5c082e8

4 files changed

Lines changed: 105 additions & 71 deletions

File tree

Lib/_py_abc.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,29 @@ def _abc_caches_clear(cls):
9191

9292
def __instancecheck__(cls, instance):
9393
"""Override for isinstance(instance, cls)."""
94-
# Inline the cache checking
95-
subclass = instance.__class__
96-
if subclass in cls._abc_cache:
97-
return True
98-
subtype = type(instance)
99-
if subtype is subclass:
100-
if (cls._abc_negative_cache_version ==
101-
ABCMeta._abc_invalidation_counter and
102-
subclass in cls._abc_negative_cache):
103-
return False
104-
# Fall back to the subclass check.
105-
return cls.__subclasscheck__(subclass)
106-
return any(cls.__subclasscheck__(c) for c in (subclass, subtype))
94+
# import traceback
95+
# print("ABCMeta.__instancecheck__ called")
96+
# traceback.print_stack()
97+
# print("now running _py_abc instancecheck")
98+
99+
def _abc_instancecheck(cls, instance):
100+
# Inline the cache checking
101+
subclass = instance.__class__
102+
# if subclass in cls._abc_cache:
103+
# return True
104+
subtype = type(instance)
105+
if subtype is subclass:
106+
# if (cls._abc_negative_cache_version ==
107+
# ABCMeta._abc_invalidation_counter and
108+
# subclass in cls._abc_negative_cache):
109+
# return False
110+
# Fall back to the subclass check.
111+
return cls.__subclasscheck__(subclass)
112+
return any(cls.__subclasscheck__(c) for c in (subclass, subtype))
113+
114+
r = _abc_instancecheck(cls, instance)
115+
# print("ABCMeta.__instancecheck__ returning", r)
116+
return r
107117

108118
def __subclasscheck__(cls, subclass):
109119
"""Override for issubclass(subclass, cls)."""

vm/src/builtins/type.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use super::{
33
mappingproxy::PyMappingProxy, object, union_,
44
};
55
use crate::{
6-
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
7-
VirtualMachine,
6+
AsObject, Context, Py, PyExact, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
7+
TryFromObject, VirtualMachine,
88
builtins::{
99
PyBaseExceptionRef,
1010
descriptor::{
@@ -155,6 +155,15 @@ fn downcast_qualname(value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyRef<
155155
}
156156
}
157157

158+
fn is_subtype_with_mro(a_mro: &[PyTypeRef], _a: &Py<PyType>, b: &Py<PyType>) -> bool {
159+
for item in a_mro {
160+
if item.is(b) {
161+
return true;
162+
}
163+
}
164+
false
165+
}
166+
158167
impl PyType {
159168
pub fn new_simple_heap(
160169
name: &str,
@@ -194,6 +203,12 @@ impl PyType {
194203
Self::new_heap_inner(base, bases, attrs, slots, heaptype_ext, metaclass, ctx)
195204
}
196205

206+
/// Equivalent to CPython's PyType_Check macro
207+
/// Checks if obj is an instance of type (or its subclass)
208+
pub(crate) fn check<'a>(obj: &'a PyObject) -> Option<&'a Py<Self>> {
209+
obj.downcast_ref::<Self>()
210+
}
211+
197212
fn resolve_mro(bases: &[PyRef<Self>]) -> Result<Vec<PyTypeRef>, String> {
198213
// Check for duplicates in bases.
199214
let mut unique_bases = HashSet::new();
@@ -436,10 +451,20 @@ impl PyType {
436451
}
437452

438453
impl Py<PyType> {
454+
pub(crate) fn is_subtype(&self, other: &Py<PyType>) -> bool {
455+
is_subtype_with_mro(&*self.mro.read(), self, other)
456+
}
457+
458+
/// Equivalent to CPython's PyType_CheckExact macro
459+
/// Checks if obj is exactly a type (not a subclass)
460+
pub fn check_exact<'a>(obj: &'a PyObject, vm: &VirtualMachine) -> Option<&'a Py<PyType>> {
461+
obj.downcast_ref_if_exact::<PyType>(vm)
462+
}
463+
439464
/// Determines if `subclass` is actually a subclass of `cls`, this doesn't call __subclasscheck__,
440465
/// so only use this if `cls` is known to have not overridden the base __subclasscheck__ magic
441466
/// method.
442-
pub fn fast_issubclass(&self, cls: &impl Borrow<crate::PyObject>) -> bool {
467+
pub fn fast_issubclass(&self, cls: &impl Borrow<PyObject>) -> bool {
443468
self.as_object().is(cls.borrow()) || self.mro.read().iter().any(|c| c.is(cls.borrow()))
444469
}
445470

vm/src/builtins/union.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ impl PyUnion {
4040
Self { args, parameters }
4141
}
4242

43+
/// Direct access to args field, matching CPython's _Py_union_args
44+
pub fn get_args(&self) -> &PyTupleRef {
45+
&self.args
46+
}
47+
4348
fn repr(&self, vm: &VirtualMachine) -> PyResult<String> {
4449
fn repr_item(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
4550
if obj.is(vm.ctx.types.none_type) {

vm/src/protocol/object.rs

Lines changed: 49 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -410,48 +410,42 @@ impl PyObject {
410410

411411
fn abstract_issubclass(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
412412
let mut derived = self;
413-
let mut first_item: PyObjectRef;
414-
let tuple;
415413

416414
// First loop: handle single inheritance without recursion
417-
loop {
415+
let bases = loop {
418416
if derived.is(cls) {
419417
return Ok(true);
420418
}
421419

422-
match derived.abstract_get_bases(vm)? {
423-
Some(bases) => {
424-
let n = bases.len();
425-
match n {
426-
0 => return Ok(false),
427-
1 => {
428-
// Avoid recursion in the single inheritance case
429-
first_item = bases.fast_getitem(0).clone();
430-
derived = &first_item;
431-
continue;
432-
}
433-
_ => {
434-
// Multiple inheritance - break out to handle recursively
435-
tuple = bases;
436-
break;
437-
}
438-
}
420+
let Some(bases) = derived.abstract_get_bases(vm)? else {
421+
return Ok(false);
422+
};
423+
let n = bases.len();
424+
match n {
425+
0 => return Ok(false),
426+
1 => {
427+
// Avoid recursion in the single inheritance case
428+
// # safety
429+
// Intention: bases.as_slice()[0].as_object();
430+
// Though type-system cannot guarantee, derived does live long enough in the loop.
431+
derived = unsafe { &*(bases.as_slice()[0].as_object() as *const _) };
432+
continue;
439433
}
440-
None => {
441-
// abstract_get_bases returned None (no valid __bases__ or not a tuple)
442-
return Ok(false);
434+
_ => {
435+
// Multiple inheritance - break out to handle recursively
436+
break bases;
443437
}
444438
}
445-
}
439+
};
446440

447441
// Second loop: handle multiple inheritance with recursion
448442
// At this point we know n >= 2
449-
let n = tuple.len();
443+
let n = bases.len();
450444
assert!(n >= 2);
451445

452446
for i in 0..n {
453447
let result = vm.with_recursion("in __issubclass__", || {
454-
tuple.fast_getitem(i).abstract_issubclass(cls, vm)
448+
bases.as_slice()[i].abstract_issubclass(cls, vm)
455449
})?;
456450
if result {
457451
return Ok(true);
@@ -463,24 +457,25 @@ impl PyObject {
463457

464458
fn recursive_issubclass(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
465459
// Fast path for both being types (matches CPython's PyType_Check)
466-
if let (Ok(obj), Ok(cls)) = (self.try_to_ref::<PyType>(vm), cls.try_to_ref::<PyType>(vm)) {
460+
if let Some(cls) = PyType::check(cls)
461+
&& let Some(derived) = PyType::check(self)
462+
{
467463
// PyType_IsSubtype equivalent
468-
Ok(obj.fast_issubclass(cls))
469-
} else {
470-
// Check if derived is a class
471-
self.check_class(self, vm, || {
472-
"issubclass() arg 1 must be a class".to_string()
473-
})?;
474-
475-
// Check if cls is a class, tuple, or union (matches CPython's order and message)
476-
if !cls.class().is(vm.ctx.types.union_type) {
477-
self.check_class(cls, vm, || {
478-
"issubclass() arg 2 must be a class, a tuple of classes, or a union".to_string()
479-
})?;
480-
}
464+
return Ok(derived.is_subtype(cls));
465+
}
466+
// Check if derived is a class
467+
self.check_class(self, vm, || {
468+
"issubclass() arg 1 must be a class".to_string()
469+
})?;
481470

482-
self.abstract_issubclass(cls, vm)
471+
// Check if cls is a class, tuple, or union (matches CPython's order and message)
472+
if !cls.class().is(vm.ctx.types.union_type) {
473+
self.check_class(cls, vm, || {
474+
"issubclass() arg 2 must be a class, a tuple of classes, or a union".to_string()
475+
})?;
483476
}
477+
478+
self.abstract_issubclass(cls, vm)
484479
}
485480

486481
/// Real issubclass check without going through __subclasscheck__
@@ -504,8 +499,9 @@ impl PyObject {
504499
// Check for Union type - CPython handles this before tuple
505500
let cls_to_check = if cls.class().is(vm.ctx.types.union_type) {
506501
// Get the __args__ attribute which contains the union members
507-
if let Ok(args) = cls.get_attr(identifier!(vm, __args__), vm) {
508-
args
502+
// Match CPython's _Py_union_args which directly accesses the args field
503+
if let Ok(union) = cls.try_to_ref::<crate::builtins::PyUnion>(vm) {
504+
union.get_args().clone().into()
509505
} else {
510506
cls.to_owned()
511507
}
@@ -556,7 +552,7 @@ impl PyObject {
556552
Ok(retval)
557553
} else {
558554
// Not a type object, check if it's a valid class
559-
self.check_cls(cls, vm, || {
555+
self.check_class(cls, vm, || {
560556
format!(
561557
"isinstance() arg 2 must be a type, a tuple of types, or a union, not {}",
562558
cls.class()
@@ -589,25 +585,23 @@ impl PyObject {
589585
}
590586

591587
// PyType_CheckExact(cls) optimization
592-
if cls.class().is(vm.ctx.types.type_type) {
588+
if cls.is(vm.ctx.types.type_type) {
593589
// When cls is exactly a type (not a subclass), use real_is_instance
594590
// to avoid going through __instancecheck__ (matches CPython behavior)
595591
return self.real_is_instance(cls, vm);
596592
}
597593

598594
// Check for Union type (e.g., int | str) - CPython checks this before tuple
599-
if cls.class().is(vm.ctx.types.union_type) {
600-
if let Ok(args) = cls.get_attr(identifier!(vm, __args__), vm) {
601-
if let Ok(tuple) = args.try_to_ref::<PyTuple>(vm) {
602-
for typ in tuple {
603-
if vm
604-
.with_recursion("in __instancecheck__", || self.is_instance(typ, vm))?
605-
{
606-
return Ok(true);
607-
}
595+
if cls.is(vm.ctx.types.union_type) {
596+
// Match CPython's _Py_union_args which directly accesses the args field
597+
if let Ok(union) = cls.try_to_ref::<crate::builtins::PyUnion>(vm) {
598+
let tuple = union.get_args();
599+
for typ in tuple.iter() {
600+
if vm.with_recursion("in __instancecheck__", || self.is_instance(typ, vm))? {
601+
return Ok(true);
608602
}
609-
return Ok(false);
610603
}
604+
return Ok(false);
611605
}
612606
}
613607

0 commit comments

Comments
 (0)