Skip to content

Commit ac48968

Browse files
authored
Merge pull request RustPython#3984 from TwoPair/fix-issubclass
Fix `instancecheck`, `subclasscheck` of `UnionType`
2 parents 2c07946 + ed001b4 commit ac48968

File tree

3 files changed

+81
-29
lines changed

3 files changed

+81
-29
lines changed

Lib/test/test_types.py

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -709,27 +709,63 @@ def test_hash(self):
709709
self.assertEqual(hash(int | str), hash(str | int))
710710
self.assertEqual(hash(int | str), hash(typing.Union[int, str]))
711711

712-
# TODO: RUSTPYTHON
713-
@unittest.expectedFailure
714-
def test_instancecheck(self):
715-
x = int | str
716-
self.assertIsInstance(1, x)
717-
self.assertIsInstance(True, x)
718-
self.assertIsInstance('a', x)
719-
self.assertNotIsInstance(None, x)
720-
self.assertTrue(issubclass(int, x))
721-
self.assertTrue(issubclass(bool, x))
722-
self.assertTrue(issubclass(str, x))
723-
self.assertFalse(issubclass(type(None), x))
724-
x = int | None
725-
self.assertIsInstance(None, x)
726-
self.assertTrue(issubclass(type(None), x))
727-
x = int | collections.abc.Mapping
728-
self.assertIsInstance({}, x)
729-
self.assertTrue(issubclass(dict, x))
712+
def test_instancecheck_and_subclasscheck(self):
713+
for x in (int | str, typing.Union[int, str]):
714+
with self.subTest(x=x):
715+
self.assertIsInstance(1, x)
716+
self.assertIsInstance(True, x)
717+
self.assertIsInstance('a', x)
718+
self.assertNotIsInstance(None, x)
719+
self.assertTrue(issubclass(int, x))
720+
self.assertTrue(issubclass(bool, x))
721+
self.assertTrue(issubclass(str, x))
722+
self.assertFalse(issubclass(type(None), x))
723+
724+
for x in (int | None, typing.Union[int, None]):
725+
with self.subTest(x=x):
726+
self.assertIsInstance(None, x)
727+
self.assertTrue(issubclass(type(None), x))
728+
729+
for x in (
730+
int | collections.abc.Mapping,
731+
typing.Union[int, collections.abc.Mapping],
732+
):
733+
with self.subTest(x=x):
734+
self.assertIsInstance({}, x)
735+
self.assertNotIsInstance((), x)
736+
self.assertTrue(issubclass(dict, x))
737+
self.assertFalse(issubclass(list, x))
738+
739+
def test_instancecheck_and_subclasscheck_order(self):
740+
T = typing.TypeVar('T')
741+
742+
will_resolve = (
743+
int | T,
744+
typing.Union[int, T],
745+
)
746+
for x in will_resolve:
747+
with self.subTest(x=x):
748+
self.assertIsInstance(1, x)
749+
self.assertTrue(issubclass(int, x))
750+
751+
wont_resolve = (
752+
T | int,
753+
typing.Union[T, int],
754+
)
755+
for x in wont_resolve:
756+
with self.subTest(x=x):
757+
with self.assertRaises(TypeError):
758+
issubclass(int, x)
759+
with self.assertRaises(TypeError):
760+
isinstance(1, x)
761+
762+
for x in (*will_resolve, *wont_resolve):
763+
with self.subTest(x=x):
764+
with self.assertRaises(TypeError):
765+
issubclass(object, x)
766+
with self.assertRaises(TypeError):
767+
isinstance(object(), x)
730768

731-
# TODO: RUSTPYTHON
732-
@unittest.expectedFailure
733769
def test_bad_instancecheck(self):
734770
class BadMeta(type):
735771
def __instancecheck__(cls, inst):
@@ -738,8 +774,6 @@ def __instancecheck__(cls, inst):
738774
self.assertTrue(isinstance(1, x))
739775
self.assertRaises(ZeroDivisionError, isinstance, [], x)
740776

741-
# TODO: RUSTPYTHON
742-
@unittest.expectedFailure
743777
def test_bad_subclasscheck(self):
744778
class BadMeta(type):
745779
def __subclasscheck__(cls, sub):

vm/src/builtins/union.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,33 @@ impl PyUnion {
9090
}
9191

9292
#[pymethod(magic)]
93-
fn instancecheck(_zelf: PyRef<Self>, _obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
94-
Err(vm
95-
.new_type_error("isinstance() argument 2 cannot be a parameterized generic".to_owned()))
93+
fn instancecheck(zelf: PyRef<Self>, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
94+
if zelf
95+
.args
96+
.iter()
97+
.any(|x| x.class().is(vm.ctx.types.generic_alias_type))
98+
{
99+
Err(vm.new_type_error(
100+
"isinstance() argument 2 cannot be a parameterized generic".to_owned(),
101+
))
102+
} else {
103+
obj.is_instance(zelf.args().as_object(), vm)
104+
}
96105
}
97106

98107
#[pymethod(magic)]
99-
fn subclasscheck(_zelf: PyRef<Self>, _obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
100-
Err(vm
101-
.new_type_error("issubclass() argument 2 cannot be a parameterized generic".to_owned()))
108+
fn subclasscheck(zelf: PyRef<Self>, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
109+
if zelf
110+
.args
111+
.iter()
112+
.any(|x| x.class().is(vm.ctx.types.generic_alias_type))
113+
{
114+
Err(vm.new_type_error(
115+
"issubclass() argument 2 cannot be a parameterized generic".to_owned(),
116+
))
117+
} else {
118+
obj.is_subclass(zelf.args().as_object(), vm)
119+
}
102120
}
103121

104122
#[pymethod(name = "__ror__")]

vm/src/protocol/object.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ impl PyObject {
398398
})
399399
.and(self.check_cls(cls, vm, || {
400400
format!(
401-
"issubclass() arg 2 must be a class or tuple of classes, not {}",
401+
"issubclass() arg 2 must be a class, a tuple of classes, or a union, not {}",
402402
cls.class()
403403
)
404404
}))

0 commit comments

Comments
 (0)