From 043bf3e943022216d171054b20b4b32bbd7ce06e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:15:14 +0000 Subject: [PATCH 1/4] Initial plan From 18fb23ed346b1266f96b96c3c244bbcef86899cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:31:24 +0000 Subject: [PATCH 2/4] Preserve str subclasses in ascii Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- Lib/test/test_str.py | 1 - crates/vm/src/cformat.rs | 4 ++-- crates/vm/src/format.rs | 4 +--- crates/vm/src/frame.rs | 2 +- crates/vm/src/protocol/object.rs | 9 ++++++--- crates/vm/src/stdlib/builtins.rs | 9 ++++++--- extra_tests/snippets/builtin_ascii.py | 16 ++++++++++++++++ 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index 3b64033c825..11e2abb82c5 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -112,7 +112,6 @@ def test_literals(self): # raw strings should not have unicode escapes self.assertNotEqual(r"\u0020", " ") - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: is not def test_ascii(self): self.assertEqual(ascii('abc'), "'abc'") self.assertEqual(ascii('ab\\c'), "'ab\\\\c'") diff --git a/crates/vm/src/cformat.rs b/crates/vm/src/cformat.rs index 939b1c7760f..5421b21d416 100644 --- a/crates/vm/src/cformat.rs +++ b/crates/vm/src/cformat.rs @@ -28,7 +28,7 @@ fn spec_format_bytes( // Unlike strings, %r and %a are identical for bytes: the behaviour corresponds to // %a for strings (not %r) CFormatConversion::Repr | CFormatConversion::Ascii => { - let b = builtins::ascii(obj, vm)?.into(); + let b = builtins::ascii(obj, vm)?.as_bytes().to_vec(); Ok(b) } CFormatConversion::Str | CFormatConversion::Bytes => { @@ -132,7 +132,7 @@ fn spec_format_string( match &spec.format_type { CFormatType::String(conversion) => { let result = match conversion { - CFormatConversion::Ascii => builtins::ascii(obj, vm)?.into(), + CFormatConversion::Ascii => builtins::ascii(obj, vm)?.as_wtf8().to_owned(), CFormatConversion::Str => obj.str(vm)?.as_wtf8().to_owned(), CFormatConversion::Repr => obj.repr(vm)?.as_wtf8().to_owned(), CFormatConversion::Bytes => { diff --git a/crates/vm/src/format.rs b/crates/vm/src/format.rs index 95bd893baea..e60b8ed9a4a 100644 --- a/crates/vm/src/format.rs +++ b/crates/vm/src/format.rs @@ -159,9 +159,7 @@ fn format_internal( let argument = match conversion_spec.and_then(FormatConversion::from_char) { Some(FormatConversion::Str) => argument.str(vm)?.into(), Some(FormatConversion::Repr) => argument.repr(vm)?.into(), - Some(FormatConversion::Ascii) => { - vm.ctx.new_str(builtins::ascii(argument, vm)?).into() - } + Some(FormatConversion::Ascii) => builtins::ascii(argument, vm)?.into(), Some(FormatConversion::Bytes) => { vm.call_method(&argument, identifier!(vm, decode).as_str(), ())? } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 16fd9fa88b1..d8e3d35f9e3 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -7104,7 +7104,7 @@ impl ExecutingFrame<'_> { let value = match conversion { ConvertValueOparg::Str => value.str(vm)?.into(), ConvertValueOparg::Repr => value.repr(vm)?.into(), - ConvertValueOparg::Ascii => vm.ctx.new_str(builtins::ascii(value, vm)?).into(), + ConvertValueOparg::Ascii => builtins::ascii(value, vm)?.into(), ConvertValueOparg::None => value, }; diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index 922acaa411b..871c1514c31 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -370,10 +370,13 @@ impl PyObject { }) } - pub fn ascii(&self, vm: &VirtualMachine) -> PyResult { + pub fn ascii(&self, vm: &VirtualMachine) -> PyResult> { let repr = self.repr(vm)?; - let ascii = to_ascii(repr.as_wtf8()); - Ok(ascii) + if repr.as_wtf8().is_ascii() { + Ok(repr) + } else { + Ok(vm.ctx.new_str(to_ascii(repr.as_wtf8()))) + } } pub fn str_utf8(&self, vm: &VirtualMachine) -> PyResult> { diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index a506f774609..6017cd96308 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -64,10 +64,13 @@ mod builtins { } #[pyfunction] - pub fn ascii(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + pub fn ascii(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { let repr = obj.repr(vm)?; - let ascii = to_ascii(repr.as_wtf8()); - Ok(ascii) + if repr.as_wtf8().is_ascii() { + Ok(repr) + } else { + Ok(vm.ctx.new_str(to_ascii(repr.as_wtf8()))) + } } #[pyfunction] diff --git a/extra_tests/snippets/builtin_ascii.py b/extra_tests/snippets/builtin_ascii.py index 5b5b45d9990..ef0bb124271 100644 --- a/extra_tests/snippets/builtin_ascii.py +++ b/extra_tests/snippets/builtin_ascii.py @@ -5,3 +5,19 @@ assert ascii(chr(0x10001)) == "'\\U00010001'" assert ascii(chr(0x9999)) == "'\\u9999'" assert ascii(chr(0x0A)) == "'\\n'" + +class MyStr(str): + pass + +class Foo: + def __repr__(self): + return MyStr("hello") + +class Bar: + def __repr__(self): + return MyStr("héllo") + +assert type(ascii(Foo())) is MyStr +assert ascii(Foo()) == MyStr("hello") +assert type(ascii(Bar())) is str +assert ascii(Bar()) == "h\\xe9llo" From 6803cee423c0902fff6abd42e3651d445f47df66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:19:25 +0000 Subject: [PATCH 3/4] Fix ruff formatting in builtin_ascii.py snippet Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- extra_tests/snippets/builtin_ascii.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extra_tests/snippets/builtin_ascii.py b/extra_tests/snippets/builtin_ascii.py index ef0bb124271..08ce51cc086 100644 --- a/extra_tests/snippets/builtin_ascii.py +++ b/extra_tests/snippets/builtin_ascii.py @@ -6,17 +6,21 @@ assert ascii(chr(0x9999)) == "'\\u9999'" assert ascii(chr(0x0A)) == "'\\n'" + class MyStr(str): pass + class Foo: def __repr__(self): return MyStr("hello") + class Bar: def __repr__(self): return MyStr("héllo") + assert type(ascii(Foo())) is MyStr assert ascii(Foo()) == MyStr("hello") assert type(ascii(Bar())) is str From 77767fcd7ad1b8788e6dbe001c232e613b636bc6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:21:30 +0000 Subject: [PATCH 4/4] Delegate builtin_ascii() to PyObject::ascii() to eliminate duplication Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/vm/src/stdlib/builtins.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 6017cd96308..008d342a79b 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -18,7 +18,7 @@ mod builtins { iter::PyCallableIterator, list::{PyList, SortOptions}, }, - common::{hash::PyHash, str::to_ascii}, + common::hash::PyHash, function::{ ArgBytesLike, ArgCallable, ArgIndex, ArgIntoBool, ArgIterable, ArgMapping, ArgStrOrBytesLike, Either, FsPath, FuncArgs, KwArgs, OptionalArg, OptionalOption, @@ -65,12 +65,7 @@ mod builtins { #[pyfunction] pub fn ascii(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let repr = obj.repr(vm)?; - if repr.as_wtf8().is_ascii() { - Ok(repr) - } else { - Ok(vm.ctx.new_str(to_ascii(repr.as_wtf8()))) - } + obj.ascii(vm) } #[pyfunction]