Skip to content

Commit c191c6c

Browse files
Copilotyouknowone
authored andcommitted
Preserve str subclasses in ascii() for ASCII-only __repr__ results (RustPython#7455)
1 parent ae0ef7b commit c191c6c

7 files changed

Lines changed: 33 additions & 15 deletions

File tree

Lib/test/test_str.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ def test_literals(self):
112112
# raw strings should not have unicode escapes
113113
self.assertNotEqual(r"\u0020", " ")
114114

115-
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <class 'str'> is not <class 'test.test_str.StrSubclass'>
116115
def test_ascii(self):
117116
self.assertEqual(ascii('abc'), "'abc'")
118117
self.assertEqual(ascii('ab\\c'), "'ab\\\\c'")

crates/vm/src/cformat.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ fn spec_format_bytes(
2828
// Unlike strings, %r and %a are identical for bytes: the behaviour corresponds to
2929
// %a for strings (not %r)
3030
CFormatConversion::Repr | CFormatConversion::Ascii => {
31-
let b = builtins::ascii(obj, vm)?.into();
31+
let b = builtins::ascii(obj, vm)?.as_bytes().to_vec();
3232
Ok(b)
3333
}
3434
CFormatConversion::Str | CFormatConversion::Bytes => {
@@ -132,7 +132,7 @@ fn spec_format_string(
132132
match &spec.format_type {
133133
CFormatType::String(conversion) => {
134134
let result = match conversion {
135-
CFormatConversion::Ascii => builtins::ascii(obj, vm)?.into(),
135+
CFormatConversion::Ascii => builtins::ascii(obj, vm)?.as_wtf8().to_owned(),
136136
CFormatConversion::Str => obj.str(vm)?.as_wtf8().to_owned(),
137137
CFormatConversion::Repr => obj.repr(vm)?.as_wtf8().to_owned(),
138138
CFormatConversion::Bytes => {

crates/vm/src/format.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,7 @@ fn format_internal(
159159
let argument = match conversion_spec.and_then(FormatConversion::from_char) {
160160
Some(FormatConversion::Str) => argument.str(vm)?.into(),
161161
Some(FormatConversion::Repr) => argument.repr(vm)?.into(),
162-
Some(FormatConversion::Ascii) => {
163-
vm.ctx.new_str(builtins::ascii(argument, vm)?).into()
164-
}
162+
Some(FormatConversion::Ascii) => builtins::ascii(argument, vm)?.into(),
165163
Some(FormatConversion::Bytes) => {
166164
vm.call_method(&argument, identifier!(vm, decode).as_str(), ())?
167165
}

crates/vm/src/frame.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7104,7 +7104,7 @@ impl ExecutingFrame<'_> {
71047104
let value = match conversion {
71057105
ConvertValueOparg::Str => value.str(vm)?.into(),
71067106
ConvertValueOparg::Repr => value.repr(vm)?.into(),
7107-
ConvertValueOparg::Ascii => vm.ctx.new_str(builtins::ascii(value, vm)?).into(),
7107+
ConvertValueOparg::Ascii => builtins::ascii(value, vm)?.into(),
71087108
ConvertValueOparg::None => value,
71097109
};
71107110

crates/vm/src/protocol/object.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,10 +370,13 @@ impl PyObject {
370370
})
371371
}
372372

373-
pub fn ascii(&self, vm: &VirtualMachine) -> PyResult<ascii::AsciiString> {
373+
pub fn ascii(&self, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
374374
let repr = self.repr(vm)?;
375-
let ascii = to_ascii(repr.as_wtf8());
376-
Ok(ascii)
375+
if repr.as_wtf8().is_ascii() {
376+
Ok(repr)
377+
} else {
378+
Ok(vm.ctx.new_str(to_ascii(repr.as_wtf8())))
379+
}
377380
}
378381

379382
pub fn str_utf8(&self, vm: &VirtualMachine) -> PyResult<PyRef<PyUtf8Str>> {

crates/vm/src/stdlib/builtins.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ mod builtins {
1818
iter::PyCallableIterator,
1919
list::{PyList, SortOptions},
2020
},
21-
common::{hash::PyHash, str::to_ascii},
21+
common::hash::PyHash,
2222
function::{
2323
ArgBytesLike, ArgCallable, ArgIndex, ArgIntoBool, ArgIterable, ArgMapping,
2424
ArgStrOrBytesLike, Either, FsPath, FuncArgs, KwArgs, OptionalArg, OptionalOption,
@@ -64,10 +64,8 @@ mod builtins {
6464
}
6565

6666
#[pyfunction]
67-
pub fn ascii(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<ascii::AsciiString> {
68-
let repr = obj.repr(vm)?;
69-
let ascii = to_ascii(repr.as_wtf8());
70-
Ok(ascii)
67+
pub fn ascii(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
68+
obj.ascii(vm)
7169
}
7270

7371
#[pyfunction]

extra_tests/snippets/builtin_ascii.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,23 @@
55
assert ascii(chr(0x10001)) == "'\\U00010001'"
66
assert ascii(chr(0x9999)) == "'\\u9999'"
77
assert ascii(chr(0x0A)) == "'\\n'"
8+
9+
10+
class MyStr(str):
11+
pass
12+
13+
14+
class Foo:
15+
def __repr__(self):
16+
return MyStr("hello")
17+
18+
19+
class Bar:
20+
def __repr__(self):
21+
return MyStr("héllo")
22+
23+
24+
assert type(ascii(Foo())) is MyStr
25+
assert ascii(Foo()) == MyStr("hello")
26+
assert type(ascii(Bar())) is str
27+
assert ascii(Bar()) == "h\\xe9llo"

0 commit comments

Comments
 (0)