diff --git a/py/mpconfig.h b/py/mpconfig.h index f2a8c98cbb..b8a96f0b0a 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -787,6 +787,14 @@ typedef double mp_float_t; #define MICROPY_PY_BUILTINS_RANGE_ATTRS (1) #endif +// Whether to support binary ops [only (in)equality is defined] between range +// objects. With this option disabled all range objects that are not exactly +// the same object will compare as not-equal. With it enabled the semantics +// match CPython and ranges are equal if they yield the same sequence of items. +#ifndef MICROPY_PY_BUILTINS_RANGE_BINOP +#define MICROPY_PY_BUILTINS_RANGE_BINOP (0) +#endif + // Whether to support timeout exceptions (like socket.timeout) #ifndef MICROPY_PY_BUILTINS_TIMEOUTERROR #define MICROPY_PY_BUILTINS_TIMEOUTERROR (0) diff --git a/py/objrange.c b/py/objrange.c index 3874adb11c..86aa0ccfe6 100644 --- a/py/objrange.c +++ b/py/objrange.c @@ -138,6 +138,24 @@ STATIC mp_obj_t range_unary_op(mp_unary_op_t op, mp_obj_t self_in) { } } +#if MICROPY_PY_BUILTINS_RANGE_BINOP +STATIC mp_obj_t range_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { + if (!MP_OBJ_IS_TYPE(rhs_in, &mp_type_range) || op != MP_BINARY_OP_EQUAL) { + return MP_OBJ_NULL; // op not supported + } + mp_obj_range_t *lhs = MP_OBJ_TO_PTR(lhs_in); + mp_obj_range_t *rhs = MP_OBJ_TO_PTR(rhs_in); + mp_int_t lhs_len = range_len(lhs); + mp_int_t rhs_len = range_len(rhs); + return mp_obj_new_bool( + lhs_len == rhs_len + && (lhs_len == 0 + || (lhs->start == rhs->start + && (lhs_len == 1 || lhs->step == rhs->step))) + ); +} +#endif + STATIC mp_obj_t range_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { if (value == MP_OBJ_SENTINEL) { // load @@ -195,6 +213,9 @@ const mp_obj_type_t mp_type_range = { .print = range_print, .make_new = range_make_new, .unary_op = range_unary_op, + #if MICROPY_PY_BUILTINS_RANGE_BINOP + .binary_op = range_binary_op, + #endif .subscr = range_subscr, .getiter = range_getiter, #if MICROPY_PY_BUILTINS_RANGE_ATTRS diff --git a/tests/basics/builtin_range_binop.py b/tests/basics/builtin_range_binop.py new file mode 100644 index 0000000000..e4e054c276 --- /dev/null +++ b/tests/basics/builtin_range_binop.py @@ -0,0 +1,32 @@ +# test binary operations on range objects; (in)equality only + +# this "feature test" actually tests the implementation but is the best we can do +if range(1) != range(1): + print("SKIP") + raise SystemExit + +# basic (in)equality +print(range(1) == range(1)) +print(range(1) != range(1)) +print(range(1) != range(2)) + +# empty range +print(range(0) == range(0)) +print(range(1, 0) == range(0)) +print(range(1, 4, -1) == range(6, 3)) + +# 1 element range +print(range(1, 4, 10) == range(1, 4, 10)) +print(range(1, 4, 10) == range(1, 4, 20)) +print(range(1, 4, 10) == range(1, 8, 20)) + +# more than 1 element +print(range(0, 3, 2) == range(0, 3, 2)) +print(range(0, 3, 2) == range(0, 4, 2)) +print(range(0, 3, 2) == range(0, 5, 2)) + +# unsupported binary op +try: + range(1) + 10 +except TypeError: + print('TypeError')