Skip to content

Commit eaaafac

Browse files
Merge pull request RustPython#554 from skinny121/isinstance
Call __instancecheck__ and __subclasscheck__
2 parents de32268 + 5779124 commit eaaafac

File tree

7 files changed

+207
-17
lines changed

7 files changed

+207
-17
lines changed

tests/snippets/isinstance.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
2+
class Regular:
3+
pass
4+
5+
6+
assert isinstance(Regular(), Regular)
7+
8+
9+
class MCNotInstanceOf(type):
10+
def __instancecheck__(self, instance):
11+
return False
12+
13+
14+
class NotInstanceOf(metaclass=MCNotInstanceOf):
15+
pass
16+
17+
18+
class InheritedNotInstanceOf(NotInstanceOf):
19+
pass
20+
21+
22+
assert not isinstance(Regular(), NotInstanceOf)
23+
assert not isinstance(1, NotInstanceOf)
24+
25+
# weird cpython behaviour if exact match then isinstance return true
26+
assert isinstance(NotInstanceOf(), NotInstanceOf)
27+
assert not NotInstanceOf.__instancecheck__(NotInstanceOf())
28+
assert not isinstance(InheritedNotInstanceOf(), NotInstanceOf)
29+
30+
31+
class MCAlwaysInstanceOf(type):
32+
def __instancecheck__(self, instance):
33+
return True
34+
35+
36+
class AlwaysInstanceOf(metaclass=MCAlwaysInstanceOf):
37+
pass
38+
39+
40+
assert isinstance(AlwaysInstanceOf(), AlwaysInstanceOf)
41+
assert isinstance(Regular(), AlwaysInstanceOf)
42+
assert isinstance(1, AlwaysInstanceOf)
43+
44+
45+
class MCReturnInt(type):
46+
def __instancecheck__(self, instance):
47+
return 3
48+
49+
50+
class ReturnInt(metaclass=MCReturnInt):
51+
pass
52+
53+
54+
assert isinstance("a", ReturnInt) is True

tests/snippets/issubclass.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
2+
class A:
3+
pass
4+
5+
6+
class B(A):
7+
pass
8+
9+
10+
assert issubclass(A, A)
11+
assert issubclass(B, A)
12+
assert not issubclass(A, B)
13+
14+
15+
class MCNotSubClass(type):
16+
def __subclasscheck__(self, subclass):
17+
return False
18+
19+
20+
class NotSubClass(metaclass=MCNotSubClass):
21+
pass
22+
23+
24+
class InheritedNotSubClass(NotSubClass):
25+
pass
26+
27+
28+
assert not issubclass(A, NotSubClass)
29+
assert not issubclass(NotSubClass, NotSubClass)
30+
assert not issubclass(InheritedNotSubClass, NotSubClass)
31+
assert not issubclass(NotSubClass, InheritedNotSubClass)
32+
33+
34+
class MCAlwaysSubClass(type):
35+
def __subclasscheck__(self, subclass):
36+
return True
37+
38+
39+
class AlwaysSubClass(metaclass=MCAlwaysSubClass):
40+
pass
41+
42+
43+
class InheritedAlwaysSubClass(AlwaysSubClass):
44+
pass
45+
46+
47+
assert issubclass(A, AlwaysSubClass)
48+
assert issubclass(AlwaysSubClass, AlwaysSubClass)
49+
assert issubclass(InheritedAlwaysSubClass, AlwaysSubClass)
50+
assert issubclass(AlwaysSubClass, InheritedAlwaysSubClass)
51+
52+
53+
class MCAVirtualSubClass(type):
54+
def __subclasscheck__(self, subclass):
55+
return subclass is A
56+
57+
58+
class AVirtualSubClass(metaclass=MCAVirtualSubClass):
59+
pass
60+
61+
62+
assert issubclass(A, AVirtualSubClass)
63+
assert not isinstance(B, AVirtualSubClass)

