From 0344fa1ddfbe8674061fed8e904468b9bd2aa550 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 3 Nov 2014 16:09:39 +0000 Subject: [PATCH] py: Fix builtin callable so it checks user-defined instances correctly. Addresses issue #953. --- py/obj.c | 7 +++++- py/objtype.c | 17 +++++++++++-- py/objtype.h | 4 +++ tests/basics/builtin_callable.py | 42 ++++++++++++++++++++++++++++++-- 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/py/obj.c b/py/obj.c index 0b57f24c5c..02f75c7c40 100644 --- a/py/obj.c +++ b/py/obj.c @@ -34,6 +34,7 @@ #include "misc.h" #include "qstr.h" #include "obj.h" +#include "objtype.h" #include "mpz.h" #include "objint.h" #include "runtime0.h" @@ -145,7 +146,11 @@ bool mp_obj_is_true(mp_obj_t arg) { } bool mp_obj_is_callable(mp_obj_t o_in) { - return mp_obj_get_type(o_in)->call != NULL; + mp_call_fun_t call = mp_obj_get_type(o_in)->call; + if (call != mp_obj_instance_call) { + return call != NULL; + } + return mp_obj_instance_is_callable(o_in); } mp_int_t mp_obj_hash(mp_obj_t o_in) { diff --git a/py/objtype.c b/py/objtype.c index cb6b957336..b0c6a629d0 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -583,7 +583,7 @@ STATIC mp_obj_t instance_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value } } -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) { +bool mp_obj_instance_is_callable(mp_obj_t self_in) { mp_obj_instance_t *self = self_in; mp_obj_t member[2] = {MP_OBJ_NULL}; 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 .dest = member, }; mp_obj_class_lookup(&lookup, self->base.type); + return member[0] != MP_OBJ_NULL; +} + +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) { + mp_obj_instance_t *self = self_in; + mp_obj_t member[2] = {MP_OBJ_NULL, MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = self, + .attr = MP_QSTR___call__, + .meth_offset = offsetof(mp_obj_type_t, call), + .dest = member, + }; + mp_obj_class_lookup(&lookup, self->base.type); if (member[0] == MP_OBJ_NULL) { nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "'%s' object is not callable", mp_obj_get_type_str(self_in))); } @@ -777,7 +790,7 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) o->load_attr = instance_load_attr; o->store_attr = instance_store_attr; o->subscr = instance_subscr; - o->call = instance_call; + o->call = mp_obj_instance_call; o->getiter = instance_getiter; o->bases_tuple = bases_tuple; o->locals_dict = locals_dict; diff --git a/py/objtype.h b/py/objtype.h index 6a8a18c50a..dd2e44a799 100644 --- a/py/objtype.h +++ b/py/objtype.h @@ -32,3 +32,7 @@ typedef struct _mp_obj_instance_t { mp_obj_t subobj[]; // TODO maybe cache __getattr__ and __setattr__ for efficient lookup of them } mp_obj_instance_t; + +// these need to be exposed so mp_obj_is_callable can work correctly +bool mp_obj_instance_is_callable(mp_obj_t self_in); +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); diff --git a/tests/basics/builtin_callable.py b/tests/basics/builtin_callable.py index caddb885cf..3ae49f004d 100644 --- a/tests/basics/builtin_callable.py +++ b/tests/basics/builtin_callable.py @@ -1,5 +1,43 @@ -import sys +# test builtin callable + +# primitives should not be callable +print(callable(None)) print(callable(1)) +print(callable([])) print(callable("dfsd")) -print(callable(callable)) + +# modules should not be callabe +import sys print(callable(sys)) + +# builtins should be callable +print(callable(callable)) + +# lambdas should be callable +print(callable(lambda:None)) + +# user defined functions should be callable +def f(): + pass +print(callable(f)) + +# types should be callable, but not instances +class A: + pass +print(callable(A)) +print(callable(A())) + +# instances with __call__ method should be callable +class B: + def __call__(self): + pass +print(callable(B())) + +# this checks internal use of callable when extracting members from an instance +class C: + def f(self): + return "A.f" +class D: + g = C() # g is a value and is not callable +print(callable(D().g)) +print(D().g.f())