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
Description
str()on an object whose__repr__returns astrsubclass instance incorrectly returns a plainstr, discarding the subclass type. CPython preserves the subclass type as-is. This is a CPython compatibility issue.Root Cause
PyStr::py_newcallsinput.str(vm)which correctly resolves to the__repr__return value (a str subclass instance), but then extracts only the raw string data viaSelf::from(s.as_wtf8().to_owned())and creates a new plainPyStr, discarding the subclass type.Reproduction
Output
RustPython:
CPython:
Environment