https://github.com/python/cpython/commit/012c498035a9adfe9fd218907db1550ecb057f77
commit: 012c498035a9adfe9fd218907db1550ecb057f77
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-01-24T11:13:50Z
summary:
gh-142037: Improve error messages for printf-style formatting (GH-142081)
This affects string formatting as well as bytes and bytearray formatting.
* For errors in the format string, always include the position of the
start of the format unit.
* For errors related to the formatted arguments, always include the number
or the name of the formatted argument.
* Suggest more probable causes of errors in the format string (stray %,
unsupported format, unexpected character).
* Provide more information when the number of arguments does not match
the number of format units.
* Raise more specific errors when access of arguments by name is mixed with
sequential access and when * is used with a mapping.
* Add tests for some uncovered cases.
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-10-06-06.gh-issue-142037.OpIGzK.rst
M Lib/test/test_bytes.py
M Lib/test/test_format.py
M Lib/test/test_peepholer.py
M Lib/test/test_str.py
M Objects/bytesobject.c
M Objects/unicode_format.c
diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py
index c42c0d4f5e9bc2..742bad21a3346b 100644
--- a/Lib/test/test_bytes.py
+++ b/Lib/test/test_bytes.py
@@ -792,16 +792,16 @@ def __int__(self):
pi = PseudoFloat(3.1415)
exceptions_params = [
- ('%x format: an integer is required, not float', b'%x', 3.14),
- ('%X format: an integer is required, not float', b'%X', 2.11),
- ('%o format: an integer is required, not float', b'%o', 1.79),
- ('%x format: an integer is required, not PseudoFloat', b'%x', pi),
- ('%x format: an integer is required, not complex', b'%x', 3j),
- ('%X format: an integer is required, not complex', b'%X', 2j),
- ('%o format: an integer is required, not complex', b'%o', 1j),
- ('%u format: a real number is required, not complex', b'%u', 3j),
- ('%i format: a real number is required, not complex', b'%i', 2j),
- ('%d format: a real number is required, not complex', b'%d', 2j),
+ ('%x requires an integer, not float', b'%x', 3.14),
+ ('%X requires an integer, not float', b'%X', 2.11),
+ ('%o requires an integer, not float', b'%o', 1.79),
+ (r'%x requires an integer, not .*\.PseudoFloat', b'%x', pi),
+ ('%x requires an integer, not complex', b'%x', 3j),
+ ('%X requires an integer, not complex', b'%X', 2j),
+ ('%o requires an integer, not complex', b'%o', 1j),
+ ('%u requires a real number, not complex', b'%u', 3j),
+ ('%i requires a real number, not complex', b'%i', 2j),
+ ('%d requires a real number, not complex', b'%d', 2j),
(
r'%c requires an integer in range\(256\)'
r' or a single byte, not .*\.PseudoFloat',
@@ -810,7 +810,7 @@ def __int__(self):
]
for msg, format_bytes, value in exceptions_params:
- with self.assertRaisesRegex(TypeError, msg):
+ with self.assertRaisesRegex(TypeError, 'format argument: ' + msg):
operator.mod(format_bytes, value)
def test_memory_leak_gh_140939(self):
diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py
index 1f626d87fa6c7a..00f1ab44b0a8fa 100644
--- a/Lib/test/test_format.py
+++ b/Lib/test/test_format.py
@@ -57,10 +57,6 @@ def testcommon(formatstr, args, output=None, limit=None,
overflowok=False):
else:
b_format = formatstr
ba_format = bytearray(b_format)
- b_args = []
- if not isinstance(args, tuple):
- args = (args, )
- b_args = tuple(args)
if output is None:
b_output = ba_output = None
else:
@@ -69,8 +65,8 @@ def testcommon(formatstr, args, output=None, limit=None,
overflowok=False):
else:
b_output = output
ba_output = bytearray(b_output)
- testformat(b_format, b_args, b_output, limit, overflowok)
- testformat(ba_format, b_args, ba_output, limit, overflowok)
+ testformat(b_format, args, b_output, limit, overflowok)
+ testformat(ba_format, args, ba_output, limit, overflowok)
def test_exc(formatstr, args, exception, excmsg):
try:
@@ -82,6 +78,7 @@ def test_exc(formatstr, args, exception, excmsg):
else:
if verbose: print('no')
print('Unexpected ', exception, ':', repr(str(exc)))
+ raise
except:
if verbose: print('no')
print('Unexpected exception')
@@ -92,6 +89,8 @@ def test_exc(formatstr, args, exception, excmsg):
def test_exc_common(formatstr, args, exception, excmsg):
# test str and bytes
test_exc(formatstr, args, exception, excmsg)
+ if isinstance(args, dict):
+ args = {k.encode('ascii'): v for k, v in args.items()}
test_exc(formatstr.encode('ascii'), args, exception, excmsg)
class FormatTest(unittest.TestCase):
@@ -272,45 +271,154 @@ def test_common_format(self):
if verbose:
print('Testing exceptions')
- test_exc_common('%', (), ValueError, "incomplete format")
- test_exc_common('% %s', 1, ValueError,
- "unsupported format character '%' (0x25) at index 2")
+ test_exc_common('abc %', (), ValueError, "stray % at position 4")
+ test_exc_common('abc % %s', 1, ValueError,
+ "stray % at position 4 or unexpected format character
'%' at position 6")
+ test_exc_common('abc %z', 1, ValueError,
+ "unsupported format %z at position 4")
+ test_exc_common("abc %Id", 1, ValueError,
+ "unsupported format %I at position 4")
+ test_exc_common("abc %'d", 1, ValueError,
+ "stray % at position 4 or unexpected format character
\"'\" at position 5")
+ test_exc_common("abc %1 d", 1, ValueError,
+ "stray % at position 4 or unexpected format character
' ' at position 6")
+ test_exc_common('abc % (x)r', {}, ValueError,
+ "stray % at position 4 or unexpected format character
'(' at position 6")
+ test_exc_common('abc %((x)r', {}, ValueError,
+ "stray % or incomplete format key at position 4")
+ test_exc_common('%r %r', 1, TypeError,
+ "not enough arguments for format string (got 1)")
+ test_exc_common('%r %r', (1,), TypeError,
+ "not enough arguments for format string (got 1)")
+ test_exc_common('%r', (), TypeError,
+ "not enough arguments for format string (got 0)")
+ test_exc_common('abc %' + '9'*50 + 'r', 1, ValueError,
+ "width too big at position 4")
+ test_exc_common('abc %.' + '9'*50 + 'r', 1, ValueError,
+ "precision too big at position 4")
+ test_exc_common('%r %*r', 1, TypeError,
+ "not enough arguments for format string (got 1)")
+ test_exc_common('%r %*r', (1,), TypeError,
+ "not enough arguments for format string (got 1)")
+ test_exc_common('%*r', (1,), TypeError,
+ "not enough arguments for format string (got 1)")
+ test_exc_common('%*r', (), TypeError,
+ "not enough arguments for format string (got 0)")
+ test_exc_common('%r %.*r', 1, TypeError,
+ "not enough arguments for format string (got 1)")
+ test_exc_common('%r %.*r', (1,), TypeError,
+ "not enough arguments for format string (got 1)")
+ test_exc_common('%.*r', (1,), TypeError,
+ "not enough arguments for format string (got 1)")
+ test_exc_common('%.*r', (), TypeError,
+ "not enough arguments for format string (got 0)")
+ test_exc_common('%(x)r', 1, TypeError,
+ "format requires a mapping, not int")
+ test_exc_common('%*r', 1, TypeError,
+ "not enough arguments for format string (got 1)")
+ test_exc_common('%*r', 3.14, TypeError,
+ "not enough arguments for format string (got 1)")
+ test_exc_common('%*r', (3.14, 1), TypeError,
+ "format argument 1: * requires int, not float")
+ test_exc_common('%.*r', 1, TypeError,
+ "not enough arguments for format string (got 1)")
+ test_exc_common('%.*r', 3.14, TypeError,
+ "not enough arguments for format string (got 1)")
+ test_exc_common('%.*r', (3.14, 1), TypeError,
+ "format argument 1: * requires int, not float")
+ test_exc_common('%*r', (2**1000, 1), OverflowError,
+ "format argument 1: too big for width")
+ test_exc_common('%*r', (-2**1000, 1), OverflowError,
+ "format argument 1: too big for width")
+ test_exc_common('%.*r', (2**1000, 1), OverflowError,
+ "format argument 1: too big for precision")
+ test_exc_common('%.*r', (-2**1000, 1), OverflowError,
+ "format argument 1: too big for precision")
test_exc_common('%d', '1', TypeError,
- "%d format: a real number is required, not str")
+ "format argument: %d requires a real number, not str")
test_exc_common('%d', b'1', TypeError,
- "%d format: a real number is required, not bytes")
+ "format argument: %d requires a real number, not
bytes")
+ test_exc_common('%d', ('1',), TypeError,
+ "format argument 1: %d requires a real number, not
str")
test_exc_common('%x', '1', TypeError,
- "%x format: an integer is required, not str")
+ "format argument: %x requires an integer, not str")
test_exc_common('%x', 3.14, TypeError,
- "%x format: an integer is required, not float")
+ "format argument: %x requires an integer, not float")
+ test_exc_common('%x', ('1',), TypeError,
+ "format argument 1: %x requires an integer, not str")
test_exc_common('%i', '1', TypeError,
- "%i format: a real number is required, not str")
+ "format argument: %i requires a real number, not str")
test_exc_common('%i', b'1', TypeError,
- "%i format: a real number is required, not bytes")
+ "format argument: %i requires a real number, not
bytes")
+ test_exc_common('%g', '1', TypeError,
+ "format argument: %g requires a real number, not str")
+ test_exc_common('%g', ('1',), TypeError,
+ "format argument 1: %g requires a real number, not
str")
def test_str_format(self):
testformat("%r", "\u0378", "'\\u0378'") # non printable
testformat("%a", "\u0378", "'\\u0378'") # non printable
testformat("%r", "\u0374", "'\u0374'") # printable
testformat("%a", "\u0374", "'\\u0374'") # printable
+ testformat('%(x)r', {'x': 1}, '1')
# Test exception for unknown format characters, etc.
if verbose:
print('Testing exceptions')
test_exc('abc %b', 1, ValueError,
- "unsupported format character 'b' (0x62) at index 5")
- #test_exc(unicode('abc %\u3000','raw-unicode-escape'), 1, ValueError,
- # "unsupported format character '?' (0x3000) at index 5")
- test_exc('%g', '1', TypeError, "must be real number, not str")
+ "unsupported format %b at position 4")
+ test_exc("abc %\nd", 1, ValueError,
+ "stray % at position 4 or unexpected format character U+000A
at position 5")
+ test_exc("abc %\x1fd", 1, ValueError,
+ "stray % at position 4 or unexpected format character U+001F
at position 5")
+ test_exc("abc %\x7fd", 1, ValueError,
+ "stray % at position 4 or unexpected format character U+007F
at position 5")
+ test_exc("abc %\x80d", 1, ValueError,
+ "stray % at position 4 or unexpected format character U+0080
at position 5")
+ test_exc('abc %äd', 1, ValueError,
+ "stray % at position 4 or unexpected format character 'ä'
(U+00E4) at position 5")
+ test_exc('abc %€d', 1, ValueError,
+ "stray % at position 4 or unexpected format character '€'
(U+20AC) at position 5")
test_exc('no format', '1', TypeError,
- "not all arguments converted during string formatting")
- test_exc('%c', -1, OverflowError, "%c arg not in range(0x110000)")
+ "not all arguments converted during string formatting
(required 0, got 1)")
+ test_exc('%r', (1, 2), TypeError,
+ "not all arguments converted during string formatting
(required 1, got 2)")
+ test_exc('%(x)r %r', {'x': 1}, ValueError,
+ "format requires a parenthesised mapping key at position 6")
+ test_exc('%(x)*r', {'x': 1}, ValueError,
+ "* cannot be used with a parenthesised mapping key at
position 0")
+ test_exc('%(x).*r', {'x': 1}, ValueError,
+ "* cannot be used with a parenthesised mapping key at
position 0")
+ test_exc('%(x)d', {'x': '1'}, TypeError,
+ "format argument 'x': %d requires a real number, not str")
+ test_exc('%(x)x', {'x': '1'}, TypeError,
+ "format argument 'x': %x requires an integer, not str")
+ test_exc('%(x)g', {'x': '1'}, TypeError,
+ "format argument 'x': %g requires a real number, not str")
+ test_exc('%c', -1, OverflowError,
+ "format argument: %c argument not in range(0x110000)")
+ test_exc('%c', (-1,), OverflowError,
+ "format argument 1: %c argument not in range(0x110000)")
+ test_exc('%(x)c', {'x': -1}, OverflowError,
+ "format argument 'x': %c argument not in range(0x110000)")
test_exc('%c', sys.maxunicode+1, OverflowError,
- "%c arg not in range(0x110000)")
- #test_exc('%c', 2**128, OverflowError, "%c arg not in range(0x110000)")
- test_exc('%c', 3.14, TypeError, "%c requires an int or a unicode
character, not float")
- test_exc('%c', 'ab', TypeError, "%c requires an int or a unicode
character, not a string of length 2")
- test_exc('%c', b'x', TypeError, "%c requires an int or a unicode
character, not bytes")
+ "format argument: %c argument not in range(0x110000)")
+ test_exc('%c', 2**128, OverflowError,
+ "format argument: %c argument not in range(0x110000)")
+ test_exc('%c', 3.14, TypeError,
+ "format argument: %c requires an integer or a unicode
character, not float")
+ test_exc('%c', (3.14,), TypeError,
+ "format argument 1: %c requires an integer or a unicode
character, not float")
+ test_exc('%(x)c', {'x': 3.14}, TypeError,
+ "format argument 'x': %c requires an integer or a unicode
character, not float")
+ test_exc('%c', 'ab', TypeError,
+ "format argument: %c requires an integer or a unicode
character, not a string of length 2")
+ test_exc('%c', ('ab',), TypeError,
+ "format argument 1: %c requires an integer or a unicode
character, not a string of length 2")
+ test_exc('%(x)c', {'x': 'ab'}, TypeError,
+ "format argument 'x': %c requires an integer or a unicode
character, not a string of length 2")
+ test_exc('%c', b'x', TypeError,
+ "format argument: %c requires an integer or a unicode
character, not bytes")
if maxsize == 2**31-1:
# crashes 2.2.1 and earlier:
@@ -355,36 +463,83 @@ def __bytes__(self):
testcommon(b"%r", b"ghi", b"b'ghi'")
testcommon(b"%r", "jkl", b"'jkl'")
testcommon(b"%r", "\u0544", b"'\\u0544'")
+ testcommon(b'%(x)r', {b'x': 1}, b'1')
# Test exception for unknown format characters, etc.
if verbose:
print('Testing exceptions')
- test_exc(b'%g', '1', TypeError, "float argument required, not str")
- test_exc(b'%g', b'1', TypeError, "float argument required, not bytes")
+ test_exc(b"abc %\nd", 1, ValueError,
+ "stray % at position 4 or unexpected format character with
code 0x0a at position 5")
+ test_exc(b"abc %'d", 1, ValueError,
+ "stray % at position 4 or unexpected format character \"'\"
at position 5")
+ test_exc(b"abc %\x1fd", 1, ValueError,
+ "stray % at position 4 or unexpected format character with
code 0x1f at position 5")
+ test_exc(b"abc %\x7fd", 1, ValueError,
+ "stray % at position 4 or unexpected format character with
code 0x7f at position 5")
+ test_exc(b"abc %\x80d", 1, ValueError,
+ "stray % at position 4 or unexpected format character with
code 0x80 at position 5")
test_exc(b'no format', 7, TypeError,
- "not all arguments converted during bytes formatting")
+ "not all arguments converted during bytes formatting
(required 0, got 1)")
test_exc(b'no format', b'1', TypeError,
- "not all arguments converted during bytes formatting")
+ "not all arguments converted during bytes formatting
(required 0, got 1)")
test_exc(b'no format', bytearray(b'1'), TypeError,
- "not all arguments converted during bytes formatting")
+ "not all arguments converted during bytes formatting
(required 0, got 1)")
+ test_exc(b'%r', (1, 2), TypeError,
+ "not all arguments converted during bytes formatting
(required 1, got 2)")
+ test_exc(b'%(x)r %r', {b'x': 1}, ValueError,
+ "format requires a parenthesised mapping key at position 6")
+ test_exc(b'%(x)*r', {b'x': 1}, ValueError,
+ "* cannot be used with a parenthesised mapping key at
position 0")
+ test_exc(b'%(x).*r', {b'x': 1}, ValueError,
+ "* cannot be used with a parenthesised mapping key at
position 0")
+ test_exc(b'%(x)d', {b'x': '1'}, TypeError,
+ "format argument b'x': %d requires a real number, not str")
+ test_exc(b'%(x)x', {b'x': '1'}, TypeError,
+ "format argument b'x': %x requires an integer, not str")
+ test_exc(b'%(x)g', {b'x': '1'}, TypeError,
+ "format argument b'x': %g requires a real number, not str")
test_exc(b"%c", -1, OverflowError,
- "%c arg not in range(256)")
+ "format argument: %c argument not in range(256)")
+ test_exc(b"%c", (-1,), OverflowError,
+ "format argument 1: %c argument not in range(256)")
+ test_exc(b"%(x)c", {b'x': -1}, OverflowError,
+ "format argument b'x': %c argument not in range(256)")
test_exc(b"%c", 256, OverflowError,
- "%c arg not in range(256)")
+ "format argument: %c argument not in range(256)")
test_exc(b"%c", 2**128, OverflowError,
- "%c arg not in range(256)")
+ "format argument: %c argument not in range(256)")
test_exc(b"%c", b"Za", TypeError,
- "%c requires an integer in range(256) or a single byte, not a
bytes object of length 2")
+ "format argument: %c requires an integer in range(256) or a
single byte, not a bytes object of length 2")
+ test_exc(b"%c", (b"Za",), TypeError,
+ "format argument 1: %c requires an integer in range(256) or a
single byte, not a bytes object of length 2")
+ test_exc(b"%(x)c", {b'x': b"Za"}, TypeError,
+ "format argument b'x': %c requires an integer in range(256) or
a single byte, not a bytes object of length 2")
+ test_exc(b"%c", bytearray(b"Za"), TypeError,
+ "format argument: %c requires an integer in range(256) or a
single byte, not a bytearray object of length 2")
+ test_exc(b"%c", (bytearray(b"Za"),), TypeError,
+ "format argument 1: %c requires an integer in range(256) or a
single byte, not a bytearray object of length 2")
+ test_exc(b"%(x)c", {b'x': bytearray(b"Za")}, TypeError,
+ "format argument b'x': %c requires an integer in range(256) or
a single byte, not a bytearray object of length 2")
test_exc(b"%c", "Y", TypeError,
- "%c requires an integer in range(256) or a single byte, not
str")
+ "format argument: %c requires an integer in range(256) or a
single byte, not str")
test_exc(b"%c", 3.14, TypeError,
- "%c requires an integer in range(256) or a single byte, not
float")
+ "format argument: %c requires an integer in range(256) or a
single byte, not float")
+ test_exc(b"%c", (3.14,), TypeError,
+ "format argument 1: %c requires an integer in range(256) or a
single byte, not float")
+ test_exc(b"%(x)c", {b'x': 3.14}, TypeError,
+ "format argument b'x': %c requires an integer in range(256) or
a single byte, not float")
test_exc(b"%b", "Xc", TypeError,
- "%b requires a bytes-like object, "
- "or an object that implements __bytes__, not 'str'")
+ "format argument: %b requires a bytes-like object, "
+ "or an object that implements __bytes__, not str")
+ test_exc(b"%b", ("Xc",), TypeError,
+ "format argument 1: %b requires a bytes-like object, "
+ "or an object that implements __bytes__, not str")
+ test_exc(b"%(x)b", {b'x': "Xc"}, TypeError,
+ "format argument b'x': %b requires a bytes-like object, "
+ "or an object that implements __bytes__, not str")
test_exc(b"%s", "Wd", TypeError,
- "%b requires a bytes-like object, "
- "or an object that implements __bytes__, not 'str'")
+ "format argument: %b requires a bytes-like object, "
+ "or an object that implements __bytes__, not str")
if maxsize == 2**31-1:
# crashes 2.2.1 and earlier:
@@ -626,7 +781,7 @@ def test_specifier_z_error(self):
with self.assertRaisesRegex(ValueError, error_msg):
f"{'x':zs}" # can't apply to string
- error_msg = re.escape("unsupported format character 'z'")
+ error_msg = re.escape("unsupported format %z at position 0")
with self.assertRaisesRegex(ValueError, error_msg):
"%z.1f" % 0 # not allowed in old style string interpolation
with self.assertRaisesRegex(ValueError, error_msg):
diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py
index 1caf6de4a14e39..88d20bbb028d6f 100644
--- a/Lib/test/test_peepholer.py
+++ b/Lib/test/test_peepholer.py
@@ -733,22 +733,27 @@ def test_format_errors(self):
with self.assertRaisesRegex(TypeError,
'not all arguments converted during string formatting'):
eval("'%s' % (x, y)", {'x': 1, 'y': 2})
- with self.assertRaisesRegex(ValueError, 'incomplete format'):
+ with self.assertRaisesRegex(ValueError, 'stray % at position 2'):
eval("'%s%' % (x,)", {'x': 1234})
- with self.assertRaisesRegex(ValueError, 'incomplete format'):
+ with self.assertRaisesRegex(ValueError, 'stray % at position 4'):
eval("'%s%%%' % (x,)", {'x': 1234})
with self.assertRaisesRegex(TypeError,
'not enough arguments for format string'):
eval("'%s%z' % (x,)", {'x': 1234})
- with self.assertRaisesRegex(ValueError, 'unsupported format
character'):
+ with self.assertRaisesRegex(ValueError,
+ 'unsupported format %z at position 2'):
eval("'%s%z' % (x, 5)", {'x': 1234})
- with self.assertRaisesRegex(TypeError, 'a real number is required, not
str'):
+ with self.assertRaisesRegex(TypeError,
+ 'format argument 1: %d requires a real number, not str'):
eval("'%d' % (x,)", {'x': '1234'})
- with self.assertRaisesRegex(TypeError, 'an integer is required, not
float'):
+ with self.assertRaisesRegex(TypeError,
+ 'format argument 1: %x requires an integer, not float'):
eval("'%x' % (x,)", {'x': 1234.56})
- with self.assertRaisesRegex(TypeError, 'an integer is required, not
str'):
+ with self.assertRaisesRegex(TypeError,
+ 'format argument 1: %x requires an integer, not str'):
eval("'%x' % (x,)", {'x': '1234'})
- with self.assertRaisesRegex(TypeError, 'must be real number, not str'):
+ with self.assertRaisesRegex(TypeError,
+ 'format argument 1: %f requires a real number, not str'):
eval("'%f' % (x,)", {'x': '1234'})
with self.assertRaisesRegex(TypeError,
'not enough arguments for format string'):
diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py
index 2584fbf72d3fa6..0a8dddb026f6c8 100644
--- a/Lib/test/test_str.py
+++ b/Lib/test/test_str.py
@@ -1578,17 +1578,40 @@ def __int__(self):
self.assertEqual('%X' % letter_m, '6D')
self.assertEqual('%o' % letter_m, '155')
self.assertEqual('%c' % letter_m, 'm')
- self.assertRaisesRegex(TypeError, '%x format: an integer is required,
not float', operator.mod, '%x', 3.14)
- self.assertRaisesRegex(TypeError, '%X format: an integer is required,
not float', operator.mod, '%X', 2.11)
- self.assertRaisesRegex(TypeError, '%o format: an integer is required,
not float', operator.mod, '%o', 1.79)
- self.assertRaisesRegex(TypeError, '%x format: an integer is required,
not PseudoFloat', operator.mod, '%x', pi)
- self.assertRaisesRegex(TypeError, '%x format: an integer is required,
not complex', operator.mod, '%x', 3j)
- self.assertRaisesRegex(TypeError, '%X format: an integer is required,
not complex', operator.mod, '%X', 2j)
- self.assertRaisesRegex(TypeError, '%o format: an integer is required,
not complex', operator.mod, '%o', 1j)
- self.assertRaisesRegex(TypeError, '%u format: a real number is
required, not complex', operator.mod, '%u', 3j)
- self.assertRaisesRegex(TypeError, '%i format: a real number is
required, not complex', operator.mod, '%i', 2j)
- self.assertRaisesRegex(TypeError, '%d format: a real number is
required, not complex', operator.mod, '%d', 1j)
- self.assertRaisesRegex(TypeError, r'%c requires an int or a unicode
character, not .*\.PseudoFloat', operator.mod, '%c', pi)
+ with self.assertRaisesRegex(TypeError,
+ 'format argument: %x requires an integer, not float'):
+ '%x' % 3.14
+ with self.assertRaisesRegex(TypeError,
+ 'format argument: %X requires an integer, not float'):
+ '%X' % 2.11
+ with self.assertRaisesRegex(TypeError,
+ 'format argument: %o requires an integer, not float'):
+ '%o' % 1.79
+ with self.assertRaisesRegex(TypeError,
+ r'format argument: %x requires an integer, not
.*\.PseudoFloat'):
+ '%x' % pi
+ with self.assertRaisesRegex(TypeError,
+ 'format argument: %x requires an integer, not complex'):
+ '%x' % 3j
+ with self.assertRaisesRegex(TypeError,
+ 'format argument: %X requires an integer, not complex'):
+ '%X' % 2j
+ with self.assertRaisesRegex(TypeError,
+ 'format argument: %o requires an integer, not complex'):
+ '%o' % 1j
+ with self.assertRaisesRegex(TypeError,
+ 'format argument: %u requires a real number, not complex'):
+ '%u' % 3j
+ with self.assertRaisesRegex(TypeError,
+ 'format argument: %i requires a real number, not complex'):
+ '%i' % 2j
+ with self.assertRaisesRegex(TypeError,
+ 'format argument: %d requires a real number, not complex'):
+ '%d' % 1j
+ with self.assertRaisesRegex(TypeError,
+ r'format argument: %c requires an integer or a unicode
character, '
+ r'not .*\.PseudoFloat'):
+ '%c' % pi
class RaisingNumber:
def __int__(self):
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-10-06-06.gh-issue-142037.OpIGzK.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-10-06-06.gh-issue-142037.OpIGzK.rst
new file mode 100644
index 00000000000000..6a59be7726f254
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-10-06-06.gh-issue-142037.OpIGzK.rst
@@ -0,0 +1,7 @@
+Improve error messages for printf-style formatting.
+For errors in the format string, always include the position of the
+start of the format unit.
+For errors related to the formatted arguments, always include the number
+or the name of the argument.
+Raise more specific errors and include more information (type and number
+of arguments, most probable causes of error).
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index 56de99bde11682..b85cbfe43e11e0 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -404,26 +404,44 @@ PyBytes_FromFormat(const char *format, ...)
/* Helpers for formatstring */
+#define FORMAT_ERROR(EXC, FMT, ...) do { \
+ if (key != NULL) { \
+ PyErr_Format((EXC), "format argument %R: " FMT, \
+ key, __VA_ARGS__); \
+ } \
+ else if (argidx >= 0) { \
+ PyErr_Format((EXC), "format argument %zd: " FMT, \
+ argidx, __VA_ARGS__); \
+ } \
+ else { \
+ PyErr_Format((EXC), "format argument: " FMT, __VA_ARGS__); \
+ } \
+} while (0)
+
Py_LOCAL_INLINE(PyObject *)
-getnextarg(PyObject *args, Py_ssize_t arglen, Py_ssize_t *p_argidx)
+getnextarg(PyObject *args, Py_ssize_t arglen, Py_ssize_t *p_argidx, int
allowone)
{
Py_ssize_t argidx = *p_argidx;
if (argidx < arglen) {
(*p_argidx)++;
- if (arglen < 0)
- return args;
- else
+ if (arglen >= 0) {
return PyTuple_GetItem(args, argidx);
+ }
+ else if (allowone) {
+ return args;
+ }
}
- PyErr_SetString(PyExc_TypeError,
- "not enough arguments for format string");
+ PyErr_Format(PyExc_TypeError,
+ "not enough arguments for format string (got %zd)",
+ arglen < 0 ? 1 : arglen);
return NULL;
}
/* Returns a new reference to a PyBytes object, or NULL on failure. */
static char*
-formatfloat(PyObject *v, int flags, int prec, int type,
+formatfloat(PyObject *v, Py_ssize_t argidx, PyObject *key,
+ int flags, int prec, int type,
PyObject **p_result, PyBytesWriter *writer, char *str)
{
char *p;
@@ -434,8 +452,11 @@ formatfloat(PyObject *v, int flags, int prec, int type,
x = PyFloat_AsDouble(v);
if (x == -1.0 && PyErr_Occurred()) {
- PyErr_Format(PyExc_TypeError, "float argument required, "
- "not %.200s", Py_TYPE(v)->tp_name);
+ if (PyErr_ExceptionMatches(PyExc_TypeError)) {
+ FORMAT_ERROR(PyExc_TypeError,
+ "%%%c requires a real number, not %T",
+ type, v);
+ }
return NULL;
}
@@ -470,7 +491,8 @@ formatfloat(PyObject *v, int flags, int prec, int type,
}
static PyObject *
-formatlong(PyObject *v, int flags, int prec, int type)
+formatlong(PyObject *v, Py_ssize_t argidx, PyObject *key,
+ int flags, int prec, int type)
{
PyObject *result, *iobj;
if (PyLong_Check(v))
@@ -490,20 +512,21 @@ formatlong(PyObject *v, int flags, int prec, int type)
if (!PyErr_ExceptionMatches(PyExc_TypeError))
return NULL;
}
- PyErr_Format(PyExc_TypeError,
- "%%%c format: %s is required, not %.200s", type,
- (type == 'o' || type == 'x' || type == 'X') ? "an integer"
- : "a real number",
- Py_TYPE(v)->tp_name);
+ FORMAT_ERROR(PyExc_TypeError,
+ "%%%c requires %s, not %T",
+ type,
+ (type == 'o' || type == 'x' || type == 'X') ? "an integer"
+ : "a real number",
+ v);
return NULL;
}
static int
-byte_converter(PyObject *arg, char *p)
+byte_converter(PyObject *arg, Py_ssize_t argidx, PyObject *key, char *p)
{
if (PyBytes_Check(arg)) {
if (PyBytes_GET_SIZE(arg) != 1) {
- PyErr_Format(PyExc_TypeError,
+ FORMAT_ERROR(PyExc_TypeError,
"%%c requires an integer in range(256) or "
"a single byte, not a bytes object of length %zd",
PyBytes_GET_SIZE(arg));
@@ -514,7 +537,7 @@ byte_converter(PyObject *arg, char *p)
}
else if (PyByteArray_Check(arg)) {
if (PyByteArray_GET_SIZE(arg) != 1) {
- PyErr_Format(PyExc_TypeError,
+ FORMAT_ERROR(PyExc_TypeError,
"%%c requires an integer in range(256) or "
"a single byte, not a bytearray object of length %zd",
PyByteArray_GET_SIZE(arg));
@@ -531,23 +554,25 @@ byte_converter(PyObject *arg, char *p)
}
if (!(0 <= ival && ival <= 255)) {
/* this includes an overflow in converting to C long */
- PyErr_SetString(PyExc_OverflowError,
- "%c arg not in range(256)");
+ FORMAT_ERROR(PyExc_OverflowError,
+ "%%c argument not in range(256)%s", "");
return 0;
}
*p = (char)ival;
return 1;
}
- PyErr_Format(PyExc_TypeError,
- "%%c requires an integer in range(256) or a single byte, not %T",
- arg);
+ FORMAT_ERROR(PyExc_TypeError,
+ "%%c requires an integer in range(256) or "
+ "a single byte, not %T",
+ arg);
return 0;
}
static PyObject *_PyBytes_FromBuffer(PyObject *x);
static PyObject *
-format_obj(PyObject *v, const char **pbuf, Py_ssize_t *plen)
+format_obj(PyObject *v, Py_ssize_t argidx, PyObject *key,
+ const char **pbuf, Py_ssize_t *plen)
{
PyObject *func, *result;
/* is it a bytes object? */
@@ -589,10 +614,10 @@ format_obj(PyObject *v, const char **pbuf, Py_ssize_t
*plen)
*plen = PyBytes_GET_SIZE(result);
return result;
}
- PyErr_Format(PyExc_TypeError,
+ FORMAT_ERROR(PyExc_TypeError,
"%%b requires a bytes-like object, "
- "or an object that implements __bytes__, not '%.100s'",
- Py_TYPE(v)->tp_name);
+ "or an object that implements __bytes__, not %T",
+ v);
return NULL;
}
@@ -607,6 +632,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
Py_ssize_t fmtcnt;
int args_owned = 0;
PyObject *dict = NULL;
+ PyObject *key = NULL;
if (args == NULL) {
PyErr_BadInternalCall();
@@ -680,15 +706,17 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t
format_len,
fmtcnt--;
continue;
}
+ Py_CLEAR(key);
+ const char *fmtstart = fmt;
if (*fmt == '(') {
const char *keystart;
Py_ssize_t keylen;
- PyObject *key;
int pcount = 1;
if (dict == NULL) {
- PyErr_SetString(PyExc_TypeError,
- "format requires a mapping");
+ PyErr_Format(PyExc_TypeError,
+ "format requires a mapping, not %T",
+ args);
goto error;
}
++fmt;
@@ -704,8 +732,10 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t
format_len,
}
keylen = fmt - keystart - 1;
if (fmtcnt < 0 || pcount > 0) {
- PyErr_SetString(PyExc_ValueError,
- "incomplete format key");
+ PyErr_Format(PyExc_ValueError,
+ "stray %% or incomplete format key "
+ "at position %zd",
+ (Py_ssize_t)(fmtstart - format - 1));
goto error;
}
key = PyBytes_FromStringAndSize(keystart,
@@ -717,13 +747,21 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t
format_len,
args_owned = 0;
}
args = PyObject_GetItem(dict, key);
- Py_DECREF(key);
if (args == NULL) {
goto error;
}
args_owned = 1;
- arglen = -1;
- argidx = -2;
+ arglen = -3;
+ argidx = -4;
+ }
+ else {
+ if (arglen < -1) {
+ PyErr_Format(PyExc_ValueError,
+ "format requires a parenthesised mapping key "
+ "at position %zd",
+ (Py_ssize_t)(fmtstart - format - 1));
+ goto error;
+ }
}
/* Parse flags. Example: "%+i" => flags=F_SIGN. */
@@ -740,17 +778,28 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t
format_len,
/* Parse width. Example: "%10s" => width=10 */
if (c == '*') {
- v = getnextarg(args, arglen, &argidx);
+ if (arglen < -1) {
+ PyErr_Format(PyExc_ValueError,
+ "* cannot be used with a parenthesised mapping key
"
+ "at position %zd",
+ (Py_ssize_t)(fmtstart - format - 1));
+ goto error;
+ }
+ v = getnextarg(args, arglen, &argidx, 0);
if (v == NULL)
goto error;
if (!PyLong_Check(v)) {
- PyErr_SetString(PyExc_TypeError,
- "* wants int");
+ FORMAT_ERROR(PyExc_TypeError, "* requires int, not %T", v);
goto error;
}
width = PyLong_AsSsize_t(v);
- if (width == -1 && PyErr_Occurred())
+ if (width == -1 && PyErr_Occurred()) {
+ if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+ FORMAT_ERROR(PyExc_OverflowError,
+ "too big for width%s", "");
+ }
goto error;
+ }
if (width < 0) {
flags |= F_LJUST;
width = -width;
@@ -765,9 +814,9 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
if (!Py_ISDIGIT(c))
break;
if (width > (PY_SSIZE_T_MAX - ((int)c - '0')) / 10) {
- PyErr_SetString(
- PyExc_ValueError,
- "width too big");
+ PyErr_Format(PyExc_ValueError,
+ "width too big at position %zd",
+ (Py_ssize_t)(fmtstart - format - 1));
goto error;
}
width = width*10 + (c - '0');
@@ -780,18 +829,29 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t
format_len,
if (--fmtcnt >= 0)
c = *fmt++;
if (c == '*') {
- v = getnextarg(args, arglen, &argidx);
+ if (arglen < -1) {
+ PyErr_Format(PyExc_ValueError,
+ "* cannot be used with a parenthesised mapping
key "
+ "at position %zd",
+ (Py_ssize_t)(fmtstart - format - 1));
+ goto error;
+ }
+ v = getnextarg(args, arglen, &argidx, 0);
if (v == NULL)
goto error;
if (!PyLong_Check(v)) {
- PyErr_SetString(
- PyExc_TypeError,
- "* wants int");
+ FORMAT_ERROR(PyExc_TypeError,
+ "* requires int, not %T", v);
goto error;
}
prec = PyLong_AsInt(v);
- if (prec == -1 && PyErr_Occurred())
+ if (prec == -1 && PyErr_Occurred()) {
+ if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+ FORMAT_ERROR(PyExc_OverflowError,
+ "too big for precision%s", "");
+ }
goto error;
+ }
if (prec < 0)
prec = 0;
if (--fmtcnt >= 0)
@@ -804,9 +864,9 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
if (!Py_ISDIGIT(c))
break;
if (prec > (INT_MAX - ((int)c - '0')) / 10) {
- PyErr_SetString(
- PyExc_ValueError,
- "prec too big");
+ PyErr_Format(PyExc_ValueError,
+ "precision too big at position %zd",
+ (Py_ssize_t)(fmtstart - format - 1));
goto error;
}
prec = prec*10 + (c - '0');
@@ -820,11 +880,12 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t
format_len,
}
}
if (fmtcnt < 0) {
- PyErr_SetString(PyExc_ValueError,
- "incomplete format");
+ PyErr_Format(PyExc_ValueError,
+ "stray %% at position %zd",
+ (Py_ssize_t)(fmtstart - format - 1));
goto error;
}
- v = getnextarg(args, arglen, &argidx);
+ v = getnextarg(args, arglen, &argidx, 1);
if (v == NULL)
goto error;
@@ -852,7 +913,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
case 's':
// %s is only for 2/3 code; 3 only code should use %b
case 'b':
- temp = format_obj(v, &pbuf, &len);
+ temp = format_obj(v, argidx, key, &pbuf, &len);
if (temp == NULL)
goto error;
if (prec >= 0 && len > prec)
@@ -900,7 +961,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
continue;
}
- temp = formatlong(v, flags, prec, c);
+ temp = formatlong(v, argidx, key, flags, prec, c);
if (!temp)
goto error;
assert(PyUnicode_IS_ASCII(temp));
@@ -921,13 +982,13 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t
format_len,
&& !(flags & (F_SIGN | F_BLANK)))
{
/* Fast path */
- res = formatfloat(v, flags, prec, c, NULL, writer, res);
+ res = formatfloat(v, argidx, key, flags, prec, c, NULL,
writer, res);
if (res == NULL)
goto error;
continue;
}
- if (!formatfloat(v, flags, prec, c, &temp, NULL, res))
+ if (!formatfloat(v, argidx, key, flags, prec, c, &temp, NULL,
res))
goto error;
pbuf = PyBytes_AS_STRING(temp);
len = PyBytes_GET_SIZE(temp);
@@ -938,7 +999,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
case 'c':
pbuf = &onechar;
- len = byte_converter(v, &onechar);
+ len = byte_converter(v, argidx, key, &onechar);
if (!len)
goto error;
if (width == -1) {
@@ -949,11 +1010,36 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t
format_len,
break;
default:
- PyErr_Format(PyExc_ValueError,
- "unsupported format character '%c' (0x%x) "
- "at index %zd",
- c, c,
- (Py_ssize_t)(fmt - 1 - format));
+ if (Py_ISALPHA(c)) {
+ PyErr_Format(PyExc_ValueError,
+ "unsupported format %%%c at position %zd",
+ c, (Py_ssize_t)(fmtstart - format - 1));
+ }
+ else if (c == '\'') {
+ PyErr_Format(PyExc_ValueError,
+ "stray %% at position %zd or unexpected "
+ "format character \"'\" "
+ "at position %zd",
+ (Py_ssize_t)(fmtstart - format - 1),
+ (Py_ssize_t)(fmt - format - 1));
+ }
+ else if (c >= 32 && c < 127 && c != '\'') {
+ PyErr_Format(PyExc_ValueError,
+ "stray %% at position %zd or unexpected "
+ "format character '%c' "
+ "at position %zd",
+ (Py_ssize_t)(fmtstart - format - 1),
+ c, (Py_ssize_t)(fmt - format - 1));
+ }
+ else {
+ PyErr_Format(PyExc_ValueError,
+ "stray %% at position %zd or unexpected "
+ "format character with code 0x%02x "
+ "at position %zd",
+ (Py_ssize_t)(fmtstart - format - 1),
+ Py_CHARMASK(c),
+ (Py_ssize_t)(fmt - format - 1));
+ }
goto error;
}
@@ -1042,6 +1128,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t
format_len,
}
if (dict && (argidx < arglen)) {
+ // XXX: Never happens?
PyErr_SetString(PyExc_TypeError,
"not all arguments converted during bytes
formatting");
Py_XDECREF(temp);
@@ -1061,8 +1148,11 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t
format_len,
} /* until end */
if (argidx < arglen && !dict) {
- PyErr_SetString(PyExc_TypeError,
- "not all arguments converted during bytes formatting");
+ PyErr_Format(PyExc_TypeError,
+ "not all arguments converted during bytes formatting "
+ "(required %zd, got %zd)",
+ arglen < 0 ? 0 : argidx,
+ arglen < 0 ? 1 : arglen);
goto error;
}
@@ -1072,6 +1162,7 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t
format_len,
return PyBytesWriter_FinishWithPointer(writer, res);
error:
+ Py_XDECREF(key);
PyBytesWriter_Discard(writer);
if (args_owned) {
Py_DECREF(args);
diff --git a/Objects/unicode_format.c b/Objects/unicode_format.c
index 26bdae55d8b931..e2790c8c1d4343 100644
--- a/Objects/unicode_format.c
+++ b/Objects/unicode_format.c
@@ -72,23 +72,44 @@ struct unicode_format_arg_t {
Py_ssize_t width;
int prec;
int sign;
+ Py_ssize_t fmtstart;
+ PyObject *key;
};
+// Use FORMAT_ERROR("...%s", "") when there is no arguments.
+#define FORMAT_ERROR(EXC, FMT, ...) do { \
+ if (arg->key != NULL) { \
+ PyErr_Format((EXC), "format argument %R: " FMT, \
+ arg->key, __VA_ARGS__); \
+ } \
+ else if (ctx->argidx >= 0) { \
+ PyErr_Format((EXC), "format argument %zd: " FMT, \
+ ctx->argidx, __VA_ARGS__); \
+ } \
+ else { \
+ PyErr_Format((EXC), "format argument: " FMT, __VA_ARGS__); \
+ } \
+} while (0)
+
+
static PyObject *
-unicode_format_getnextarg(struct unicode_formatter_t *ctx)
+unicode_format_getnextarg(struct unicode_formatter_t *ctx, int allowone)
{
Py_ssize_t argidx = ctx->argidx;
- if (argidx < ctx->arglen) {
+ if (argidx < ctx->arglen && (allowone || ctx->arglen >= 0)) {
ctx->argidx++;
- if (ctx->arglen < 0)
- return ctx->args;
- else
+ if (ctx->arglen >= 0) {
return PyTuple_GetItem(ctx->args, argidx);
+ }
+ else if (allowone) {
+ return ctx->args;
+ }
}
- PyErr_SetString(PyExc_TypeError,
- "not enough arguments for format string");
+ PyErr_Format(PyExc_TypeError,
+ "not enough arguments for format string (got %zd)",
+ ctx->arglen < 0 ? 1 : ctx->arglen);
return NULL;
}
@@ -100,7 +121,9 @@ unicode_format_getnextarg(struct unicode_formatter_t *ctx)
Return 0 on success, raise an exception and return -1 on error. */
static int
-formatfloat(PyObject *v, struct unicode_format_arg_t *arg,
+formatfloat(PyObject *v,
+ struct unicode_formatter_t *ctx,
+ struct unicode_format_arg_t *arg,
PyObject **p_output,
_PyUnicodeWriter *writer)
{
@@ -111,8 +134,14 @@ formatfloat(PyObject *v, struct unicode_format_arg_t *arg,
int dtoa_flags = 0;
x = PyFloat_AsDouble(v);
- if (x == -1.0 && PyErr_Occurred())
+ if (x == -1.0 && PyErr_Occurred()) {
+ if (PyErr_ExceptionMatches(PyExc_TypeError)) {
+ FORMAT_ERROR(PyExc_TypeError,
+ "%%%c requires a real number, not %T",
+ arg->ch, v);
+ }
return -1;
+ }
prec = arg->prec;
if (prec < 0)
@@ -287,6 +316,7 @@ _PyUnicode_FormatLong(PyObject *val, int alt, int prec, int
type)
* -1 and raise an exception on error */
static int
mainformatlong(PyObject *v,
+ struct unicode_formatter_t *ctx,
struct unicode_format_arg_t *arg,
PyObject **p_output,
_PyUnicodeWriter *writer)
@@ -364,16 +394,14 @@ mainformatlong(PyObject *v,
case 'o':
case 'x':
case 'X':
- PyErr_Format(PyExc_TypeError,
- "%%%c format: an integer is required, "
- "not %.200s",
- type, Py_TYPE(v)->tp_name);
+ FORMAT_ERROR(PyExc_TypeError,
+ "%%%c requires an integer, not %T",
+ arg->ch, v);
break;
default:
- PyErr_Format(PyExc_TypeError,
- "%%%c format: a real number is required, "
- "not %.200s",
- type, Py_TYPE(v)->tp_name);
+ FORMAT_ERROR(PyExc_TypeError,
+ "%%%c requires a real number, not %T",
+ arg->ch, v);
break;
}
return -1;
@@ -381,15 +409,17 @@ mainformatlong(PyObject *v,
static Py_UCS4
-formatchar(PyObject *v)
+formatchar(PyObject *v,
+ struct unicode_formatter_t *ctx,
+ struct unicode_format_arg_t *arg)
{
/* presume that the buffer is at least 3 characters long */
if (PyUnicode_Check(v)) {
if (PyUnicode_GET_LENGTH(v) == 1) {
return PyUnicode_READ_CHAR(v, 0);
}
- PyErr_Format(PyExc_TypeError,
- "%%c requires an int or a unicode character, "
+ FORMAT_ERROR(PyExc_TypeError,
+ "%%c requires an integer or a unicode character, "
"not a string of length %zd",
PyUnicode_GET_LENGTH(v));
return (Py_UCS4) -1;
@@ -399,18 +429,18 @@ formatchar(PyObject *v)
long x = PyLong_AsLongAndOverflow(v, &overflow);
if (x == -1 && PyErr_Occurred()) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
- PyErr_Format(PyExc_TypeError,
- "%%c requires an int or a unicode character, not
%T",
+ FORMAT_ERROR(PyExc_TypeError,
+ "%%c requires an integer or a unicode character, "
+ "not %T",
v);
- return (Py_UCS4) -1;
}
return (Py_UCS4) -1;
}
if (x < 0 || x > MAX_UNICODE) {
/* this includes an overflow in converting to C long */
- PyErr_SetString(PyExc_OverflowError,
- "%c arg not in range(0x110000)");
+ FORMAT_ERROR(PyExc_OverflowError,
+ "%%c argument not in range(0x110000)%s", "");
return (Py_UCS4) -1;
}
@@ -438,12 +468,12 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
/* Get argument value from a dictionary. Example: "%(name)s". */
Py_ssize_t keystart;
Py_ssize_t keylen;
- PyObject *key;
int pcount = 1;
if (ctx->dict == NULL) {
- PyErr_SetString(PyExc_TypeError,
- "format requires a mapping");
+ PyErr_Format(PyExc_TypeError,
+ "format requires a mapping, not %T",
+ ctx->args);
return -1;
}
++ctx->fmtpos;
@@ -460,25 +490,34 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
}
keylen = ctx->fmtpos - keystart - 1;
if (ctx->fmtcnt < 0 || pcount > 0) {
- PyErr_SetString(PyExc_ValueError,
- "incomplete format key");
+ PyErr_Format(PyExc_ValueError,
+ "stray %% or incomplete format key at position %zd",
+ arg->fmtstart);
return -1;
}
- key = PyUnicode_Substring(ctx->fmtstr,
- keystart, keystart + keylen);
- if (key == NULL)
+ arg->key = PyUnicode_Substring(ctx->fmtstr,
+ keystart, keystart + keylen);
+ if (arg->key == NULL)
return -1;
if (ctx->args_owned) {
ctx->args_owned = 0;
Py_DECREF(ctx->args);
}
- ctx->args = PyObject_GetItem(ctx->dict, key);
- Py_DECREF(key);
+ ctx->args = PyObject_GetItem(ctx->dict, arg->key);
if (ctx->args == NULL)
return -1;
ctx->args_owned = 1;
- ctx->arglen = -1;
- ctx->argidx = -2;
+ ctx->arglen = -3;
+ ctx->argidx = -4;
+ }
+ else {
+ if (ctx->arglen < -1) {
+ PyErr_Format(PyExc_ValueError,
+ "format requires a parenthesised mapping key "
+ "at position %zd",
+ arg->fmtstart);
+ return -1;
+ }
}
/* Parse flags. Example: "%+i" => flags=F_SIGN. */
@@ -497,17 +536,28 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
/* Parse width. Example: "%10s" => width=10 */
if (arg->ch == '*') {
- v = unicode_format_getnextarg(ctx);
+ if (ctx->arglen < -1) {
+ PyErr_Format(PyExc_ValueError,
+ "* cannot be used with a parenthesised mapping key "
+ "at position %zd",
+ arg->fmtstart);
+ return -1;
+ }
+ v = unicode_format_getnextarg(ctx, 0);
if (v == NULL)
return -1;
if (!PyLong_Check(v)) {
- PyErr_SetString(PyExc_TypeError,
- "* wants int");
+ FORMAT_ERROR(PyExc_TypeError, "* requires int, not %T", v);
return -1;
}
arg->width = PyLong_AsSsize_t(v);
- if (arg->width == -1 && PyErr_Occurred())
+ if (arg->width == -1 && PyErr_Occurred()) {
+ if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+ FORMAT_ERROR(PyExc_OverflowError,
+ "too big for width%s", "");
+ }
return -1;
+ }
if (arg->width < 0) {
arg->flags |= F_LJUST;
arg->width = -arg->width;
@@ -528,8 +578,9 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
mixing signed and unsigned comparison. Since arg->ch is between
'0' and '9', casting to int is safe. */
if (arg->width > (PY_SSIZE_T_MAX - ((int)arg->ch - '0')) / 10) {
- PyErr_SetString(PyExc_ValueError,
- "width too big");
+ PyErr_Format(PyExc_ValueError,
+ "width too big at position %zd",
+ arg->fmtstart);
return -1;
}
arg->width = arg->width*10 + (arg->ch - '0');
@@ -544,17 +595,28 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
ctx->fmtpos++;
}
if (arg->ch == '*') {
- v = unicode_format_getnextarg(ctx);
+ if (ctx->arglen < -1) {
+ PyErr_Format(PyExc_ValueError,
+ "* cannot be used with a parenthesised mapping key "
+ "at position %zd",
+ arg->fmtstart);
+ return -1;
+ }
+ v = unicode_format_getnextarg(ctx, 0);
if (v == NULL)
return -1;
if (!PyLong_Check(v)) {
- PyErr_SetString(PyExc_TypeError,
- "* wants int");
+ FORMAT_ERROR(PyExc_TypeError, "* requires int, not %T", v);
return -1;
}
arg->prec = PyLong_AsInt(v);
- if (arg->prec == -1 && PyErr_Occurred())
+ if (arg->prec == -1 && PyErr_Occurred()) {
+ if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+ FORMAT_ERROR(PyExc_OverflowError,
+ "too big for precision%s", "");
+ }
return -1;
+ }
if (arg->prec < 0)
arg->prec = 0;
if (--ctx->fmtcnt >= 0) {
@@ -570,8 +632,9 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
if (arg->ch < '0' || arg->ch > '9')
break;
if (arg->prec > (INT_MAX - ((int)arg->ch - '0')) / 10) {
- PyErr_SetString(PyExc_ValueError,
- "precision too big");
+ PyErr_Format(PyExc_ValueError,
+ "precision too big at position %zd",
+ arg->fmtstart);
return -1;
}
arg->prec = arg->prec*10 + (arg->ch - '0');
@@ -589,8 +652,8 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx,
}
}
if (ctx->fmtcnt < 0) {
- PyErr_SetString(PyExc_ValueError,
- "incomplete format");
+ PyErr_Format(PyExc_ValueError,
+ "stray %% at position %zd", arg->fmtstart);
return -1;
}
return 0;
@@ -624,7 +687,7 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx,
if (ctx->fmtcnt == 0)
ctx->writer.overallocate = 0;
- v = unicode_format_getnextarg(ctx);
+ v = unicode_format_getnextarg(ctx, 1);
if (v == NULL)
return -1;
@@ -660,7 +723,7 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx,
case 'x':
case 'X':
{
- int ret = mainformatlong(v, arg, p_str, writer);
+ int ret = mainformatlong(v, ctx, arg, p_str, writer);
if (ret != 0)
return ret;
arg->sign = 1;
@@ -677,19 +740,19 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx,
&& !(arg->flags & (F_SIGN | F_BLANK)))
{
/* Fast path */
- if (formatfloat(v, arg, NULL, writer) == -1)
+ if (formatfloat(v, ctx, arg, NULL, writer) == -1)
return -1;
return 1;
}
arg->sign = 1;
- if (formatfloat(v, arg, p_str, NULL) == -1)
+ if (formatfloat(v, ctx, arg, p_str, NULL) == -1)
return -1;
break;
case 'c':
{
- Py_UCS4 ch = formatchar(v);
+ Py_UCS4 ch = formatchar(v, ctx, arg);
if (ch == (Py_UCS4) -1)
return -1;
if (arg->width == -1 && arg->prec == -1) {
@@ -703,12 +766,38 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx,
}
default:
- PyErr_Format(PyExc_ValueError,
- "unsupported format character '%c' (0x%x) "
- "at index %zd",
- (31<=arg->ch && arg->ch<=126) ? (char)arg->ch : '?',
- (int)arg->ch,
- ctx->fmtpos - 1);
+ if (arg->ch < 128 && Py_ISALPHA(arg->ch)) {
+ PyErr_Format(PyExc_ValueError,
+ "unsupported format %%%c at position %zd",
+ (int)arg->ch, arg->fmtstart);
+ }
+ else if (arg->ch == '\'') {
+ PyErr_Format(PyExc_ValueError,
+ "stray %% at position %zd or unexpected "
+ "format character \"'\" at position %zd",
+ arg->fmtstart,
+ ctx->fmtpos - 1);
+ }
+ else if (arg->ch >= 32 && arg->ch < 127) {
+ PyErr_Format(PyExc_ValueError,
+ "stray %% at position %zd or unexpected "
+ "format character '%c' at position %zd",
+ arg->fmtstart,
+ (int)arg->ch, ctx->fmtpos - 1);
+ }
+ else if (Py_UNICODE_ISPRINTABLE(arg->ch)) {
+ PyErr_Format(PyExc_ValueError,
+ "stray %% at position %zd or unexpected "
+ "format character '%c' (U+%04X) at position %zd",
+ arg->fmtstart,
+ (int)arg->ch, (int)arg->ch, ctx->fmtpos - 1);
+ }
+ else {
+ PyErr_Format(PyExc_ValueError,
+ "stray %% at position %zd or unexpected "
+ "format character U+%04X at position %zd",
+ arg->fmtstart, (int)arg->ch, ctx->fmtpos - 1);
+ }
return -1;
}
if (*p_str == NULL)
@@ -892,29 +981,40 @@ unicode_format_arg(struct unicode_formatter_t *ctx)
arg.width = -1;
arg.prec = -1;
arg.sign = 0;
+ arg.fmtstart = ctx->fmtpos - 1;
+ arg.key = NULL;
str = NULL;
ret = unicode_format_arg_parse(ctx, &arg);
- if (ret == -1)
- return -1;
+ if (ret == -1) {
+ goto onError;
+ }
ret = unicode_format_arg_format(ctx, &arg, &str);
- if (ret == -1)
- return -1;
+ if (ret == -1) {
+ goto onError;
+ }
if (ret != 1) {
ret = unicode_format_arg_output(ctx, &arg, str);
Py_DECREF(str);
- if (ret == -1)
- return -1;
+ if (ret == -1) {
+ goto onError;
+ }
}
if (ctx->dict && (ctx->argidx < ctx->arglen)) {
+ // XXX: Never happens?
PyErr_SetString(PyExc_TypeError,
"not all arguments converted during string
formatting");
- return -1;
+ goto onError;
}
+ Py_XDECREF(arg.key);
return 0;
+
+ onError:
+ Py_XDECREF(arg.key);
+ return -1;
}
@@ -983,8 +1083,11 @@ PyUnicode_Format(PyObject *format, PyObject *args)
}
if (ctx.argidx < ctx.arglen && !ctx.dict) {
- PyErr_SetString(PyExc_TypeError,
- "not all arguments converted during string
formatting");
+ PyErr_Format(PyExc_TypeError,
+ "not all arguments converted during string formatting "
+ "(required %zd, got %zd)",
+ ctx.arglen < 0 ? 0 : ctx.argidx,
+ ctx.arglen < 0 ? 1 : ctx.arglen);
goto onError;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]