diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index a611142aed..8487a0b1d9 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -709,27 +709,63 @@ def test_hash(self): self.assertEqual(hash(int | str), hash(str | int)) self.assertEqual(hash(int | str), hash(typing.Union[int, str])) - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_instancecheck(self): - x = int | str - self.assertIsInstance(1, x) - self.assertIsInstance(True, x) - self.assertIsInstance('a', x) - self.assertNotIsInstance(None, x) - self.assertTrue(issubclass(int, x)) - self.assertTrue(issubclass(bool, x)) - self.assertTrue(issubclass(str, x)) - self.assertFalse(issubclass(type(None), x)) - x = int | None - self.assertIsInstance(None, x) - self.assertTrue(issubclass(type(None), x)) - x = int | collections.abc.Mapping - self.assertIsInstance({}, x) - self.assertTrue(issubclass(dict, x)) + def test_instancecheck_and_subclasscheck(self): + for x in (int | str, typing.Union[int, str]): + with self.subTest(x=x): + self.assertIsInstance(1, x) + self.assertIsInstance(True, x) + self.assertIsInstance('a', x) + self.assertNotIsInstance(None, x) + self.assertTrue(issubclass(int, x)) + self.assertTrue(issubclass(bool, x)) + self.assertTrue(issubclass(str, x)) + self.assertFalse(issubclass(type(None), x)) + + for x in (int | None, typing.Union[int, None]): + with self.subTest(x=x): + self.assertIsInstance(None, x) + self.assertTrue(issubclass(type(None), x)) + + for x in ( + int | collections.abc.Mapping, + typing.Union[int, collections.abc.Mapping], + ): + with self.subTest(x=x): + self.assertIsInstance({}, x) + self.assertNotIsInstance((), x) + self.assertTrue(issubclass(dict, x)) + self.assertFalse(issubclass(list, x)) + + def test_instancecheck_and_subclasscheck_order(self): + T = typing.TypeVar('T') + + will_resolve = ( + int | T, + typing.Union[int, T], + ) + for x in will_resolve: + with self.subTest(x=x): + self.assertIsInstance(1, x) + self.assertTrue(issubclass(int, x)) + + wont_resolve = ( + T | int, + typing.Union[T, int], + ) + for x in wont_resolve: + with self.subTest(x=x): + with self.assertRaises(TypeError): + issubclass(int, x) + with self.assertRaises(TypeError): + isinstance(1, x) + + for x in (*will_resolve, *wont_resolve): + with self.subTest(x=x): + with self.assertRaises(TypeError): + issubclass(object, x) + with self.assertRaises(TypeError): + isinstance(object(), x) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_bad_instancecheck(self): class BadMeta(type): def __instancecheck__(cls, inst): @@ -738,8 +774,6 @@ def __instancecheck__(cls, inst): self.assertTrue(isinstance(1, x)) self.assertRaises(ZeroDivisionError, isinstance, [], x) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_bad_subclasscheck(self): class BadMeta(type): def __subclasscheck__(cls, sub): diff --git a/vm/src/builtins/union.rs b/vm/src/builtins/union.rs index d43fbc10b6..461c1af37e 100644 --- a/vm/src/builtins/union.rs +++ b/vm/src/builtins/union.rs @@ -90,15 +90,33 @@ impl PyUnion { } #[pymethod(magic)] - fn instancecheck(_zelf: PyRef, _obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm - .new_type_error("isinstance() argument 2 cannot be a parameterized generic".to_owned())) + fn instancecheck(zelf: PyRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if zelf + .args + .iter() + .any(|x| x.class().is(vm.ctx.types.generic_alias_type)) + { + Err(vm.new_type_error( + "isinstance() argument 2 cannot be a parameterized generic".to_owned(), + )) + } else { + obj.is_instance(zelf.args().as_object(), vm) + } } #[pymethod(magic)] - fn subclasscheck(_zelf: PyRef, _obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm - .new_type_error("issubclass() argument 2 cannot be a parameterized generic".to_owned())) + fn subclasscheck(zelf: PyRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if zelf + .args + .iter() + .any(|x| x.class().is(vm.ctx.types.generic_alias_type)) + { + Err(vm.new_type_error( + "issubclass() argument 2 cannot be a parameterized generic".to_owned(), + )) + } else { + obj.is_subclass(zelf.args().as_object(), vm) + } } #[pymethod(name = "__ror__")] diff --git a/vm/src/protocol/object.rs b/vm/src/protocol/object.rs index 79e252c4e2..ca7aa1455e 100644 --- a/vm/src/protocol/object.rs +++ b/vm/src/protocol/object.rs @@ -398,7 +398,7 @@ impl PyObject { }) .and(self.check_cls(cls, vm, || { format!( - "issubclass() arg 2 must be a class or tuple of classes, not {}", + "issubclass() arg 2 must be a class, a tuple of classes, or a union, not {}", cls.class() ) }))