mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Match invalid format specifier error messages (#8477)
* Match invalid format specifier error details Assisted-by: Codex:gpt-5.6-sol * format: convert FormatSpecError in one helper InvalidFormatSpecifier needs the spec and the object type. Drop IntoPyException so that path cannot produce a generic message. Assisted-by: Grok:4.6 --------- Co-authored-by: Jeong YunWon <jeong@youknowone.org>
This commit is contained in:
1 parent
54e119d3c1
commit
3d1c174d17
7 files changed
+105
-107
No files matched your search
Vendored
-3
@@ -529,7 +529,6 @@ class FormatTest(unittest.TestCase):
|
||||
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 @@ class FormatTest(unittest.TestCase):
|
||||
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 @@ class FormatTest(unittest.TestCase):
|
||||
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):
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use super::{PyInt, PyStrRef, PyType, PyTypeRef, PyUtf8StrRef};
|
||||
use crate::common::format::FormatSpec;
|
||||
use crate::{
|
||||
AsObject, Context, Py, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, VirtualMachine,
|
||||
class::{PyClassDef, PyClassImpl},
|
||||
convert::{IntoPyException, ToPyObject, ToPyResult},
|
||||
convert::{ToPyObject, ToPyResult},
|
||||
function::{FuncArgs, OptionalArg},
|
||||
protocol::PyNumberMethods,
|
||||
types::{AsNumber, Constructor, Representable},
|
||||
@@ -124,9 +123,11 @@ impl Py<PyBool> {
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<String> {
|
||||
let new_bool = zelf.try_to_bool(vm)?;
|
||||
FormatSpec::parse(format_spec.as_str())
|
||||
.and_then(|format_spec| format_spec.format_bool(new_bool))
|
||||
.map_err(|err| err.into_pyexception(vm))
|
||||
crate::format::parse_format_spec(zelf.as_object(), format_spec.as_str(), vm)?
|
||||
.format_bool(new_bool)
|
||||
.map_err(|err| {
|
||||
crate::format::format_spec_error(err, zelf.as_object(), format_spec.as_str(), vm)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ use crate::{
|
||||
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
builtins::PyUtf8StrRef,
|
||||
class::{PyClassDef, PyClassImpl},
|
||||
common::{format::FormatSpec, wtf8::Wtf8Buf},
|
||||
convert::{IntoPyException, ToPyObject, ToPyResult},
|
||||
common::wtf8::Wtf8Buf,
|
||||
convert::{ToPyObject, ToPyResult},
|
||||
function::{FuncArgs, OptionalArg, PyComparisonValue},
|
||||
protocol::PyNumberMethods,
|
||||
stdlib::_warnings,
|
||||
@@ -571,17 +571,16 @@ impl Py<PyComplex> {
|
||||
if format_spec.is_empty() {
|
||||
return Ok(zelf.as_object().str(vm)?.as_wtf8().to_owned());
|
||||
}
|
||||
let format_spec =
|
||||
FormatSpec::parse(format_spec.as_str()).map_err(|err| err.into_pyexception(vm))?;
|
||||
let result = if format_spec.has_locale_format() {
|
||||
let spec = crate::format::parse_format_spec(zelf.as_object(), format_spec.as_str(), vm)?;
|
||||
let result = if spec.has_locale_format() {
|
||||
let locale = crate::format::get_locale_info();
|
||||
format_spec.format_complex_locale(&zelf.as_complex(), &locale)
|
||||
spec.format_complex_locale(&zelf.as_complex(), &locale)
|
||||
} else {
|
||||
format_spec.format_complex(&zelf.as_complex())
|
||||
spec.format_complex(&zelf.as_complex())
|
||||
};
|
||||
result
|
||||
.map(Wtf8Buf::from_string)
|
||||
.map_err(|err| err.into_pyexception(vm))
|
||||
result.map(Wtf8Buf::from_string).map_err(|err| {
|
||||
crate::format::format_spec_error(err, zelf.as_object(), format_spec.as_str(), vm)
|
||||
})
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
|
||||
@@ -6,8 +6,8 @@ use crate::{
|
||||
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
|
||||
TryFromBorrowedObject, TryFromObject, VirtualMachine,
|
||||
class::{PyClassDef, PyClassImpl},
|
||||
common::{float_ops, format::FormatSpec, hash, wtf8::Wtf8Buf},
|
||||
convert::{IntoPyException, ToPyObject, ToPyResult},
|
||||
common::{float_ops, hash, wtf8::Wtf8Buf},
|
||||
convert::{ToPyObject, ToPyResult},
|
||||
function::{ArgBytesLike, FuncArgs, OptionalArg, PyArithmeticValue, PyComparisonValue},
|
||||
protocol::PyNumberMethods,
|
||||
types::{AsNumber, Callable, Comparable, Constructor, Hashable, PyComparisonOp, Representable},
|
||||
@@ -291,17 +291,16 @@ impl Py<PyFloat> {
|
||||
if format_spec.is_empty() {
|
||||
return Ok(zelf.as_object().str(vm)?.as_wtf8().to_owned());
|
||||
}
|
||||
let format_spec =
|
||||
FormatSpec::parse(format_spec.as_str()).map_err(|err| err.into_pyexception(vm))?;
|
||||
let result = if format_spec.has_locale_format() {
|
||||
let spec = crate::format::parse_format_spec(zelf.as_object(), format_spec.as_str(), vm)?;
|
||||
let result = if spec.has_locale_format() {
|
||||
let locale = crate::format::get_locale_info();
|
||||
format_spec.format_float_locale(zelf.to_f64(), &locale)
|
||||
spec.format_float_locale(zelf.to_f64(), &locale)
|
||||
} else {
|
||||
format_spec.format_float(zelf.to_f64())
|
||||
spec.format_float(zelf.to_f64())
|
||||
};
|
||||
result
|
||||
.map(Wtf8Buf::from_string)
|
||||
.map_err(|err| err.into_pyexception(vm))
|
||||
result.map(Wtf8Buf::from_string).map_err(|err| {
|
||||
crate::format::format_spec_error(err, zelf.as_object(), format_spec.as_str(), vm)
|
||||
})
|
||||
}
|
||||
|
||||
#[pystaticmethod]
|
||||
|
||||
@@ -6,12 +6,11 @@ use crate::{
|
||||
byte::bytes_from_object,
|
||||
class::{PyClassDef, PyClassImpl},
|
||||
common::{
|
||||
format::FormatSpec,
|
||||
hash,
|
||||
int::{bigint_to_finite_float, bytes_to_int, true_div},
|
||||
wtf8::Wtf8Buf,
|
||||
},
|
||||
convert::{IntoPyException, ToPyObject, ToPyResult},
|
||||
convert::{ToPyObject, ToPyResult},
|
||||
function::{
|
||||
ArgByteOrder, ArgIntoBool, FuncArgs, OptionalArg, PyArithmeticValue, PyComparisonValue,
|
||||
PySsize,
|
||||
@@ -601,20 +600,19 @@ impl Py<PyInt> {
|
||||
if format_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(format_spec.as_str()).map_err(|err| err.into_pyexception(vm))?;
|
||||
if format_spec.is_decimal_int_format() {
|
||||
let spec = crate::format::parse_format_spec(zelf.as_object(), format_spec.as_str(), vm)?;
|
||||
if spec.is_decimal_int_format() {
|
||||
check_int_to_str_digits(zelf.as_bigint(), vm)?;
|
||||
}
|
||||
let result = if format_spec.has_locale_format() {
|
||||
let result = if spec.has_locale_format() {
|
||||
let locale = crate::format::get_locale_info();
|
||||
format_spec.format_int_locale(zelf.as_bigint(), &locale)
|
||||
spec.format_int_locale(zelf.as_bigint(), &locale)
|
||||
} else {
|
||||
format_spec.format_int(zelf.as_bigint())
|
||||
spec.format_int(zelf.as_bigint())
|
||||
};
|
||||
result
|
||||
.map(Wtf8Buf::from_string)
|
||||
.map_err(|err| err.into_pyexception(vm))
|
||||
result.map(Wtf8Buf::from_string).map_err(|err| {
|
||||
crate::format::format_spec_error(err, zelf.as_object(), format_spec.as_str(), vm)
|
||||
})
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::{
|
||||
lock::LazyLock,
|
||||
str::{PyKindStr, StrData, StrKind},
|
||||
},
|
||||
convert::{IntoPyException, ToPyException, ToPyObject, ToPyResult},
|
||||
convert::{ToPyException, ToPyObject, ToPyResult},
|
||||
format::{format, format_map},
|
||||
function::{ArgIterable, FuncArgs, OptionalArg, PyComparisonValue, PySsize},
|
||||
intern::PyInterned,
|
||||
@@ -41,7 +41,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,
|
||||
@@ -1167,11 +1167,11 @@ impl Py<PyStr> {
|
||||
};
|
||||
}
|
||||
let zelf = zelf.try_into_utf8(vm)?;
|
||||
let s = FormatSpec::parse(format_spec.as_str())
|
||||
.and_then(|format_spec| {
|
||||
format_spec.format_string(&CharLenStr(zelf.as_str(), zelf.char_len()))
|
||||
})
|
||||
.map_err(|err| err.into_pyexception(vm))?;
|
||||
let s = crate::format::parse_format_spec(zelf.as_object(), format_spec.as_str(), vm)?
|
||||
.format_string(&CharLenStr(zelf.as_str(), zelf.char_len()))
|
||||
.map_err(|err| {
|
||||
crate::format::format_spec_error(err, zelf.as_object(), format_spec.as_str(), vm)
|
||||
})?;
|
||||
Ok(vm.ctx.new_str(s))
|
||||
}
|
||||
|
||||
|
||||
+65
-61
@@ -1,9 +1,6 @@
|
||||
use crate::{
|
||||
PyObject, PyResult, VirtualMachine,
|
||||
builtins::PyBaseExceptionRef,
|
||||
convert::{IntoPyException, ToPyException},
|
||||
function::FuncArgs,
|
||||
stdlib::builtins,
|
||||
PyObject, PyResult, VirtualMachine, builtins::PyBaseExceptionRef, convert::ToPyException,
|
||||
function::FuncArgs, stdlib::builtins,
|
||||
};
|
||||
|
||||
use crate::common::format::*;
|
||||
@@ -41,63 +38,70 @@ pub(crate) fn get_locale_info() -> LocaleInfo {
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoPyException for FormatSpecError {
|
||||
fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef {
|
||||
match self {
|
||||
Self::DecimalDigitsTooMany => {
|
||||
vm.new_value_error("Too many decimal digits in format string")
|
||||
}
|
||||
Self::PrecisionTooBig => vm.new_value_error("Precision too big"),
|
||||
Self::PrecisionMissing => vm.new_value_error("Format specifier missing precision"),
|
||||
Self::InvalidFormatSpecifier => vm.new_value_error("Invalid format specifier"),
|
||||
Self::UnspecifiedFormat(c1, c2) => {
|
||||
let msg = format!("Cannot specify '{c1}' with '{c2}'.");
|
||||
vm.new_value_error(msg)
|
||||
}
|
||||
Self::ExclusiveFormat(c1, c2) => {
|
||||
let msg = format!("Cannot specify both '{c1}' and '{c2}'.");
|
||||
vm.new_value_error(msg)
|
||||
}
|
||||
Self::UnknownFormatCode(c, s) => {
|
||||
let msg = format!("Unknown format code '{c}' for object of type '{s}'");
|
||||
vm.new_value_error(msg)
|
||||
}
|
||||
Self::PrecisionNotAllowed => {
|
||||
vm.new_value_error("Precision not allowed in integer format specifier")
|
||||
}
|
||||
Self::NotAllowed(s) => {
|
||||
let msg = format!("{s} not allowed with integer format specifier 'c'");
|
||||
vm.new_value_error(msg)
|
||||
}
|
||||
Self::UnableToConvert => vm.new_value_error("Unable to convert int to float"),
|
||||
Self::CodeNotInRange => vm.new_overflow_error("%c arg not in range(0x110000)"),
|
||||
Self::IntTooLargeForCLong => {
|
||||
vm.new_overflow_error("Python int too large to convert to C long")
|
||||
}
|
||||
Self::ZeroPadding => {
|
||||
vm.new_value_error("Zero padding is not allowed in complex format specifier")
|
||||
}
|
||||
Self::AlignmentFlag => {
|
||||
vm.new_value_error("'=' alignment flag is not allowed in complex format specifier")
|
||||
}
|
||||
Self::NegativeZeroCoercionNotAllowed(type_name) => {
|
||||
let msg = format!(
|
||||
"Negative zero coercion (z) not allowed in {type_name} format specifier"
|
||||
);
|
||||
vm.new_value_error(msg)
|
||||
}
|
||||
Self::StringAlignmentFlag => {
|
||||
vm.new_value_error("'=' alignment not allowed in string format specifier")
|
||||
}
|
||||
Self::StringSpecNotAllowed(s) => {
|
||||
let msg = format!("{s} not allowed in string format specifier");
|
||||
vm.new_value_error(msg)
|
||||
}
|
||||
Self::NotImplemented(c, s) => {
|
||||
let msg = format!("Format code '{c}' for object of type '{s}' not implemented yet");
|
||||
vm.new_value_error(msg)
|
||||
}
|
||||
pub(crate) fn parse_format_spec(
|
||||
object: &PyObject,
|
||||
spec: &str,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<FormatSpec> {
|
||||
FormatSpec::parse(spec).map_err(|err| format_spec_error(err, object, spec, vm))
|
||||
}
|
||||
|
||||
pub(crate) fn format_spec_error(
|
||||
err: FormatSpecError,
|
||||
object: &PyObject,
|
||||
spec: &str,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyBaseExceptionRef {
|
||||
match err {
|
||||
FormatSpecError::InvalidFormatSpecifier => vm.new_value_error(format!(
|
||||
"Invalid format specifier '{spec}' for object of type '{}'",
|
||||
object.class().name()
|
||||
)),
|
||||
FormatSpecError::DecimalDigitsTooMany => {
|
||||
vm.new_value_error("Too many decimal digits in format string")
|
||||
}
|
||||
FormatSpecError::PrecisionTooBig => vm.new_value_error("Precision too big"),
|
||||
FormatSpecError::PrecisionMissing => {
|
||||
vm.new_value_error("Format specifier missing precision")
|
||||
}
|
||||
FormatSpecError::UnspecifiedFormat(c1, c2) => {
|
||||
vm.new_value_error(format!("Cannot specify '{c1}' with '{c2}'."))
|
||||
}
|
||||
FormatSpecError::ExclusiveFormat(c1, c2) => {
|
||||
vm.new_value_error(format!("Cannot specify both '{c1}' and '{c2}'."))
|
||||
}
|
||||
FormatSpecError::UnknownFormatCode(c, s) => vm.new_value_error(format!(
|
||||
"Unknown format code '{c}' for object of type '{s}'"
|
||||
)),
|
||||
FormatSpecError::PrecisionNotAllowed => {
|
||||
vm.new_value_error("Precision not allowed in integer format specifier")
|
||||
}
|
||||
FormatSpecError::NotAllowed(s) => {
|
||||
vm.new_value_error(format!("{s} not allowed with integer format specifier 'c'"))
|
||||
}
|
||||
FormatSpecError::UnableToConvert => vm.new_value_error("Unable to convert int to float"),
|
||||
FormatSpecError::CodeNotInRange => vm.new_overflow_error("%c arg not in range(0x110000)"),
|
||||
FormatSpecError::IntTooLargeForCLong => {
|
||||
vm.new_overflow_error("Python int too large to convert to C long")
|
||||
}
|
||||
FormatSpecError::ZeroPadding => {
|
||||
vm.new_value_error("Zero padding is not allowed in complex format specifier")
|
||||
}
|
||||
FormatSpecError::AlignmentFlag => {
|
||||
vm.new_value_error("'=' alignment flag is not allowed in complex format specifier")
|
||||
}
|
||||
FormatSpecError::NegativeZeroCoercionNotAllowed(type_name) => vm.new_value_error(format!(
|
||||
"Negative zero coercion (z) not allowed in {type_name} format specifier"
|
||||
)),
|
||||
FormatSpecError::StringAlignmentFlag => {
|
||||
vm.new_value_error("'=' alignment not allowed in string format specifier")
|
||||
}
|
||||
FormatSpecError::StringSpecNotAllowed(s) => {
|
||||
vm.new_value_error(format!("{s} not allowed in string format specifier"))
|
||||
}
|
||||
FormatSpecError::NotImplemented(c, s) => vm.new_value_error(format!(
|
||||
"Format code '{c}' for object of type '{s}' not implemented yet"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user