From f7870f64dfe806dec1b20fcfa5d579611960fba5 Mon Sep 17 00:00:00 2001 From: changjoon-park Date: Mon, 27 Apr 2026 23:20:01 +0900 Subject: [PATCH] Preserve str subclass type returned by __str__ / __repr__ 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). --- Lib/test/test_str.py | 1 - crates/vm/src/builtins/str.rs | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index 15cee0d3a44..56fccea7d92 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -2414,7 +2414,6 @@ def test_ucs4(self): else: self.fail("Should have raised UnicodeDecodeError") - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: is not def test_conversion(self): # Make sure __str__() works properly class StrWithStr(str): diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index b94893fe954..40c9905c1e3 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -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) }