summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--py/objtype.c90
1 files changed, 77 insertions, 13 deletions
diff --git a/py/objtype.c b/py/objtype.c
index f2173c79a..818ceeb05 100644
--- a/py/objtype.c
+++ b/py/objtype.c
@@ -661,8 +661,8 @@ static void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *des
// try __getattr__
if (attr != MP_QSTR___getattr__) {
#if MICROPY_PY_DESCRIPTORS
- // With descriptors enabled, don't delegate lookups of __get__/__set__/__delete__.
- if (attr == MP_QSTR___get__ || attr == MP_QSTR___set__ || attr == MP_QSTR___delete__) {
+ // With descriptors enabled, don't delegate lookups of __get__/__set__/__delete__/__set_name__.
+ if (attr == MP_QSTR___get__ || attr == MP_QSTR___set__ || attr == MP_QSTR___delete__ || attr == MP_QSTR___set_name__) {
return;
}
#endif
@@ -960,7 +960,7 @@ static bool check_for_special_accessors(mp_obj_t key, mp_obj_t value) {
#endif
#if MICROPY_PY_DESCRIPTORS
static const uint8_t to_check[] = {
- MP_QSTR___get__, MP_QSTR___set__, MP_QSTR___delete__,
+ MP_QSTR___get__, MP_QSTR___set__, MP_QSTR___delete__, // not needed for MP_QSTR___set_name__ though
};
for (size_t i = 0; i < MP_ARRAY_SIZE(to_check); ++i) {
mp_obj_t dest_temp[2];
@@ -974,6 +974,48 @@ static bool check_for_special_accessors(mp_obj_t key, mp_obj_t value) {
}
#endif
+#if MICROPY_PY_DESCRIPTORS
+// Shared data layout for the __set_name__ call and a linked list of calls to be made.
+typedef union _setname_list_t setname_list_t;
+union _setname_list_t {
+ mp_obj_t call[4];
+ struct {
+ mp_obj_t _meth;
+ mp_obj_t _self;
+ setname_list_t *next; // can use the "owner" argument position temporarily for the linked list
+ mp_obj_t _name;
+ };
+};
+
+// Append any `__set_name__` method on `value` to the setname list, with its per-attr args
+static setname_list_t *setname_maybe_bind_append(setname_list_t *tail, mp_obj_t name, mp_obj_t value) {
+ // make certain our type-punning is safe:
+ MP_STATIC_ASSERT_NONCONSTEXPR(offsetof(setname_list_t, next) == offsetof(setname_list_t, call[2]));
+
+ // tail is a blank list entry
+ mp_load_method_maybe(value, MP_QSTR___set_name__, tail->call);
+ if (tail->call[1] != MP_OBJ_NULL) {
+ // Each time a __set_name__ is found, leave it in-place in the former tail and allocate a new tail
+ tail->next = m_new_obj(setname_list_t);
+ tail->next->next = NULL;
+ tail->call[3] = name;
+ return tail->next;
+ } else {
+ return tail;
+ }
+}
+
+// Execute the captured `__set_name__` calls, destroying the setname list in the process.
+static inline void setname_consume_call_all(setname_list_t *head, mp_obj_t owner) {
+ setname_list_t *next;
+ while ((next = head->next) != NULL) {
+ head->call[2] = owner;
+ mp_call_method_n_kw(2, 0, head->call);
+ head = next;
+ }
+}
+#endif
+
static void type_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind;
mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in);
@@ -1210,20 +1252,38 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict)
}
}
+ #if MICROPY_PY_DESCRIPTORS
+ // To avoid any dynamic allocations when no __set_name__ exists,
+ // the head of this list is kept on the stack (marked blank with `next = NULL`).
+ setname_list_t setname_list = { .next = NULL };
+ setname_list_t *setname_tail = &setname_list;
+ #endif
+
#if ENABLE_SPECIAL_ACCESSORS
- // Check if the class has any special accessor methods
- if (!(o->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) {
- for (size_t i = 0; i < locals_ptr->map.alloc; i++) {
- if (mp_map_slot_is_filled(&locals_ptr->map, i)) {
- const mp_map_elem_t *elem = &locals_ptr->map.table[i];
- if (check_for_special_accessors(elem->key, elem->value)) {
- o->flags |= MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS;
- break;
- }
+ // Check if the class has any special accessor methods,
+ // and accumulate bound __set_name__ methods that need to be called
+ for (size_t i = 0; i < locals_ptr->map.alloc; i++) {
+ #if !MICROPY_PY_DESCRIPTORS
+ // __set_name__ needs to scan the entire locals map, can't early-terminate
+ if (o->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS) {
+ break;
+ }
+ #endif
+
+ if (mp_map_slot_is_filled(&locals_ptr->map, i)) {
+ const mp_map_elem_t *elem = &locals_ptr->map.table[i];
+
+ if (!(o->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS) // elidable when the early-termination check is enabled
+ && check_for_special_accessors(elem->key, elem->value)) {
+ o->flags |= MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS;
}
+
+ #if MICROPY_PY_DESCRIPTORS
+ setname_tail = setname_maybe_bind_append(setname_tail, elem->key, elem->value);
+ #endif
}
}
- #endif
+ #endif // ENABLE_SPECIAL_ACCESSORS
const mp_obj_type_t *native_base;
size_t num_native_bases = instance_count_native_bases(o, &native_base);
@@ -1241,6 +1301,10 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict)
}
}
+ #if MICROPY_PY_DESCRIPTORS
+ setname_consume_call_all(&setname_list, MP_OBJ_FROM_PTR(o));
+ #endif
+
return MP_OBJ_FROM_PTR(o);
}