Skip to content

Commit 6e4ba86

Browse files
committed
Match invalid format specifier error details
Assisted-by: Codex:gpt-5.6-sol
1 parent 689c8b5 commit 6e4ba86

7 files changed

Lines changed: 26 additions & 20 deletions

File tree

Lib/test/test_format.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,6 @@ def test_with_an_underscore_and_a_comma_in_format_specifier(self):
529529
with self.assertRaisesRegex(ValueError, error_msg):
530530
'{:._,f}'.format(1.1)
531531

532-
@unittest.expectedFailure # TODO: RUSTPYTHON
533532
def test_better_error_message_format(self):
534533
# https://bugs.python.org/issue20524
535534
for value in [12j, 12, 12.0, "12"]:
@@ -551,7 +550,6 @@ def test_better_error_message_format(self):
551550
with self.assertRaisesRegex(ValueError, err):
552551
eval("f'xx{value:{bad_format_spec}}yy'")
553552

554-
@unittest.expectedFailure # TODO: RUSTPYTHON
555553
def test_unicode_in_error_message(self):
556554
str_err = re.escape(
557555
"Invalid format specifier '%ЫйЯЧ' for object of type 'str'")
@@ -615,7 +613,6 @@ def test_negative_zero(self):
615613
self.assertEqual(f"{-0.:x>z6.1f}", "xxx0.0")
616614
self.assertEqual(f"{-0.:🖤>z6.1f}", "🖤🖤🖤0.0") # multi-byte fill char
617615

618-
@unittest.expectedFailure # TODO: RUSTPYTHON
619616
def test_specifier_z_error(self):
620617
error_msg = re.compile("Invalid format specifier '.*z.*'")
621618
with self.assertRaisesRegex(ValueError, error_msg):

