Commit abf3dcec authored by Kirill Smelkov's avatar Kirill Smelkov

X golang_str: Fix bstr/ustr __add__ and friends to return NotImplemented wrt unsupported types

In bbbb58f0 (golang_str: bstr/ustr support for + and *) I've added
support for binary string operations, but similarly to __eq__ did not
handle correctly the case for arbitrary arguments that potentially
define __radd__ and similar.

As the result it breaks when running e.g. bstr + pyparsing.Regex

      File ".../pyparsing-2.4.7-py2.7.egg/pyparsing.py", line 6591, in pyparsing_common
        _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part) * 7).setName("full IPv6 address")
      File "golang/_golang_str.pyx", line 469, in golang._golang._pybstr.__add__
        return pyb(zbytes.__add__(a, _pyb_coerce(b)))
      File "golang/_golang_str.pyx", line 243, in golang._golang._pyb_coerce
        raise TypeError("b: coerce: invalid type %s" % type(x))
    TypeError: b: coerce: invalid type <class 'pyparsing.Regex'>

because pyparsing.Regex is a type, that does not inherit from str, but defines
its own __radd__ to handle str + Regex as Regex.

-> Fix it by returning NotImplemented from under __add__ and other operations
where it is needed so that bstr and ustr behave in the same way as builtin str
wrt third types, but care to handle bstr/ustr promise that

    only explicit conversion through `b` and `u` accept objects with buffer interface. Automatic coercion does not.
parent a69d44dd
Pipeline #34470 running with stage
in 0 seconds
......@@ -466,7 +466,13 @@ cdef class _pybstr(bytes): # https://github.com/cython/cython/issues/711
if type(a) is not pybstr:
assert type(b) is pybstr
return b.__radd__(a)
return pyb(zbytes.__add__(a, _pyb_coerce(b)))
try:
b = _pyb_coerce(b)
except TypeError:
if not hasattr(b, '__radd__'):
raise # don't let python to handle e.g. bstr + memoryview automatically
return NotImplemented
return pyb(zbytes.__add__(a, b))
def __radd__(b, a):
# a.__add__(b) returned NotImplementedError, e.g. for unicode.__add__(bstr)
......@@ -484,7 +490,11 @@ cdef class _pybstr(bytes): # https://github.com/cython/cython/issues/711
if type(a) is not pybstr:
assert type(b) is pybstr
return b.__rmul__(a)
return pyb(zbytes.__mul__(a, b))
try:
_ = zbytes.__mul__(a, b)
except TypeError: # TypeError: `b` cannot be interpreted as an integer
return NotImplemented
return pyb(_)
def __rmul__(b, a):
return b.__mul__(a)
......@@ -821,7 +831,13 @@ cdef class _pyustr(unicode):
if type(a) is not pyustr:
assert type(b) is pyustr, type(b)
return b.__radd__(a)
return pyu(zunicode.__add__(a, _pyu_coerce(b)))
try:
b = _pyu_coerce(b)
except TypeError:
if not hasattr(b, '__radd__'):
raise # don't let py2 to handle e.g. unicode + buffer automatically
return NotImplemented
return pyu(zunicode.__add__(a, b))
def __radd__(b, a):
# a.__add__(b) returned NotImplementedError, e.g. for unicode.__add__(bstr)
......@@ -841,7 +857,11 @@ cdef class _pyustr(unicode):
if type(a) is not pyustr:
assert type(b) is pyustr, type(b)
return b.__rmul__(a)
return pyu(zunicode.__mul__(a, b))
try:
_ = zunicode.__mul__(a, b)
except TypeError: # TypeError: `b` cannot be interpreted as an integer
return NotImplemented
return pyu(_)
def __rmul__(b, a):
return b.__mul__(a)
......
......@@ -982,6 +982,57 @@ def test_strings_ops2_bufreject(tx, ty):
y < x
# verify string operations like `x + y` for x being str/bstr/ustr and y being
# arbitrary type that defines __rop__.
@mark.parametrize('tx', (str, bstr, ustr))
def test_strings_ops2_rop_any(tx):
# ROp(rop, x, y) represents call to y.__rop__(x)
class ROp:
def __init__(r, rop, x, y):
r.rop, r.x, r.y = rop, x, y
def __repr__(r):
return 'ROp(%r, %r, %r)' % (r.rop, r.x, r.y)
def __eq__(a, b):
return isinstance(b, ROp) and a.rop == b.rop and a.x is b.x and a.y is b.y
def __ne__(a, b):
return not (a == b)
class C:
def __radd__(b, a): return ROp('radd', a, b)
def __rsub__(b, a): return ROp('rsub', a, b)
def __rmul__(b, a): return ROp('rmul', a, b)
def __rdiv__(b, a): return ROp('rdiv', a, b)
def __rtruediv__(b, a): return ROp('rtruediv', a, b)
def __rfloordiv__(b, a): return ROp('rfloordiv', a, b)
def __rmod__(b, a): return ROp('rmod', a, b)
def __rdivmod__(b, a): return ROp('rdivmod', a, b)
def __rpow__(b, a): return ROp('rpow', a, b)
def __rlshift__(b, a): return ROp('rlshift', a, b)
def __rrshift__(b, a): return ROp('rrshift', a, b)
def __rand__(b, a): return ROp('rand', a, b)
def __rxor__(b, a): return ROp('rxor', a, b)
def __ror__(b, a): return ROp('ror', a, b)
x = xstr(u'мир', tx)
y = C()
R = lambda rop: ROp(rop, x, y)
assert x + y == R('radd')
assert x - y == R('rsub')
assert x * y == R('rmul')
assert x / y == R(x32('rtruediv', 'rdiv'))
assert x // y == R('rfloordiv')
# x % y is always handled by str and verified in test_strings_mod_and_format
assert divmod(x,y) == R('rdivmod')
assert x ** y == R('rpow')
assert x << y == R('rlshift')
assert x >> y == R('rrshift')
assert x & y == R('rand')
assert x ^ y == R('rxor')
assert x | y == R('ror')
# verify string operations like `x == *` for x being bstr/ustr.
# Those operations must succeed for any hashable type or else bstr/ustr could
# not be used as dict keys.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment