Commit d4dcf5dd authored by Kirill Smelkov's avatar Kirill Smelkov

golang_str: Fix bstr/ustr .__str__ to always return bstr/ustr even for subclasses

This behaviour is provided by builtin str and we were not following it:

    $ python3
    Python 3.11.2 (main, Mar 13 2023, 12:18:29) [GCC 12.2.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> class SSS(str): pass
    ...
    >>> z = SSS('abc')
    >>> z
    'abc'
    >>> type(z)
    <class '__main__.SSS'>
    >>> q = str(z)
    >>> q
    'abc'
    >>> type(q)
    <class 'str'>
    >>> r = z.__str__()
    >>> r
    'abc'
    >>> type(r)
    <class 'str'>                       <-- NOTE str, not __main__.SSS

    $ gpython               # with str patched to be ustr
    >>> class SSS(str): pass
    >>> z = SSS('abc')
    >>> z
    'abc'
    >>> type(z)
    <class '__main__.SSS'>
    >>> q = str(z)
    >>> q
    'abc'
    >>> type(q)
    <class 'str'>
    >>> r = z.__str__()
    >>> r
    'abc'
    >>> type(r)
    <class '__main__.SSS'>              <-- NOTE not str

which leads to crash during IPython startup on py3.11:

    $ gpython -m IPython    # with str patched to be ustr
    Traceback (most recent call last):
      File "/home/kirr/src/tools/go/py3.venv/bin/gpython", line 8, in <module>
        sys.exit(main())
                 ^^^^^^
      File "/home/kirr/src/tools/go/pygolang-master/gpython/__init__.py", line 478, in main
        pymain(argv, init)
      File "/home/kirr/src/tools/go/pygolang-master/gpython/__init__.py", line 291, in pymain
        run(mmain)
      File "/home/kirr/src/tools/go/pygolang-master/gpython/__init__.py", line 162, in run
        runpy._run_module_as_main(mod)
      File "<frozen runpy>", line 198, in _run_module_as_main
      File "<frozen runpy>", line 88, in _run_code
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/IPython/__main__.py", line 15, in <module>
        start_ipython()
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/IPython/__init__.py", line 128, in start_ipython
        return launch_new_instance(argv=argv, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/traitlets/config/application.py", line 1042, in launch_instance
        app.initialize(argv)
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/traitlets/config/application.py", line 113, in inner
        return method(app, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/IPython/terminal/ipapp.py", line 279, in initialize
        self.init_shell()
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/IPython/terminal/ipapp.py", line 293, in init_shell
        self.shell = self.interactive_shell_class.instance(parent=self,
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/traitlets/config/configurable.py", line 551, in instance
        inst = cls(*args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/IPython/terminal/interactiveshell.py", line 856, in __init__
        self.init_prompt_toolkit_cli()
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/IPython/terminal/interactiveshell.py", line 648, in init_prompt_toolkit_cli
        **self._extra_prompt_options(),
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/IPython/terminal/interactiveshell.py", line 751, in _extra_prompt_options
        "lexer": IPythonPTLexer(),
                 ^^^^^^^^^^^^^^^^
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/IPython/terminal/ptutils.py", line 177, in __init__
        self.python_lexer = PygmentsLexer(l.Python3Lexer)
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/prompt_toolkit/lexers/pygments.py", line 198, in __init__
        self.pygments_lexer = pygments_lexer_cls(
                              ^^^^^^^^^^^^^^^^^^^
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/pygments/lexer.py", line 647, in __call__
        cls._tokens = cls.process_tokendef('', cls.get_tokendefs())
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/pygments/lexer.py", line 586, in process_tokendef
        cls._process_state(tokendefs, processed, state)
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/pygments/lexer.py", line 549, in _process_state
        tokens.extend(cls._process_state(unprocessed, processed,
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/kirr/src/tools/go/py3.venv/lib/python3.11/site-packages/pygments/lexer.py", line 533, in _process_state
        assert type(state) is str, "wrong state name %r (%r)" % (state, type(state))
               ^^^^^^^^^^^^^^^^^^
    AssertionError: wrong state name 'keywords' (<class 'pygments.lexer.include'>)

    If you suspect this is an IPython 8.12.0 bug, please report it at:
        https://github.com/ipython/ipython/issues
    or send an email to the mailing list at ipython-dev@python.org

    You can print a more detailed traceback right now with "%tb", or use "%debug"
    to interactively debug it.

    Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
        c.Application.verbose_crash=True

Here pygments define

    class include(str):
        pass

and wants `str(obj)` to return str, not include if obj was instance of include.

-> Adjust bstr/ustr .__str__() to always return bstr/ustr even for
subclassed.

For consistency, do the same for .__unicode__ . In case a
subclass wants its __str__, or __unicode__ to return self
without casting to bstr/ustr, it can override those methods.
parent aa5d2f91
......@@ -318,7 +318,7 @@ cdef class _pybstr(bytes): # https://github.com/cython/cython/issues/711
if PY_MAJOR_VERSION >= 3:
return pyu(self)
else:
return self
return pyb(self) # self or pybstr if it was subclass
def __repr__(self):
qself, nonascii_escape = _bpysmartquote_u3b2(self)
......@@ -709,7 +709,7 @@ cdef class _pyustr(unicode):
def __unicode__(self): return pyu(self) # see __str__
def __str__(self):
if PY_MAJOR_VERSION >= 3:
return self
return pyu(self) # self or pyustr if it was subclass
else:
return pyb(self)
......
......@@ -1960,6 +1960,27 @@ def test_strings_subclasses(tx):
_ = b(xx); assert type(_) is bstr ; assert _ == 'мир'
_ = u(xx); assert type(_) is ustr ; assert _ == 'мир'
# __str__ returns *str, not MyStr
txstr = {
unicode: str,
bstr: x32(ustr, bstr),
ustr: x32(ustr, bstr),
}[tx]
if six.PY2 and tx is unicode: # on py2 unicode.__str__ raises UnicodeEncodeError:
aa = u'mir' # `'ascii' codec can't encode ...` -> do the test on ascii
_ = aa.__str__(); assert _ == 'mir'
else:
_ = xx.__str__(); assert _ == 'мир'
assert type(_) is txstr
# for bstr/ustr __unicode__ returns *str, never MyStr
# __bytes__ returns bytes leaving string domain
# (builtin unicode has no __unicode__/__bytes__)
if tx is not unicode:
_ = xx.__unicode__(); assert type(_) is ustr; assert _ == 'мир'
_ = xx.__bytes__(); assert type(_) is bytes; assert _ == xbytes('мир')
# subclass with __str__
class MyStr(tx):
def __str__(self): return u'αβγ'
......
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