◐ Shell
clean mode source ↗

Preserve `str` subclass results from `__repr__` in plain `str()` by Copilot · Pull Request #7454 · RustPython/RustPython

str(obj) was flattening a str subclass returned by __repr__ into an exact str, diverging from CPython. This only affected the plain str(...) constructor path; subtype construction should still normalize to the requested subtype.

  • Constructor behavior

    • Change the exact str(...) fast path in PyStr::slot_new to return PyObject::str(vm) directly for the single-argument, no-kwargs case.
    • This preserves subclass identity when __str__/__repr__ returns a str subclass, instead of rebuilding a new exact str from the underlying data.
  • Regression coverage

    • Re-enable Lib/test/test_str.py::StrTest.test_conversion, which already covers WithRepr(StrSubclass(...)) and related conversion cases.
    • Keep the Lib/test/test_str.py change aligned with CPython v3.14.3 rather than adding RustPython-only assertions there.
    • Add RustPython-owned identity regression coverage under extra_tests/snippets/builtin_str_subclass.py for the exact str(...) path preserving a str subclass returned by __str__ and __repr__.

Example:

class MyStr(str):
    pass

class Foo:
    def __repr__(self):
        return MyStr("hello")

result = str(Foo())
type(result)  # now: MyStr
Original prompt

This section details on the original issue you should resolve

<issue_title>str() discards str subclass type returned by __repr__</issue_title>
<issue_description>## Description

str() on an object whose __repr__ returns a str subclass instance incorrectly returns a plain str, discarding the subclass type. CPython preserves the subclass type as-is. This is a CPython compatibility issue.

Root Cause

PyStr::py_new calls input.str(vm) which correctly resolves to the __repr__ return value (a str subclass instance), but then extracts only the raw string data via Self::from(s.as_wtf8().to_owned()) and creates a new plain PyStr, discarding the subclass type.

CPython reference: unicodeobject.c#L15575-L15596

// Key branch: only convert when type is a str subclass
if (unicode != NULL && type != &PyUnicode_Type) {
    Py_SETREF(unicode, unicode_subtype_new(type, unicode));
}
return unicode;   // when type is exactly str → return PyObject_Str result as-is

When type == &PyUnicode_Type (plain str() call), unicode_new_impl returns the PyObject_Str result without conversion, preserving the subclass type.

Reproduction

class MyStr(str):
    pass

class Foo:
    def __repr__(self):
        return MyStr('hello')

result = str(Foo())
print(result)
print(type(result))

Output

RustPython:

CPython:

hello
<class '__main__.MyStr'>

Environment

  • RustPython d248a04 (Python 3.14.0)
  • CPython 3.14.3
  • OS: Debian 12</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.

Summary by CodeRabbit

  • Bug Fixes
    • Improved string conversion behavior to ensure consistent handling of arguments passed to the string constructor.