Skip to content

Commit 784909c

Browse files
committed
py/objtype: Handle NotImplemented return from binary special methods.
NotImplemented means "try other fallbacks (like calling __rop__ instead of __op__) and if nothing works, raise TypeError". As MicroPython doesn't implement any fallbacks, signal to raise TypeError right away.
1 parent 8388ec4 commit 784909c

3 files changed

Lines changed: 67 additions & 2 deletions

File tree

py/obj.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,7 @@ extern const mp_obj_type_t mp_type_ZeroDivisionError;
616616
#define mp_const_true (MP_OBJ_FROM_PTR(&mp_const_true_obj))
617617
#define mp_const_empty_bytes (MP_OBJ_FROM_PTR(&mp_const_empty_bytes_obj))
618618
#define mp_const_empty_tuple (MP_OBJ_FROM_PTR(&mp_const_empty_tuple_obj))
619+
#define mp_const_notimplemented (MP_OBJ_FROM_PTR(&mp_const_notimplemented_obj))
619620
extern const struct _mp_obj_none_t mp_const_none_obj;
620621
extern const struct _mp_obj_bool_t mp_const_false_obj;
621622
extern const struct _mp_obj_bool_t mp_const_true_obj;

py/objtype.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -471,14 +471,28 @@ STATIC mp_obj_t instance_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t
471471
.is_type = false,
472472
};
473473
mp_obj_class_lookup(&lookup, lhs->base.type);
474+
475+
mp_obj_t res;
474476
if (dest[0] == MP_OBJ_SENTINEL) {
475-
return mp_binary_op(op, lhs->subobj[0], rhs_in);
477+
res = mp_binary_op(op, lhs->subobj[0], rhs_in);
476478
} else if (dest[0] != MP_OBJ_NULL) {
477479
dest[2] = rhs_in;
478-
return mp_call_method_n_kw(1, 0, dest);
480+
res = mp_call_method_n_kw(1, 0, dest);
479481
} else {
480482
return MP_OBJ_NULL; // op not supported
481483
}
484+
485+
#if MICROPY_PY_BUILTINS_NOTIMPLEMENTED
486+
// NotImplemented means "try other fallbacks (like calling __rop__
487+
// instead of __op__) and if nothing works, raise TypeError". As
488+
// MicroPython doesn't implement any fallbacks, signal to raise
489+
// TypeError right away.
490+
if (res == mp_const_notimplemented) {
491+
return MP_OBJ_NULL; // op not supported
492+
}
493+
#endif
494+
495+
return res;
482496
}
483497

484498
STATIC void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {

tests/basics/class_notimpl.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Test that returning of NotImplemented from binary op methods leads to
2+
# TypeError.
3+
try:
4+
NotImplemented
5+
except NameError:
6+
print("SKIP")
7+
raise SystemExit
8+
9+
class C:
10+
def __init__(self, value):
11+
self.value = value
12+
13+
def __str__(self):
14+
return "C(%s)" % self.value
15+
16+
def __add__(self, rhs):
17+
print(self, '+', rhs)
18+
return NotImplemented
19+
20+
def __sub__(self, rhs):
21+
print(self, '-', rhs)
22+
return NotImplemented
23+
24+
def __lt__(self, rhs):
25+
print(self, '<', rhs)
26+
return NotImplemented
27+
28+
def __neg__(self):
29+
print('-', self)
30+
return NotImplemented
31+
32+
c = C(0)
33+
34+
try:
35+
c + 1
36+
except TypeError:
37+
print("TypeError")
38+
39+
try:
40+
c - 2
41+
except TypeError:
42+
print("TypeError")
43+
44+
try:
45+
c < 1
46+
except TypeError:
47+
print("TypeError")
48+
49+
# NotImplemented isn't handled specially in unary methods
50+
print(-c)

0 commit comments

Comments
 (0)