diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index aa28108312e..1f626d87fa6 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -529,7 +529,6 @@ def test_with_an_underscore_and_a_comma_in_format_specifier(self): with self.assertRaisesRegex(ValueError, error_msg): '{:._,f}'.format(1.1) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_better_error_message_format(self): # https://bugs.python.org/issue20524 for value in [12j, 12, 12.0, "12"]: @@ -551,7 +550,6 @@ def test_better_error_message_format(self): with self.assertRaisesRegex(ValueError, err): eval("f'xx{value:{bad_format_spec}}yy'") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_unicode_in_error_message(self): str_err = re.escape( "Invalid format specifier '%ЫйЯЧ' for object of type 'str'") @@ -615,7 +613,6 @@ def test_negative_zero(self): self.assertEqual(f"{-0.:x>z6.1f}", "xxx0.0") self.assertEqual(f"{-0.:🖤>z6.1f}", "🖤🖤🖤0.0") # multi-byte fill char - @unittest.expectedFailure # TODO: RUSTPYTHON def test_specifier_z_error(self): error_msg = re.compile("Invalid format specifier '.*z.*'") with self.assertRaisesRegex(ValueError, error_msg): diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 4bb980d71a2..7d26e3f6411 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -1,5 +1,4 @@ use super::{PyInt, PyStrRef, PyType, PyTypeRef, PyUtf8StrRef}; -use crate::common::format::FormatSpec; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, VirtualMachine, class::PyClassImpl, @@ -100,9 +99,10 @@ impl Constructor for PyBool { impl PyBool { #[pymethod] fn __format__(obj: PyObjectRef, spec: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult { + let format_spec = crate::format::parse_format_spec(obj.as_object(), spec.as_str(), vm)?; let new_bool = obj.try_to_bool(vm)?; - FormatSpec::parse(spec.as_str()) - .and_then(|format_spec| format_spec.format_bool(new_bool)) + format_spec + .format_bool(new_bool) .map_err(|err| err.into_pyexception(vm)) } } diff --git a/crates/vm/src/builtins/complex.rs b/crates/vm/src/builtins/complex.rs index c54b3bc1731..5991392a892 100644 --- a/crates/vm/src/builtins/complex.rs +++ b/crates/vm/src/builtins/complex.rs @@ -3,7 +3,7 @@ use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::PyUtf8StrRef, class::PyClassImpl, - common::{format::FormatSpec, wtf8::Wtf8Buf}, + common::wtf8::Wtf8Buf, convert::{IntoPyException, ToPyObject, ToPyResult}, function::{FuncArgs, OptionalArg, PyComparisonValue}, protocol::PyNumberMethods, @@ -367,8 +367,7 @@ impl PyComplex { if spec.is_empty() { return Ok(zelf.as_object().str(vm)?.as_wtf8().to_owned()); } - let format_spec = - FormatSpec::parse(spec.as_str()).map_err(|err| err.into_pyexception(vm))?; + let format_spec = crate::format::parse_format_spec(zelf.as_object(), spec.as_str(), vm)?; let result = if format_spec.has_locale_format() { let locale = crate::format::get_locale_info(); format_spec.format_complex_locale(&zelf.value, &locale) diff --git a/crates/vm/src/builtins/float.rs b/crates/vm/src/builtins/float.rs index 1c861b14fc6..21163b9d0e0 100644 --- a/crates/vm/src/builtins/float.rs +++ b/crates/vm/src/builtins/float.rs @@ -6,7 +6,7 @@ use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromBorrowedObject, TryFromObject, VirtualMachine, class::PyClassImpl, - common::{float_ops, format::FormatSpec, hash, wtf8::Wtf8Buf}, + common::{float_ops, hash, wtf8::Wtf8Buf}, convert::{IntoPyException, ToPyObject, ToPyResult}, function::{ ArgBytesLike, FuncArgs, OptionalArg, OptionalOption, PyArithmeticValue, PyComparisonValue, @@ -269,8 +269,7 @@ impl PyFloat { if spec.is_empty() { return Ok(zelf.as_object().str(vm)?.as_wtf8().to_owned()); } - let format_spec = - FormatSpec::parse(spec.as_str()).map_err(|err| err.into_pyexception(vm))?; + let format_spec = crate::format::parse_format_spec(zelf.as_object(), spec.as_str(), vm)?; let result = if format_spec.has_locale_format() { let locale = crate::format::get_locale_info(); format_spec.format_float_locale(zelf.value, &locale) diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index 278a9cecbb1..689c92fb239 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -6,7 +6,6 @@ use crate::{ bytes_inner::PyBytesInner, class::PyClassImpl, common::{ - format::FormatSpec, hash, int::{bigint_to_finite_float, bytes_to_int, true_div}, wtf8::Wtf8Buf, @@ -511,8 +510,7 @@ impl PyInt { if spec.is_empty() && !zelf.class().is(vm.ctx.types.int_type) { return Ok(zelf.as_object().str(vm)?.as_wtf8().to_owned()); } - let format_spec = - FormatSpec::parse(spec.as_str()).map_err(|err| err.into_pyexception(vm))?; + let format_spec = crate::format::parse_format_spec(zelf.as_object(), spec.as_str(), vm)?; if format_spec.is_decimal_int_format() { check_int_to_str_digits(&zelf.value, vm)?; } diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 07325159a39..1746f82dad7 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -42,7 +42,7 @@ use num_traits::ToPrimitive; use rustpython_common::{ ascii, atomic::{self, PyAtomic, Radium}, - format::{FormatSpec, FormatString, FromTemplate}, + format::{FormatString, FromTemplate}, hash, lock::PyMutex, str::DeduceStrKind, @@ -1017,10 +1017,9 @@ impl PyStr { }; } let zelf = zelf.try_into_utf8(vm)?; - let s = FormatSpec::parse(spec.as_str()) - .and_then(|format_spec| { - format_spec.format_string(&CharLenStr(zelf.as_str(), zelf.char_len())) - }) + let format_spec = crate::format::parse_format_spec(zelf.as_object(), spec.as_str(), vm)?; + let s = format_spec + .format_string(&CharLenStr(zelf.as_str(), zelf.char_len())) .map_err(|err| err.into_pyexception(vm))?; Ok(vm.ctx.new_str(s)) } diff --git a/crates/vm/src/format.rs b/crates/vm/src/format.rs index 657601e1470..0c3345e3257 100644 --- a/crates/vm/src/format.rs +++ b/crates/vm/src/format.rs @@ -41,6 +41,20 @@ pub(crate) fn get_locale_info() -> LocaleInfo { } } +pub(crate) fn parse_format_spec( + object: &PyObject, + spec: &str, + vm: &VirtualMachine, +) -> PyResult { + FormatSpec::parse(spec).map_err(|err| match err { + FormatSpecError::InvalidFormatSpecifier => vm.new_value_error(format!( + "Invalid format specifier '{spec}' for object of type '{}'", + object.class().name() + )), + _ => err.into_pyexception(vm), + }) +} + impl IntoPyException for FormatSpecError { fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef { match self {