Commit 062478e6 authored by Damien George's avatar Damien George
Browse files

Improved type/class/instance code; mp_obj_type_t now has load_attr, store_attr.

Creating of classes (types) and instances is much more like CPython now.
You can use "type('name', (), {...})" to create classes.
parent d944a66e
......@@ -23,8 +23,8 @@ mp_obj_t mp_builtin___build_class__(int n_args, const mp_obj_t *args) {
// we differ from CPython: we set the new __locals__ object here
mp_map_t *old_locals = rt_locals_get();
mp_map_t *class_locals = mp_map_new(0);
rt_locals_set(class_locals);
mp_obj_t class_locals = mp_obj_new_dict(0);
rt_locals_set(mp_obj_dict_get_map(class_locals));
// call the class code
mp_obj_t cell = rt_call_function_1(args[0], (mp_obj_t)0xdeadbeef);
......@@ -32,7 +32,6 @@ mp_obj_t mp_builtin___build_class__(int n_args, const mp_obj_t *args) {
// restore old __locals__ object
rt_locals_set(old_locals);
/*
// get the class type (meta object) from the base objects
mp_obj_t meta;
if (n_args == 2) {
......@@ -42,21 +41,16 @@ mp_obj_t mp_builtin___build_class__(int n_args, const mp_obj_t *args) {
// use type of first base object
meta = mp_obj_get_type(args[2]);
}
*/
// TODO do proper metaclass resolution for multiple base objects
/*
// create the new class using a call to the meta object
// (arguments must be backwards in the array)
mp_obj_t meta_args[3];
meta_args[2] = args[1]; // class name
meta_args[1] = mp_obj_new_tuple(n_args - 2, args + 2); // tuple of bases
meta_args[0] = class_locals; // dict of members TODO, currently is a map
meta_args[0] = class_locals; // dict of members
mp_obj_t new_class = rt_call_function_n(meta, 3, meta_args);
*/
// create the new class
mp_obj_t new_class = mp_obj_new_class(class_locals);
// store into cell if neede
if (cell != mp_const_none) {
......
......@@ -18,7 +18,7 @@ typedef struct _mp_set_t {
mp_obj_t *table;
} mp_set_t;
typedef enum {
typedef enum _mp_map_lookup_kind_t {
MP_MAP_LOOKUP,
MP_MAP_LOOKUP_ADD_IF_NOT_FOUND,
MP_MAP_LOOKUP_REMOVE_IF_FOUND,
......
......@@ -61,6 +61,8 @@ typedef struct _mp_obj_base_t mp_obj_base_t;
// Need to declare this here so we are not dependent on map.h
struct _mp_map_t;
struct _mp_map_elem_t;
enum _mp_map_lookup_kind_t;
// Type definitions for methods
......@@ -78,6 +80,8 @@ typedef mp_obj_t (*mp_call_n_fun_t)(mp_obj_t fun, int n_args, const mp_obj_t *ar
typedef mp_obj_t (*mp_call_n_kw_fun_t)(mp_obj_t fun, int n_args, int n_kw, const mp_obj_t *args); // args are in reverse order in the array
typedef mp_obj_t (*mp_unary_op_fun_t)(int op, mp_obj_t);
typedef mp_obj_t (*mp_binary_op_fun_t)(int op, mp_obj_t, mp_obj_t);
typedef void (*mp_load_attr_fun_t)(mp_obj_t self_in, qstr attr, mp_obj_t *dest); // for fail, do nothing; for attr, dest[1] = value; for method, dest[0] = self, dest[1] = method
typedef bool (*mp_store_attr_fun_t)(mp_obj_t self_in, qstr attr, mp_obj_t value); // return true if store succeeded
typedef struct _mp_method_t {
const char *name;
......@@ -141,15 +145,14 @@ struct _mp_obj_type_t {
const mp_method_t *methods;
mp_load_attr_fun_t load_attr;
mp_store_attr_fun_t store_attr;
mp_obj_t locals;
/*
What we might need to add here:
dynamic_type instance
compare_op
load_attr module instance class list
load_method instance str gen list user
store_attr module instance class
store_subscr list dict
len str tuple list map
......@@ -160,7 +163,6 @@ struct _mp_obj_type_t {
get_array_n tuple list
unpack seq list tuple
__next__ gen-instance
*/
};
......@@ -178,6 +180,7 @@ extern const mp_obj_t mp_const_stop_iteration; // special object indicating end
// General API for objects
mp_obj_t mp_obj_new_type(qstr name, mp_obj_t local_dict);
mp_obj_t mp_obj_new_none(void);
mp_obj_t mp_obj_new_bool(bool value);
mp_obj_t mp_obj_new_cell(mp_obj_t obj);
......@@ -207,8 +210,6 @@ mp_obj_t mp_obj_new_dict(int n_args);
mp_obj_t mp_obj_new_set(int n_args, mp_obj_t *items);
mp_obj_t mp_obj_new_slice(mp_obj_t start, mp_obj_t stop, mp_obj_t step);
mp_obj_t mp_obj_new_bound_meth(mp_obj_t self, mp_obj_t meth);
mp_obj_t mp_obj_new_class(struct _mp_map_t *class_locals);
mp_obj_t mp_obj_new_instance(mp_obj_t clas);
mp_obj_t mp_obj_new_module(qstr module_name);
mp_obj_t mp_obj_get_type(mp_obj_t o_in);
......@@ -278,6 +279,7 @@ void mp_obj_list_store(mp_obj_t self_in, mp_obj_t index, mp_obj_t value);
extern const mp_obj_type_t dict_type;
uint mp_obj_dict_len(mp_obj_t self_in);
mp_obj_t mp_obj_dict_store(mp_obj_t self_in, mp_obj_t key, mp_obj_t value);
struct _mp_map_t *mp_obj_dict_get_map(mp_obj_t self_in);
// set
extern const mp_obj_type_t set_type;
......@@ -307,15 +309,7 @@ void mp_obj_fun_bc_get(mp_obj_t self_in, int *n_args, uint *n_state, const byte
extern const mp_obj_type_t gen_instance_type;
// class
extern const mp_obj_type_t class_type;
extern const mp_obj_t gen_instance_next_obj;
struct _mp_map_t *mp_obj_class_get_locals(mp_obj_t self_in);
// instance
extern const mp_obj_type_t instance_type;
mp_obj_t mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr);
void mp_obj_instance_load_method(mp_obj_t self_in, qstr attr, mp_obj_t *dest);
void mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value);
struct _mp_map_elem_t *mp_obj_class_lookup(mp_obj_t self_in, qstr attr, enum _mp_map_lookup_kind_t lookup_kind);
// module
extern const mp_obj_type_t module_type;
......
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include "nlr.h"
#include "misc.h"
#include "mpconfig.h"
#include "mpqstr.h"
#include "obj.h"
#include "runtime.h"
#include "map.h"
typedef struct _mp_obj_class_t {
mp_obj_base_t base;
mp_map_t *locals;
} mp_obj_class_t;
// args are in reverse order in the array
mp_obj_t class_call_n(mp_obj_t self_in, int n_args, const mp_obj_t *args) {
// instantiate an instance of a class
mp_obj_class_t *self = self_in;
// make instance
mp_obj_t o = mp_obj_new_instance(self_in);
// look for __init__ function
mp_map_elem_t *init_fn = mp_map_lookup(self->locals, MP_OBJ_NEW_QSTR(MP_QSTR___init__), MP_MAP_LOOKUP);
if (init_fn != NULL) {
// call __init__ function
mp_obj_t init_ret;
if (n_args == 0) {
init_ret = rt_call_function_n(init_fn->value, 1, (mp_obj_t*)&o);
} else {
mp_obj_t *args2 = m_new(mp_obj_t, n_args + 1);
memcpy(args2, args, n_args * sizeof(mp_obj_t));
args2[n_args] = o;
init_ret = rt_call_function_n(init_fn->value, n_args + 1, args2);
m_del(mp_obj_t, args2, n_args + 1);
}
if (init_ret != mp_const_none) {
nlr_jump(mp_obj_new_exception_msg_1_arg(MP_QSTR_TypeError, "__init__() should return None, not '%s'", mp_obj_get_type_str(init_ret)));
}
} else {
// TODO
if (n_args != 0) {
nlr_jump(mp_obj_new_exception_msg_1_arg(MP_QSTR_TypeError, "function takes 0 positional arguments but %d were given", (void*)(machine_int_t)n_args));
}
}
return o;
}
mp_map_t *mp_obj_class_get_locals(mp_obj_t self_in) {
assert(MP_OBJ_IS_TYPE(self_in, &class_type));
mp_obj_class_t *self = self_in;
return self->locals;
}
const mp_obj_type_t class_type = {
{ &mp_const_type },
"class",
.call_n = class_call_n,
};
mp_obj_t mp_obj_new_class(mp_map_t *class_locals) {
mp_obj_class_t *o = m_new_obj(mp_obj_class_t);
o->base.type = &class_type;
o->locals = class_locals;
return o;
}
......@@ -287,3 +287,9 @@ mp_obj_t mp_obj_dict_store(mp_obj_t self_in, mp_obj_t key, mp_obj_t value) {
mp_map_lookup(&self->map, key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value;
return self_in;
}
mp_map_t *mp_obj_dict_get_map(mp_obj_t self_in) {
assert(MP_OBJ_IS_TYPE(self_in, &dict_type));
mp_obj_dict_t *self = self_in;
return &self->map;
}
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include "nlr.h"
#include "misc.h"
#include "mpconfig.h"
#include "mpqstr.h"
#include "obj.h"
#include "runtime.h"
#include "map.h"
typedef struct _mp_obj_instance_t {
mp_obj_base_t base;
mp_obj_base_t *class; // points to a "class" object
mp_map_t *members;
} mp_obj_instance_t;
/*
type needs to be specified dynamically
case O_OBJ:
{
py_map_elem_t *qn = py_qstr_map_lookup(o->u_obj.class->u_class.locals, qstr_from_str_static("__qualname__"), false); assert(qn != NULL);
assert(IS_O(qn->value, O_STR));
return qstr_str(((py_obj_base_t*)qn->value)->u_str);
}
*/
mp_obj_t mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr) {
// logic: look in obj members then class locals (TODO check this against CPython)
mp_obj_instance_t *self = self_in;
mp_map_elem_t *elem = mp_map_lookup(self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
if (elem != NULL) {
// object member, always treated as a value
return elem->value;
}
elem = mp_map_lookup(mp_obj_class_get_locals(self->class), MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
if (elem != NULL) {
if (mp_obj_is_callable(elem->value)) {
// class member is callable so build a bound method
return mp_obj_new_bound_meth(self_in, elem->value);
} else {
// class member is a value, so just return that value
return elem->value;
}
}
nlr_jump(mp_obj_new_exception_msg_2_args(MP_QSTR_AttributeError, "'%s' object has no attribute '%s'", mp_obj_get_type_str(self_in), qstr_str(attr)));
}
void mp_obj_instance_load_method(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
// logic: look in obj members then class locals (TODO check this against CPython)
mp_obj_instance_t *self = self_in;
mp_map_elem_t *elem = mp_map_lookup(self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
if (elem != NULL) {
// object member, always treated as a value
dest[1] = elem->value;
dest[0] = NULL;
return;
}
elem = mp_map_lookup(mp_obj_class_get_locals(self->class), MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
if (elem != NULL) {
if (mp_obj_is_callable(elem->value)) {
// class member is callable so build a bound method
dest[1] = elem->value;
dest[0] = self_in;
return;
} else {
// class member is a value, so just return that value
dest[1] = elem->value;
dest[0] = NULL;
return;
}
}
// no such method, so fall back to load attr
dest[1] = rt_load_attr(self_in, attr);
dest[0] = NULL;
}
void mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
// logic: look in class locals (no add) then obj members (add) (TODO check this against CPython)
mp_obj_instance_t *self = self_in;
mp_map_elem_t *elem = mp_map_lookup(mp_obj_class_get_locals(self->class), MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
if (elem != NULL) {
elem->value = value;
} else {
mp_map_lookup(self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value;
}
}
const mp_obj_type_t instance_type = {
{ &mp_const_type },
"instance",
};
mp_obj_t mp_obj_new_instance(mp_obj_t class) {
mp_obj_instance_t *o = m_new_obj(mp_obj_instance_t);
o->base.type = &instance_type;
o->class = class;
o->members = mp_map_new(0);
return o;
}
......@@ -17,15 +17,32 @@ typedef struct _mp_obj_module_t {
mp_map_t *globals;
} mp_obj_module_t;
void module_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in) {
static void module_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in) {
mp_obj_module_t *self = self_in;
print(env, "<module '%s' from '-unknown-file-'>", qstr_str(self->name));
}
static void module_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
mp_obj_module_t *self = self_in;
mp_map_elem_t *elem = mp_map_lookup(self->globals, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
if (elem != NULL) {
dest[1] = elem->value;
}
}
static bool module_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
mp_obj_module_t *self = self_in;
// TODO CPython allows STORE_ATTR to a module, but is this the correct implementation?
mp_map_lookup(self->globals, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value;
return true;
}
const mp_obj_type_t module_type = {
{ &mp_const_type },
"module",
.print = module_print,
.load_attr = module_load_attr,
.store_attr = module_store_attr,
};
mp_obj_t mp_obj_new_module(qstr module_name) {
......
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include "nlr.h"
#include "misc.h"
#include "mpconfig.h"
#include "mpqstr.h"
#include "obj.h"
#include "map.h"
#include "runtime.h"
/******************************************************************************/
// class object
// creating an instance of a class makes one of these objects
typedef struct _mp_obj_class_t {
mp_obj_base_t base;
mp_map_t members;
} mp_obj_class_t;
static mp_obj_t mp_obj_new_class(mp_obj_t class) {
mp_obj_class_t *o = m_new_obj(mp_obj_class_t);
o->base.type = class;
mp_map_init(&o->members, 0);
return o;
}
static void class_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in) {
print(env, "<%s object at %p>", mp_obj_get_type_str(self_in), self_in);
}
// args are reverse in the array
static mp_obj_t class_make_new(mp_obj_t self_in, int n_args, const mp_obj_t *args) {
assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type));
mp_obj_t o = mp_obj_new_class(self_in);
// look for __init__ function
mp_map_elem_t *init_fn = mp_obj_class_lookup(self_in, MP_QSTR___init__, MP_MAP_LOOKUP);
if (init_fn != NULL) {
// call __init__ function
mp_obj_t init_ret;
if (n_args == 0) {
init_ret = rt_call_function_n(init_fn->value, 1, (mp_obj_t*)&o);
} else {
mp_obj_t *args2 = m_new(mp_obj_t, n_args + 1);
memcpy(args2, args, n_args * sizeof(mp_obj_t));
args2[n_args] = o;
init_ret = rt_call_function_n(init_fn->value, n_args + 1, args2);
m_del(mp_obj_t, args2, n_args + 1);
}
if (init_ret != mp_const_none) {
nlr_jump(mp_obj_new_exception_msg_1_arg(MP_QSTR_TypeError, "__init__() should return None, not '%s'", mp_obj_get_type_str(init_ret)));
}
} else {
// TODO
if (n_args != 0) {
nlr_jump(mp_obj_new_exception_msg_1_arg(MP_QSTR_TypeError, "function takes 0 positional arguments but %d were given", (void*)(machine_int_t)n_args));
}
}
return o;
}
static void class_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
// logic: look in obj members then class locals (TODO check this against CPython)
mp_obj_class_t *self = self_in;
mp_map_elem_t *elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
if (elem != NULL) {
// object member, always treated as a value
dest[1] = elem->value;
return;
}
elem = mp_obj_class_lookup((mp_obj_t)self->base.type, attr, MP_MAP_LOOKUP);
if (elem != NULL) {
if (mp_obj_is_callable(elem->value)) {
// class member is callable so build a bound method
dest[1] = elem->value;
dest[0] = self_in;
return;
} else {
// class member is a value, so just return that value
dest[1] = elem->value;
return;
}
}
}
static bool class_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
// logic: look in class locals (no add) then obj members (add) (TODO check this against CPython)
mp_obj_class_t *self = self_in;
mp_map_elem_t *elem = mp_obj_class_lookup((mp_obj_t)self->base.type, attr, MP_MAP_LOOKUP);
if (elem != NULL) {
elem->value = value;
} else {
mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value;
}
return true;
}
mp_map_elem_t *mp_obj_class_lookup(mp_obj_t self_in, qstr attr, mp_map_lookup_kind_t lookup_kind) {
assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type));
mp_obj_type_t *self = self_in;
if (self->locals == NULL) {
return NULL;
}
assert(MP_OBJ_IS_TYPE(self->locals, &dict_type)); // Micro Python restriction, for now
mp_map_t *locals_map = ((void*)self->locals + sizeof(mp_obj_base_t)); // XXX hack to get map object from dict object
return mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), lookup_kind);
}
/******************************************************************************/
// type object
// - the struct is mp_obj_type_t and is defined in obj.h so const types can be made
// - there is a constant mp_obj_type_t (called mp_const_type) for the 'type' object
// - creating a new class (a new type) creates a new mp_obj_type_t
static void type_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in) {
mp_obj_type_t *self = self_in;
......@@ -24,13 +136,7 @@ static mp_obj_t type_make_new(mp_obj_t type_in, int n_args, const mp_obj_t *args
// args[1] = bases tuple
// args[0] = locals dict
mp_obj_type_t *new_type = m_new0(mp_obj_type_t, 1);
new_type->base.type = &mp_const_type;
new_type->name = qstr_str(mp_obj_get_qstr(args[2]));
return new_type;
//mp_obj_t new_class = mp_obj_new_class(mp_obj_get_qstr(args[2]), args[0]);
//return new_class;
return mp_obj_new_type(mp_obj_get_qstr(args[2]), args[0]);
}
default:
......@@ -38,14 +144,42 @@ static mp_obj_t type_make_new(mp_obj_t type_in, int n_args, const mp_obj_t *args
}
}
// args are in reverse order in the array
static mp_obj_t type_call_n(mp_obj_t self_in, int n_args, const mp_obj_t *args) {
// instantiate an instance of a class
mp_obj_type_t *self = self_in;
if (self->make_new != NULL) {
// TODO we need to init the object if it's an instance of a type
return self->make_new(self, n_args, args);
} else {
if (self->make_new == NULL) {
nlr_jump(mp_obj_new_exception_msg_1_arg(MP_QSTR_TypeError, "cannot create '%s' instances", self->name));
}
// make new instance
mp_obj_t o = self->make_new(self, n_args, args);
// return new instance
return o;
}
// for fail, do nothing; for attr, dest[1] = value; for method, dest[0] = self, dest[1] = method
static void type_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
mp_map_elem_t *elem = mp_obj_class_lookup(self_in, attr, MP_MAP_LOOKUP);
if (elem != NULL) {
dest[1] = elem->value;
return;
}
}
static bool type_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
// TODO CPython allows STORE_ATTR to a class, but is this the correct implementation?
mp_map_elem_t *elem = mp_obj_class_lookup(self_in, attr, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
if (elem != NULL) {
elem->value = value;
return true;
} else {
return false;
}
}
const mp_obj_type_t mp_const_type = {
......@@ -54,4 +188,19 @@ const mp_obj_type_t mp_const_type = {
.print = type_print,
.make_new = type_make_new,
.call_n = type_call_n,
.load_attr = type_load_attr,
.store_attr = type_store_attr,
};
mp_obj_t mp_obj_new_type(qstr name, mp_obj_t local_dict) {
mp_obj_type_t *o = m_new0(mp_obj_type_t, 1);
o->base.type = &mp_const_type;
o->name = qstr_str(name);
o->print = class_print;
o->make_new = class_make_new;
o->load_attr = class_load_attr;
o->store_attr = class_store_attr;
o->locals = local_dict;
assert(MP_OBJ_IS_TYPE(o->locals, &dict_type)); // Micro Python restriction, for now
return o;
}
......@@ -74,7 +74,6 @@ PY_O_BASENAME = \
objbool.o \
objboundmeth.o \
objcell.o \
objclass.o \
objclosure.o \
objcomplex.o \
objdict.o \
......@@ -82,7 +81,6 @@ PY_O_BASENAME = \
objfloat.o \
objfun.o \
objgenerator.o \
objinstance.o \
objint.o \
objlist.o \
objmodule.o \
......
......@@ -188,8 +188,10 @@ void rt_assign_byte_code(int unique_code_id, byte *code, uint len, int n_args, i
DEBUG_printf(" %02x", code[i]);
}
DEBUG_printf("\n");
#if MICROPY_SHOW_BC
extern void mp_show_byte_code(const byte *code, int len);
mp_show_byte_code(code, len);
#endif
#ifdef WRITE_CODE
if (fp_write_code != NULL) {
......@@ -775,85 +777,74 @@ mp_obj_t rt_store_map(mp_obj_t map, mp_obj_t key, mp_obj_t value) {
}
mp_obj_t rt_load_attr(mp_obj_t base, qstr attr) {
DEBUG_OP_printf("load attr %s\n", qstr_str(attr));
if (MP_OBJ_IS_TYPE(base, &class_type)) {
mp_map_elem_t *elem = mp_map_lookup(mp_obj_class_get_locals(base), MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
if (elem == NULL) {
// TODO what about generic method lookup?
goto no_attr;
}
return elem->value;
} else if (MP_OBJ_IS_TYPE(base, &instance_type)) {
return mp_obj_instance_load_attr(base, attr);
} else if (MP_OBJ_IS_TYPE(base, &module_type)) {
DEBUG_OP_printf("lookup module map %p\n", mp_obj_module_get_globals(base));
mp_map_elem_t *elem = mp_map_lookup(mp_obj_module_get_globals(base), MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
if (elem == NULL) {
// TODO what about generic method lookup?
goto no_attr;
}
return elem->value;
} else if (MP_OBJ_IS_OBJ(base)) {
// generic method lookup
mp_obj_base_t *o = base;
const mp_method_t *meth = o->type->methods;
if (meth != NULL) {
for (; meth->name != NULL; meth++) {
if (strcmp(meth->name, qstr_str(attr)) == 0) {
return mp_obj_new_bound_meth(base, (mp_obj_t)meth->fun);
}
}
}
DEBUG_OP_printf("load attr %p.%s\n", base, qstr_str(attr));
// use load_method