py/runtime: Add MP_BINARY_OP_CONTAINS as reverse of MP_BINARY_OP_IN.
Before this patch MP_BINARY_OP_IN had two meanings: coming from bytecode it meant that the args needed to be swapped, but coming from within the runtime meant that the args were already in the correct order. This lead to some confusion in the code and comments stating how args were reversed. It also lead to 2 bugs: 1) containment for a subclass of a native type didn't work; 2) the expression "{True} in True" would illegally succeed and return True. In both of these cases it was because the args to MP_BINARY_OP_IN ended up being reversed twice. To fix these things this patch introduces MP_BINARY_OP_CONTAINS which corresponds exactly to the __contains__ special method, and this is the operator that built-in types should implement. MP_BINARY_OP_IN is now only emitted by the compiler and is converted to MP_BINARY_OP_CONTAINS by swapping the arguments.
This commit is contained in:
parent
5b2f62aff3
commit
5e34a113ea
@ -282,7 +282,7 @@ STATIC mp_obj_t btree_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) {
|
|||||||
STATIC mp_obj_t btree_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
|
STATIC mp_obj_t btree_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
|
||||||
mp_obj_btree_t *self = MP_OBJ_TO_PTR(lhs_in);
|
mp_obj_btree_t *self = MP_OBJ_TO_PTR(lhs_in);
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case MP_BINARY_OP_IN: {
|
case MP_BINARY_OP_CONTAINS: {
|
||||||
DBT key, val;
|
DBT key, val;
|
||||||
key.data = (void*)mp_obj_str_get_data(rhs_in, &key.size);
|
key.data = (void*)mp_obj_str_get_data(rhs_in, &key.size);
|
||||||
int res = __bt_get(self->db, &key, &val, 0);
|
int res = __bt_get(self->db, &key, &val, 0);
|
||||||
|
@ -269,8 +269,7 @@ STATIC mp_obj_t array_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs
|
|||||||
return lhs_in;
|
return lhs_in;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MP_BINARY_OP_IN: {
|
case MP_BINARY_OP_CONTAINS: {
|
||||||
/* NOTE `a in b` is `b.__contains__(a)` */
|
|
||||||
mp_buffer_info_t lhs_bufinfo;
|
mp_buffer_info_t lhs_bufinfo;
|
||||||
mp_buffer_info_t rhs_bufinfo;
|
mp_buffer_info_t rhs_bufinfo;
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ STATIC mp_obj_t dict_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
|
|||||||
STATIC mp_obj_t dict_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
|
STATIC mp_obj_t dict_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
|
||||||
mp_obj_dict_t *o = MP_OBJ_TO_PTR(lhs_in);
|
mp_obj_dict_t *o = MP_OBJ_TO_PTR(lhs_in);
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case MP_BINARY_OP_IN: {
|
case MP_BINARY_OP_CONTAINS: {
|
||||||
mp_map_elem_t *elem = mp_map_lookup(&o->map, rhs_in, MP_MAP_LOOKUP);
|
mp_map_elem_t *elem = mp_map_lookup(&o->map, rhs_in, MP_MAP_LOOKUP);
|
||||||
return mp_obj_new_bool(elem != NULL);
|
return mp_obj_new_bool(elem != NULL);
|
||||||
}
|
}
|
||||||
@ -485,7 +485,7 @@ STATIC mp_obj_t dict_view_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t
|
|||||||
if (o->kind != MP_DICT_VIEW_KEYS) {
|
if (o->kind != MP_DICT_VIEW_KEYS) {
|
||||||
return MP_OBJ_NULL; // op not supported
|
return MP_OBJ_NULL; // op not supported
|
||||||
}
|
}
|
||||||
if (op != MP_BINARY_OP_IN) {
|
if (op != MP_BINARY_OP_CONTAINS) {
|
||||||
return MP_OBJ_NULL; // op not supported
|
return MP_OBJ_NULL; // op not supported
|
||||||
}
|
}
|
||||||
return dict_binary_op(op, o->dict, rhs_in);
|
return dict_binary_op(op, o->dict, rhs_in);
|
||||||
|
@ -207,7 +207,7 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i
|
|||||||
return mp_obj_new_float(flhs / frhs);
|
return mp_obj_new_float(flhs / frhs);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} else if (op >= MP_BINARY_OP_INPLACE_OR) {
|
} else if (op >= MP_BINARY_OP_INPLACE_OR && op < MP_BINARY_OP_CONTAINS) {
|
||||||
mp_obj_int_t *res = mp_obj_int_new_mpz();
|
mp_obj_int_t *res = mp_obj_int_new_mpz();
|
||||||
|
|
||||||
switch (op) {
|
switch (op) {
|
||||||
|
@ -461,7 +461,7 @@ STATIC mp_obj_t set_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) {
|
|||||||
#else
|
#else
|
||||||
bool update = true;
|
bool update = true;
|
||||||
#endif
|
#endif
|
||||||
if (op != MP_BINARY_OP_IN && !is_set_or_frozenset(rhs)) {
|
if (op != MP_BINARY_OP_CONTAINS && !is_set_or_frozenset(rhs)) {
|
||||||
// For all ops except containment the RHS must be a set/frozenset
|
// For all ops except containment the RHS must be a set/frozenset
|
||||||
return MP_OBJ_NULL;
|
return MP_OBJ_NULL;
|
||||||
}
|
}
|
||||||
@ -507,7 +507,7 @@ STATIC mp_obj_t set_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) {
|
|||||||
return set_issubset(lhs, rhs);
|
return set_issubset(lhs, rhs);
|
||||||
case MP_BINARY_OP_MORE_EQUAL:
|
case MP_BINARY_OP_MORE_EQUAL:
|
||||||
return set_issuperset(lhs, rhs);
|
return set_issuperset(lhs, rhs);
|
||||||
case MP_BINARY_OP_IN: {
|
case MP_BINARY_OP_CONTAINS: {
|
||||||
mp_obj_set_t *o = MP_OBJ_TO_PTR(lhs);
|
mp_obj_set_t *o = MP_OBJ_TO_PTR(lhs);
|
||||||
mp_obj_t elem = mp_set_lookup(&o->set, rhs, MP_MAP_LOOKUP);
|
mp_obj_t elem = mp_set_lookup(&o->set, rhs, MP_MAP_LOOKUP);
|
||||||
return mp_obj_new_bool(elem != MP_OBJ_NULL);
|
return mp_obj_new_bool(elem != MP_OBJ_NULL);
|
||||||
|
@ -384,8 +384,7 @@ mp_obj_t mp_obj_str_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i
|
|||||||
return mp_obj_new_str_from_vstr(lhs_type, &vstr);
|
return mp_obj_new_str_from_vstr(lhs_type, &vstr);
|
||||||
}
|
}
|
||||||
|
|
||||||
case MP_BINARY_OP_IN:
|
case MP_BINARY_OP_CONTAINS:
|
||||||
/* NOTE `a in b` is `b.__contains__(a)` */
|
|
||||||
return mp_obj_new_bool(find_subbytes(lhs_data, lhs_len, rhs_data, rhs_len, 1) != NULL);
|
return mp_obj_new_bool(find_subbytes(lhs_data, lhs_len, rhs_data, rhs_len, 1) != NULL);
|
||||||
|
|
||||||
//case MP_BINARY_OP_NOT_EQUAL: // This is never passed here
|
//case MP_BINARY_OP_NOT_EQUAL: // This is never passed here
|
||||||
|
@ -424,7 +424,7 @@ const byte mp_binary_op_method_name[MP_BINARY_OP_NUM_RUNTIME] = {
|
|||||||
[MP_BINARY_OP_LESS_EQUAL] = MP_QSTR___le__,
|
[MP_BINARY_OP_LESS_EQUAL] = MP_QSTR___le__,
|
||||||
[MP_BINARY_OP_MORE_EQUAL] = MP_QSTR___ge__,
|
[MP_BINARY_OP_MORE_EQUAL] = MP_QSTR___ge__,
|
||||||
// MP_BINARY_OP_NOT_EQUAL, // a != b calls a == b and inverts result
|
// MP_BINARY_OP_NOT_EQUAL, // a != b calls a == b and inverts result
|
||||||
[MP_BINARY_OP_IN] = MP_QSTR___contains__,
|
[MP_BINARY_OP_CONTAINS] = MP_QSTR___contains__,
|
||||||
|
|
||||||
// All inplace methods are optional, and normal methods will be used
|
// All inplace methods are optional, and normal methods will be used
|
||||||
// as a fallback.
|
// as a fallback.
|
||||||
|
@ -47,6 +47,6 @@ MP_DEFINE_CONST_FUN_OBJ_2(mp_op_delitem_obj, op_delitem);
|
|||||||
|
|
||||||
STATIC mp_obj_t op_contains(mp_obj_t lhs_in, mp_obj_t rhs_in) {
|
STATIC mp_obj_t op_contains(mp_obj_t lhs_in, mp_obj_t rhs_in) {
|
||||||
mp_obj_type_t *type = mp_obj_get_type(lhs_in);
|
mp_obj_type_t *type = mp_obj_get_type(lhs_in);
|
||||||
return type->binary_op(MP_BINARY_OP_IN, lhs_in, rhs_in);
|
return type->binary_op(MP_BINARY_OP_CONTAINS, lhs_in, rhs_in);
|
||||||
}
|
}
|
||||||
MP_DEFINE_CONST_FUN_OBJ_2(mp_op_contains_obj, op_contains);
|
MP_DEFINE_CONST_FUN_OBJ_2(mp_op_contains_obj, op_contains);
|
||||||
|
42
py/runtime.c
42
py/runtime.c
@ -523,30 +523,12 @@ mp_obj_t mp_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* deal with `in`
|
// Convert MP_BINARY_OP_IN to MP_BINARY_OP_CONTAINS with swapped args.
|
||||||
*
|
|
||||||
* NOTE `a in b` is `b.__contains__(a)`, hence why the generic dispatch
|
|
||||||
* needs to go below with swapped arguments
|
|
||||||
*/
|
|
||||||
if (op == MP_BINARY_OP_IN) {
|
if (op == MP_BINARY_OP_IN) {
|
||||||
mp_obj_type_t *type = mp_obj_get_type(rhs);
|
op = MP_BINARY_OP_CONTAINS;
|
||||||
if (type->binary_op != NULL) {
|
mp_obj_t temp = lhs;
|
||||||
mp_obj_t res = type->binary_op(op, rhs, lhs);
|
lhs = rhs;
|
||||||
if (res != MP_OBJ_NULL) {
|
rhs = temp;
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// final attempt, walk the iterator (will raise if rhs is not iterable)
|
|
||||||
mp_obj_iter_buf_t iter_buf;
|
|
||||||
mp_obj_t iter = mp_getiter(rhs, &iter_buf);
|
|
||||||
mp_obj_t next;
|
|
||||||
while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) {
|
|
||||||
if (mp_obj_equal(next, lhs)) {
|
|
||||||
return mp_const_true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mp_const_false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// generic binary_op supplied by type
|
// generic binary_op supplied by type
|
||||||
@ -575,6 +557,20 @@ generic_binary_op:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (op == MP_BINARY_OP_CONTAINS) {
|
||||||
|
// If type didn't support containment then explicitly walk the iterator.
|
||||||
|
// mp_getiter will raise the appropriate exception if lhs is not iterable.
|
||||||
|
mp_obj_iter_buf_t iter_buf;
|
||||||
|
mp_obj_t iter = mp_getiter(lhs, &iter_buf);
|
||||||
|
mp_obj_t next;
|
||||||
|
while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) {
|
||||||
|
if (mp_obj_equal(next, rhs)) {
|
||||||
|
return mp_const_true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mp_const_false;
|
||||||
|
}
|
||||||
|
|
||||||
unsupported_op:
|
unsupported_op:
|
||||||
if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) {
|
if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) {
|
||||||
mp_raise_TypeError("unsupported type for operator");
|
mp_raise_TypeError("unsupported type for operator");
|
||||||
|
@ -131,6 +131,10 @@ typedef enum {
|
|||||||
#endif
|
#endif
|
||||||
,
|
,
|
||||||
|
|
||||||
|
// The runtime will convert MP_BINARY_OP_IN to this operator with swapped args.
|
||||||
|
// A type should implement this containment operator instead of MP_BINARY_OP_IN.
|
||||||
|
MP_BINARY_OP_CONTAINS,
|
||||||
|
|
||||||
MP_BINARY_OP_NUM_RUNTIME,
|
MP_BINARY_OP_NUM_RUNTIME,
|
||||||
|
|
||||||
// These 2 are not supported by the runtime and must be synthesised by the emitter
|
// These 2 are not supported by the runtime and must be synthesised by the emitter
|
||||||
|
@ -18,7 +18,7 @@ ViperTypeError('must raise an object',)
|
|||||||
ViperTypeError('unary op __pos__ not implemented',)
|
ViperTypeError('unary op __pos__ not implemented',)
|
||||||
ViperTypeError('unary op __neg__ not implemented',)
|
ViperTypeError('unary op __neg__ not implemented',)
|
||||||
ViperTypeError('unary op __invert__ not implemented',)
|
ViperTypeError('unary op __invert__ not implemented',)
|
||||||
ViperTypeError('binary op __contains__ not implemented',)
|
ViperTypeError('binary op not implemented',)
|
||||||
NotImplementedError('native yield',)
|
NotImplementedError('native yield',)
|
||||||
NotImplementedError('native yield from',)
|
NotImplementedError('native yield from',)
|
||||||
NotImplementedError('conversion to object',)
|
NotImplementedError('conversion to object',)
|
||||||
|
Loading…
Reference in New Issue
Block a user