vm/src/builtins.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -349,21 +349,25 @@ fn builtin_id(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
349349
// builtin_input
350350

351351
fn builtin_isinstance(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
352-
arg_check!(vm, args, required = [(obj, None), (typ, None)]);
352+
arg_check!(
353+
vm,
354+
args,
355+
required = [(obj, None), (typ, Some(vm.get_type()))]
356+
);
353357

354-
let isinstance = objtype::isinstance(obj, typ);
355-
Ok(vm.context().new_bool(isinstance))
358+
let isinstance = objtype::real_isinstance(vm, obj, typ)?;
359+
Ok(vm.new_bool(isinstance))
356360
}
357361

358362
fn builtin_issubclass(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
359-
if args.args.len() != 2 {
360-
panic!("issubclass expects exactly two parameters");
361-
}
362-
363-
let cls1 = &args.args[0];
364-
let cls2 = &args.args[1];
363+
arg_check!(
364+
vm,
365+
args,
366+
required = [(subclass, Some(vm.get_type())), (cls, Some(vm.get_type()))]
367+
);
365368

366-
Ok(vm.context().new_bool(objtype::issubclass(cls1, cls2)))
369+
let issubclass = objtype::real_issubclass(vm, subclass, cls)?;
370+
Ok(vm.context().new_bool(issubclass))
367371
}
368372

369373
fn builtin_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
@@ -818,7 +822,7 @@ pub fn builtin_build_class_(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> Py
818822
let mut metaclass = args.get_kwarg("metaclass", vm.get_type());
819823

820824
for base in bases.clone() {
821-
if objtype::issubclass(&base.typ(), &metaclass) {
825+
if objtype::real_issubclass(vm, &base.typ(), &metaclass)? {
822826
metaclass = base.typ();
823827
} else if !objtype::issubclass(&metaclass, &base.typ()) {
824828
return Err(vm.new_type_error("metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases".to_string()));

vm/src/macros.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ macro_rules! type_check {
1818
// None indicates that we have no type requirement (i.e. we accept any type)
1919
if let Some(expected_type) = $arg_type {
2020
let arg = &$args.args[$arg_count];
21-
if !$crate::obj::objtype::isinstance(arg, &expected_type) {
21+
22+
if !$crate::obj::objtype::real_isinstance($vm, arg, &expected_type)? {
2223
let arg_typ = arg.typ();
2324
let expected_type_name = $vm.to_pystr(&expected_type)?;
2425
let actual_type = $vm.to_pystr(&arg_typ)?;

vm/src/obj/objsuper.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ fn super_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
7272
};
7373

7474
// Check obj type:
75-
if !(objtype::isinstance(&py_obj, &py_type) || objtype::issubclass(&py_obj, &py_type)) {
75+
if !(objtype::real_isinstance(vm, &py_obj, &py_type)?
76+
|| objtype::real_issubclass(vm, &py_obj, &py_type)?)
77+
{
7678
return Err(vm.new_type_error(
7779
"super(type, obj): obj must be an instance or subtype of type".to_string(),
7880
));

vm/src/obj/objtype.rs

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use super::objbool;
12
use super::objdict;
23
use super::objstr;
34
use super::objtype; // Required for arg_check! to use isinstance
@@ -8,6 +9,7 @@ use crate::pyobject::{
89
use crate::vm::VirtualMachine;
910
use std::cell::RefCell;
1011
use std::collections::HashMap;
12+
use std::rc::Rc;
1113

1214
/*
1315
* The magical type type
@@ -56,6 +58,16 @@ pub fn init(context: &PyContext) {
5658
"__getattribute__",
5759
context.new_rustfunc(type_getattribute),
5860
);
61+
context.set_attr(
62+
&type_type,
63+
"__instancecheck__",
64+
context.new_rustfunc(type_instance_check),
65+
);
66+
context.set_attr(
67+
&type_type,
68+
"__subclasscheck__",
69+
context.new_rustfunc(type_subclass_check),
70+
);
5971
context.set_attr(&type_type, "__doc__", context.new_str(type_doc.to_string()));
6072
}
6173

@@ -89,16 +101,70 @@ pub fn base_classes(obj: &PyObjectRef) -> Vec<PyObjectRef> {
89101
_mro(obj.typ()).unwrap()
90102
}
91103

104+
/// Determines if `obj` actually an instance of `cls`, this doesn't call __instancecheck__, so only
105+
/// use this if `cls` is known to have not overridden the base __instancecheck__ magic method.
92106
pub fn isinstance(obj: &PyObjectRef, cls: &PyObjectRef) -> bool {
93107
let mro = _mro(obj.typ()).unwrap();
94108
mro.into_iter().any(|c| c.is(&cls))
95109
}
96110

97-
pub fn issubclass(typ: &PyObjectRef, cls: &PyObjectRef) -> bool {
98-
let mro = _mro(typ.clone()).unwrap();
111+
/// Determines if `obj` is an instance of `cls`, either directly, indirectly or virtually via the
112+
/// __instancecheck__ magic method.
113+
pub fn real_isinstance(
114+
vm: &mut VirtualMachine,
115+
obj: &PyObjectRef,
116+
cls: &PyObjectRef,
117+
) -> PyResult<bool> {
118+
// cpython first does an exact check on the type, although documentation doesn't state that
119+
// https://github.com/python/cpython/blob/a24107b04c1277e3c1105f98aff5bfa3a98b33a0/Objects/abstract.c#L2408
120+
if Rc::ptr_eq(&obj.typ(), cls) {
121+
Ok(true)
122+
} else {
123+
let ret = vm.call_method(cls, "__instancecheck__", vec![obj.clone()])?;
124+
objbool::boolval(vm, ret)
125+
}
126+
}
127+
128+
fn type_instance_check(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
129+
arg_check!(
130+
vm,
131+
args,
132+
required = [(typ, Some(vm.ctx.type_type())), (obj, None)]
133+
);
134+
Ok(vm.new_bool(isinstance(obj, typ)))
135+
}
136+
137+
/// Determines if `subclass` is actually a subclass of `cls`, this doesn't call __subclasscheck__,
138+
/// so only use this if `cls` is known to have not overridden the base __subclasscheck__ magic
139+
/// method.
140+
pub fn issubclass(subclass: &PyObjectRef, cls: &PyObjectRef) -> bool {
141+
let mro = _mro(subclass.clone()).unwrap();
99142
mro.into_iter().any(|c| c.is(&cls))
100143
}
101144

145+
/// Determines if `subclass` is a subclass of `cls`, either directly, indirectly or virtually via
146+
/// the __subclasscheck__ magic method.
147+
pub fn real_issubclass(
148+
vm: &mut VirtualMachine,
149+
subclass: &PyObjectRef,
150+
cls: &PyObjectRef,
151+
) -> PyResult<bool> {
152+
let ret = vm.call_method(cls, "__subclasscheck__", vec![subclass.clone()])?;
153+
objbool::boolval(vm, ret)
154+
}
155+
156+
fn type_subclass_check(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
157+
arg_check!(
158+
vm,
159+
args,
160+
required = [
161+
(cls, Some(vm.ctx.type_type())),
162+
(subclass, Some(vm.ctx.type_type()))
163+
]
164+
);
165+
Ok(vm.new_bool(issubclass(subclass, cls)))
166+
}
167+
102168
pub fn get_type_name(typ: &PyObjectRef) -> String {
103169
if let PyObjectPayload::Class { name, .. } = &typ.payload {
104170
name.clone()

vm/src/pyobject.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,7 @@ impl PyFuncArgs {
967967
) -> Result<Option<PyObjectRef>, PyObjectRef> {
968968
match self.get_optional_kwarg(key) {
969969
Some(kwarg) => {
970-
if objtype::isinstance(&kwarg, &ty) {
970+
if objtype::real_isinstance(vm, &kwarg, &ty)? {
971971
Ok(Some(kwarg))
972972
} else {
973973
let expected_ty_name = vm.to_pystr(&ty)?;
@@ -1148,7 +1148,7 @@ impl Signature {
11481148

11491149
for (pos, arg) in args.args.iter().enumerate() {
11501150
if let Some(expected_type) = self.arg_type(pos) {
1151-
if !objtype::isinstance(arg, expected_type) {
1151+
if !objtype::real_isinstance(vm, arg, expected_type)? {
11521152
let arg_typ = arg.typ();
11531153
let expected_type_name = vm.to_pystr(&expected_type)?;
11541154
let actual_type = vm.to_pystr(&arg_typ)?;

0 commit comments

Comments
 (0)