Preserve str subclass type returned by __str__ / __repr__ (#7701)

Closes #7450.

CPython's unicode_new_impl returns the PyObject_Str result as-is when
type == &PyUnicode_Type, only invoking unicode_subtype_new for actual
str subclasses. RustPython's PyStr::Constructor stripped the result via
Self::from(s.as_wtf8().to_owned()) and re-materialized through
into_ref_with_type, dropping the subclass type even when cls is exactly
str.

Add a slot_new branch that returns input.str(vm)? directly when cls is
str_type with no encoding. Subtype construction and the bytes-decoding
path are unchanged.

Unmasks test_str.StrTest.test_conversion (11 assertTypedEqual cases).
This commit is contained in:
Changjoon
2026-04-28 21:09:49 +09:00
committed by GitHub
parent b8f7ae4265
commit 6c498fc4a7
2 changed files with 12 additions and 1 deletions

View File

@@ -2414,7 +2414,6 @@ class StrTest(string_tests.StringLikeTest,
else:
self.fail("Should have raised UnicodeDecodeError")
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <class 'str'> is not <class 'test.test_str.StrSubclass'>
def test_conversion(self):
# Make sure __str__() works properly
class StrWithStr(str):

View File

@@ -406,6 +406,18 @@ impl Constructor for PyStr {
}
let args: Self::Args = func_args.bind(vm)?;
// CPython parity: when cls is exactly str, return the __str__ / __repr__
// result as-is so any str subclass type the user returned is preserved
// (matches unicode_new_impl which only invokes unicode_subtype_new when
// type != &PyUnicode_Type).
if cls.is(vm.ctx.types.str_type)
&& args.encoding.is_missing()
&& let OptionalArg::Present(input) = &args.object
{
return Ok(input.str(vm)?.into());
}
let payload = Self::py_new(&cls, args, vm)?;
payload.into_ref_with_type(vm, cls).map(Into::into)
}