Skip to content

Preserve str subclass type returned by __str__ / __repr__#7701

Merged
youknowone merged 1 commit intoRustPython:mainfrom
changjoon-park:fix-str-subclass-preservation
Apr 28, 2026
Merged

Preserve str subclass type returned by __str__ / __repr__#7701
youknowone merged 1 commit intoRustPython:mainfrom
changjoon-park:fix-str-subclass-preservation

Conversation

@changjoon-park
Copy link
Copy Markdown
Contributor

@changjoon-park changjoon-park commented Apr 27, 2026

Closes #7450.

Background

CPython's unicode_new_impl (Objects/unicodeobject.c#L15575-L15596) only invokes unicode_subtype_new when the requested type is a str subclass. When type == &PyUnicode_Type, it returns the result of PyObject_Str(x) as-is, preserving any str subclass type that __str__ or __repr__ returned.

RustPython's PyStr::Constructor::py_new extracted the raw bytes via Self::from(s.as_wtf8().to_owned()), then slot_new re-materialized through into_ref_with_type(vm, cls). This stripped the subclass type even when cls is str.

Repro

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

print(type(str(Foo())))
# CPython 3.14:    <class '__main__.MyStr'>
# RustPython:      <class 'str'>     (before this PR)

Fix

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

Tests unmasked

  • test_str.StrTest.test_conversion (11 assertTypedEqual cases covering str(WithStr/StrWithStr/WithRepr(...)) × StrSubclass / OtherStrSubclass permutations)

Verification

  • CPython 3.14.4 byte-identical for 8 probed cases (plain str(), str('hi'), str(MyStr('x')), __str__/__repr__ returning subclass, MyStr('z'), OtherStr(MyStr('w')), str(b'abc', 'utf-8'))
  • No regressions across 14 modules (~3,349 tests): test_str, test_descr, test_class, test_typing, test_userstring, test_format, test_pickle, test_io, test_collections, test_dict, test_set, test_exceptions, test_fstring, test_genericalias

Summary by CodeRabbit

  • Bug Fixes
    • Improved string constructor to correctly preserve behavior when using user-defined string subclasses.

Closes RustPython#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).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: a993d0e1-3739-445d-bfa0-88d5be8bf2ac

📥 Commits

Reviewing files that changed from the base of the PR and between 1d42ee5 and f7870f6.

⛔ Files ignored due to path filters (1)
  • Lib/test/test_str.py is excluded by !Lib/**
📒 Files selected for processing (1)
  • crates/vm/src/builtins/str.rs

📝 Walkthrough

Walkthrough

This PR adds a short-circuit optimization to PyStr::slot_new that preserves str subclass types returned by __repr__. When constructing an exact str from a single object argument without encoding, the implementation now directly returns the result of input.str(vm) instead of converting it to a plain str, aligning with CPython's behavior.

Changes

Cohort / File(s) Summary
Str constructor short-circuit
crates/vm/src/builtins/str.rs
Added early return path in PyStr::slot_new for exact str type with single argument (no encoding), preserving subclass types from __repr__ method returns.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related issues

  • #7451: Both changes address preserving str subclass objects by returning existing repr/str directly instead of always constructing a new plain str.

Suggested reviewers

  • ShaharNaveh
  • youknowone

Poem

🐰 A subclass preserved, oh what delight!
No more discarding types left and right,
When __repr__ speaks in custom voice,
The rabbit cheers—CPython's choice!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: preserving str subclass types returned by str/repr methods in the constructor logic.
Linked Issues check ✅ Passed The PR directly addresses issue #7450 by adding a constructor short-circuit that returns input.str(vm) directly when cls is exactly str, preserving str subclass types as required.
Out of Scope Changes check ✅ Passed All changes are localized to PyStr::slot_new constructor logic and directly address the linked issue requirements; no out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

[x] test: cpython/Lib/test/test_str.py (TODO: 14)
[x] test: cpython/Lib/test/test_fstring.py (TODO: 19)
[x] test: cpython/Lib/test/test_string_literals.py (TODO: 4)

dependencies:

dependent tests: (no tests depend on str)

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

Copy link
Copy Markdown
Contributor

@ShaharNaveh ShaharNaveh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!
ty for finding these:)

@changjoon-park
Copy link
Copy Markdown
Contributor Author

Great! ty for finding these:)

thanks for the quick check !

@youknowone youknowone merged commit 6c498fc into RustPython:main Apr 28, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

str() discards str subclass type returned by __repr__

3 participants