Skip to content

Commit 0344fa1

Browse files
committed
py: Fix builtin callable so it checks user-defined instances correctly.
Addresses issue micropython#953.
1 parent 2cd79fa commit 0344fa1

File tree

4 files changed

+65
-5
lines changed

4 files changed

+65
-5
lines changed

py/obj.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "misc.h"
3535
#include "qstr.h"
3636
#include "obj.h"
37+
#include "objtype.h"
3738
#include "mpz.h"
3839
#include "objint.h"
3940
#include "runtime0.h"
@@ -145,7 +146,11 @@ bool mp_obj_is_true(mp_obj_t arg) {
145146
}
146147

147148
bool mp_obj_is_callable(mp_obj_t o_in) {
148-
return mp_obj_get_type(o_in)->call != NULL;
149+
mp_call_fun_t call = mp_obj_get_type(o_in)->call;
150+
if (call != mp_obj_instance_call) {
151+
return call != NULL;
152+
}
153+
return mp_obj_instance_is_callable(o_in);
149154
}
150155

151156
mp_int_t mp_obj_hash(mp_obj_t o_in) {

py/objtype.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ STATIC mp_obj_t instance_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value
583583
}
584584
}
585585

586-
STATIC mp_obj_t instance_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
586+
bool mp_obj_instance_is_callable(mp_obj_t self_in) {
587587
mp_obj_instance_t *self = self_in;
588588
mp_obj_t member[2] = {MP_OBJ_NULL};
589589
struct class_lookup_data lookup = {
@@ -593,6 +593,19 @@ STATIC mp_obj_t instance_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw
593593
.dest = member,
594594
};
595595
mp_obj_class_lookup(&lookup, self->base.type);
596+
return member[0] != MP_OBJ_NULL;
597+
}
598+
599+
mp_obj_t mp_obj_instance_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
600+
mp_obj_instance_t *self = self_in;
601+
mp_obj_t member[2] = {MP_OBJ_NULL, MP_OBJ_NULL};
602+
struct class_lookup_data lookup = {
603+
.obj = self,
604+
.attr = MP_QSTR___call__,
605+
.meth_offset = offsetof(mp_obj_type_t, call),
606+
.dest = member,
607+
};
608+
mp_obj_class_lookup(&lookup, self->base.type);
596609
if (member[0] == MP_OBJ_NULL) {
597610
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "'%s' object is not callable", mp_obj_get_type_str(self_in)));
598611
}
@@ -777,7 +790,7 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict)
777790
o->load_attr = instance_load_attr;
778791
o->store_attr = instance_store_attr;
779792
o->subscr = instance_subscr;
780-
o->call = instance_call;
793+
o->call = mp_obj_instance_call;
781794
o->getiter = instance_getiter;
782795
o->bases_tuple = bases_tuple;
783796
o->locals_dict = locals_dict;

py/objtype.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,7 @@ typedef struct _mp_obj_instance_t {
3232
mp_obj_t subobj[];
3333
// TODO maybe cache __getattr__ and __setattr__ for efficient lookup of them
3434
} mp_obj_instance_t;
35+
36+
// these need to be exposed so mp_obj_is_callable can work correctly
37+
bool mp_obj_instance_is_callable(mp_obj_t self_in);
38+
mp_obj_t mp_obj_instance_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args);

tests/basics/builtin_callable.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
1-
import sys
1+
# test builtin callable
2+
3+
# primitives should not be callable
4+
print(callable(None))
25
print(callable(1))
6+
print(callable([]))
37
print(callable("dfsd"))
4-
print(callable(callable))
8+
9+
# modules should not be callabe
10+
import sys
511
print(callable(sys))
12+
13+
# builtins should be callable
14+
print(callable(callable))
15+
16+
# lambdas should be callable
17+
print(callable(lambda:None))
18+
19+
# user defined functions should be callable
20+
def f():
21+
pass
22+
print(callable(f))
23+
24+
# types should be callable, but not instances
25+
class A:
26+
pass
27+
print(callable(A))
28+
print(callable(A()))
29+
30+
# instances with __call__ method should be callable
31+
class B:
32+
def __call__(self):
33+
pass
34+
print(callable(B()))
35+
36+
# this checks internal use of callable when extracting members from an instance
37+
class C:
38+
def f(self):
39+
return "A.f"
40+
class D:
41+
g = C() # g is a value and is not callable
42+
print(callable(D().g))
43+
print(D().g.f())

0 commit comments

Comments
 (0)