diff --git a/vm/src/builtins/union.rs b/vm/src/builtins/union.rs index 996d3f66543..b8e34a7268f 100644 --- a/vm/src/builtins/union.rs +++ b/vm/src/builtins/union.rs @@ -40,6 +40,12 @@ impl PyUnion { Self { args, parameters } } + /// Direct access to args field, matching CPython's _Py_union_args + #[inline] + pub fn args(&self) -> &PyTupleRef { + &self.args + } + fn repr(&self, vm: &VirtualMachine) -> PyResult { fn repr_item(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { if obj.is(vm.ctx.types.none_type) { diff --git a/vm/src/protocol/object.rs b/vm/src/protocol/object.rs index 61973def4b4..98046bab820 100644 --- a/vm/src/protocol/object.rs +++ b/vm/src/protocol/object.rs @@ -497,30 +497,31 @@ impl PyObject { /// via the __subclasscheck__ magic method. /// PyObject_IsSubclass/object_issubclass pub fn is_subclass(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult { + let derived = self; // PyType_CheckExact(cls) if cls.class().is(vm.ctx.types.type_type) { - if self.is(cls) { + if derived.is(cls) { return Ok(true); } - return self.recursive_issubclass(cls, vm); + return derived.recursive_issubclass(cls, vm); } // Check for Union type - CPython handles this before tuple - let cls_to_check = if cls.class().is(vm.ctx.types.union_type) { + let cls = if cls.class().is(vm.ctx.types.union_type) { // Get the __args__ attribute which contains the union members - if let Ok(args) = cls.get_attr(identifier!(vm, __args__), vm) { - args - } else { - cls.to_owned() - } + // Match CPython's _Py_union_args which directly accesses the args field + let union = cls + .downcast_ref::() + .expect("union is already checked"); + union.args().as_object() } else { - cls.to_owned() + cls }; - // Check if cls_to_check is a tuple - if let Ok(tuple) = cls_to_check.try_to_value::<&Py>(vm) { - for typ in tuple { - if vm.with_recursion("in __subclasscheck__", || self.is_subclass(typ, vm))? { + // Check if cls is a tuple + if let Some(tuple) = cls.downcast_ref::() { + for item in tuple { + if vm.with_recursion("in __subclasscheck__", || derived.is_subclass(item, vm))? { return Ok(true); } } @@ -528,14 +529,14 @@ impl PyObject { } // Check for __subclasscheck__ method - if let Some(meth) = vm.get_special_method(cls, identifier!(vm, __subclasscheck__))? { - let ret = vm.with_recursion("in __subclasscheck__", || { - meth.invoke((self.to_owned(),), vm) + if let Some(checker) = vm.get_special_method(cls, identifier!(vm, __subclasscheck__))? { + let res = vm.with_recursion("in __subclasscheck__", || { + checker.invoke((derived.to_owned(),), vm) })?; - return ret.try_to_bool(vm); + return res.try_to_bool(vm); } - self.recursive_issubclass(cls, vm) + derived.recursive_issubclass(cls, vm) } /// Real isinstance check without going through __instancecheck__ @@ -601,16 +602,14 @@ impl PyObject { // Check for Union type (e.g., int | str) - CPython checks this before tuple if cls.class().is(vm.ctx.types.union_type) { - if let Ok(args) = cls.get_attr(identifier!(vm, __args__), vm) { - if let Ok(tuple) = args.try_to_ref::(vm) { - for typ in tuple { - if vm - .with_recursion("in __instancecheck__", || self.is_instance(typ, vm))? - { - return Ok(true); - } - } - return Ok(false); + // Match CPython's _Py_union_args which directly accesses the args field + let union = cls + .try_to_ref::(vm) + .expect("checked by is"); + let tuple = union.args(); + for typ in tuple.iter() { + if vm.with_recursion("in __instancecheck__", || self.is_instance(typ, vm))? { + return Ok(true); } } }