crates/vm/src/builtins/bool.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use super::{PyInt, PyStrRef, PyType, PyTypeRef, PyUtf8StrRef};
2-
use crate::common::format::FormatSpec;
32
use crate::{
43
AsObject, Context, Py, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, VirtualMachine,
54
class::PyClassImpl,
@@ -100,9 +99,10 @@ impl Constructor for PyBool {
10099
impl PyBool {
101100
#[pymethod]
102101
fn __format__(obj: PyObjectRef, spec: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<String> {
102+
let format_spec = crate::format::parse_format_spec(obj.as_object(), spec.as_str(), vm)?;
103103
let new_bool = obj.try_to_bool(vm)?;
104-
FormatSpec::parse(spec.as_str())
105-
.and_then(|format_spec| format_spec.format_bool(new_bool))
104+
format_spec
105+
.format_bool(new_bool)
106106
.map_err(|err| err.into_pyexception(vm))
107107
}
108108
}

crates/vm/src/builtins/complex.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
44
builtins::PyUtf8StrRef,
55
class::PyClassImpl,
6-
common::{format::FormatSpec, wtf8::Wtf8Buf},
6+
common::wtf8::Wtf8Buf,
77
convert::{IntoPyException, ToPyObject, ToPyResult},
88
function::{FuncArgs, OptionalArg, PyComparisonValue},
99
protocol::PyNumberMethods,
@@ -367,8 +367,7 @@ impl PyComplex {
367367
if spec.is_empty() {
368368
return Ok(zelf.as_object().str(vm)?.as_wtf8().to_owned());
369369
}
370-
let format_spec =
371-
FormatSpec::parse(spec.as_str()).map_err(|err| err.into_pyexception(vm))?;
370+
let format_spec = crate::format::parse_format_spec(zelf.as_object(), spec.as_str(), vm)?;
372371
let result = if format_spec.has_locale_format() {
373372
let locale = crate::format::get_locale_info();
374373
format_spec.format_complex_locale(&zelf.value, &locale)

crates/vm/src/builtins/float.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
77
TryFromBorrowedObject, TryFromObject, VirtualMachine,
88
class::PyClassImpl,
9-
common::{float_ops, format::FormatSpec, hash, wtf8::Wtf8Buf},
9+
common::{float_ops, hash, wtf8::Wtf8Buf},
1010
convert::{IntoPyException, ToPyObject, ToPyResult},
1111
function::{
1212
ArgBytesLike, FuncArgs, OptionalArg, OptionalOption, PyArithmeticValue, PyComparisonValue,
@@ -269,8 +269,7 @@ impl PyFloat {
269269
if spec.is_empty() {
270270
return Ok(zelf.as_object().str(vm)?.as_wtf8().to_owned());
271271
}
272-
let format_spec =
273-
FormatSpec::parse(spec.as_str()).map_err(|err| err.into_pyexception(vm))?;
272+
let format_spec = crate::format::parse_format_spec(zelf.as_object(), spec.as_str(), vm)?;
274273
let result = if format_spec.has_locale_format() {
275274
let locale = crate::format::get_locale_info();
276275
format_spec.format_float_locale(zelf.value, &locale)

crates/vm/src/builtins/int.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use crate::{
66
bytes_inner::PyBytesInner,
77
class::PyClassImpl,
88
common::{
9-
format::FormatSpec,
109
hash,
1110
int::{bigint_to_finite_float, bytes_to_int, true_div},
1211
wtf8::Wtf8Buf,
@@ -511,8 +510,7 @@ impl PyInt {
511510
if spec.is_empty() && !zelf.class().is(vm.ctx.types.int_type) {
512511
return Ok(zelf.as_object().str(vm)?.as_wtf8().to_owned());
513512
}
514-
let format_spec =
515-
FormatSpec::parse(spec.as_str()).map_err(|err| err.into_pyexception(vm))?;
513+
let format_spec = crate::format::parse_format_spec(zelf.as_object(), spec.as_str(), vm)?;
516514
if format_spec.is_decimal_int_format() {
517515
check_int_to_str_digits(&zelf.value, vm)?;
518516
}

crates/vm/src/builtins/str.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use num_traits::ToPrimitive;
4242
use rustpython_common::{
4343
ascii,
4444
atomic::{self, PyAtomic, Radium},
45-
format::{FormatSpec, FormatString, FromTemplate},
45+
format::{FormatString, FromTemplate},
4646
hash,
4747
lock::PyMutex,
4848
str::DeduceStrKind,
@@ -1017,10 +1017,9 @@ impl PyStr {
10171017
};
10181018
}
10191019
let zelf = zelf.try_into_utf8(vm)?;
1020-
let s = FormatSpec::parse(spec.as_str())
1021-
.and_then(|format_spec| {
1022-
format_spec.format_string(&CharLenStr(zelf.as_str(), zelf.char_len()))
1023-
})
1020+
let format_spec = crate::format::parse_format_spec(zelf.as_object(), spec.as_str(), vm)?;
1021+
let s = format_spec
1022+
.format_string(&CharLenStr(zelf.as_str(), zelf.char_len()))
10241023
.map_err(|err| err.into_pyexception(vm))?;
10251024
Ok(vm.ctx.new_str(s))
10261025
}

crates/vm/src/format.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ pub(crate) fn get_locale_info() -> LocaleInfo {
4141
}
4242
}
4343

44+
pub(crate) fn parse_format_spec(
45+
object: &PyObject,
46+
spec: &str,
47+
vm: &VirtualMachine,
48+
) -> PyResult<FormatSpec> {
49+
FormatSpec::parse(spec).map_err(|err| match err {
50+
FormatSpecError::InvalidFormatSpecifier => vm.new_value_error(format!(
51+
"Invalid format specifier '{spec}' for object of type '{}'",
52+
object.class().name()
53+
)),
54+
_ => err.into_pyexception(vm),
55+
})
56+
}
57+
4458
impl IntoPyException for FormatSpecError {
4559
fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef {
4660
match self {

0 commit comments

Comments
 (0)