Skip to content

Commit 07df8d5

Browse files
gh-93283: Improve error message for f-string with invalid conversion character (GH-93349)
1 parent bb90071 commit 07df8d5

File tree

3 files changed

+52
-23
lines changed

3 files changed

+52
-23
lines changed

Lib/test/test_fstring.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,9 @@ def test_format_specifier_expressions(self):
590590
self.assertEqual(f'{-10:{"-"}#{1}0{"x"}}', ' -0xa')
591591
self.assertEqual(f'{10:#{3 != {4:5} and width}x}', ' 0xa')
592592

593-
self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
593+
self.assertAllRaise(SyntaxError,
594+
"""f-string: invalid conversion character 'r{"': """
595+
"""expected 's', 'r', or 'a'""",
594596
["""f'{"s"!r{":10"}}'""",
595597

596598
# This looks like a nested format spec.
@@ -1012,19 +1014,28 @@ def test_conversions(self):
10121014
# Not a conversion, but show that ! is allowed in a format spec.
10131015
self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!')
10141016

1015-
self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
1016-
["f'{3!g}'",
1017-
"f'{3!A}'",
1018-
"f'{3!3}'",
1019-
"f'{3!G}'",
1020-
"f'{3!!}'",
1017+
self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
1018+
["f'{3!'",
1019+
"f'{3!s'",
1020+
"f'{3!g'",
1021+
])
1022+
1023+
self.assertAllRaise(SyntaxError, 'f-string: missed conversion character',
1024+
["f'{3!}'",
1025+
"f'{3!:'",
10211026
"f'{3!:}'",
1022-
"f'{3! s}'", # no space before conversion char
10231027
])
10241028

1025-
self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
1026-
["f'{x!s{y}}'",
1027-
"f'{3!ss}'",
1029+
for conv in 'g', 'A', '3', 'G', '!', ' s', 's ', ' s ', 'ä', 'ɐ', 'ª':
1030+
self.assertAllRaise(SyntaxError,
1031+
"f-string: invalid conversion character %r: "
1032+
"expected 's', 'r', or 'a'" % conv,
1033+
["f'{3!" + conv + "}'"])
1034+
1035+
self.assertAllRaise(SyntaxError,
1036+
"f-string: invalid conversion character 'ss': "
1037+
"expected 's', 'r', or 'a'",
1038+
["f'{3!ss}'",
10281039
"f'{3!ss:}'",
10291040
"f'{3!ss:s}'",
10301041
])
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve error message for invalid syntax of conversion character in f-string
2+
expressions.

Parser/string_parser.c

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -767,27 +767,43 @@ fstring_find_expr(Parser *p, const char **str, const char *end, int raw, int rec
767767
/* Check for a conversion char, if present. */
768768
if (**str == '!') {
769769
*str += 1;
770-
if (*str >= end) {
771-
goto unexpected_end_of_string;
770+
const char *conv_start = *str;
771+
while (1) {
772+
if (*str >= end) {
773+
goto unexpected_end_of_string;
774+
}
775+
if (**str == '}' || **str == ':') {
776+
break;
777+
}
778+
*str += 1;
779+
}
780+
if (*str == conv_start) {
781+
RAISE_SYNTAX_ERROR(
782+
"f-string: missed conversion character");
783+
goto error;
772784
}
773785

774-
conversion = (unsigned char)**str;
775-
*str += 1;
776-
786+
conversion = (unsigned char)*conv_start;
777787
/* Validate the conversion. */
778-
if (!(conversion == 's' || conversion == 'r' || conversion == 'a')) {
779-
RAISE_SYNTAX_ERROR(
780-
"f-string: invalid conversion character: "
781-
"expected 's', 'r', or 'a'");
788+
if ((*str != conv_start + 1) ||
789+
!(conversion == 's' || conversion == 'r' || conversion == 'a'))
790+
{
791+
PyObject *conv_obj = PyUnicode_FromStringAndSize(conv_start,
792+
*str-conv_start);
793+
if (conv_obj) {
794+
RAISE_SYNTAX_ERROR(
795+
"f-string: invalid conversion character %R: "
796+
"expected 's', 'r', or 'a'",
797+
conv_obj);
798+
Py_DECREF(conv_obj);
799+
}
782800
goto error;
783801
}
784802

785803
}
786804

787805
/* Check for the format spec, if present. */
788-
if (*str >= end) {
789-
goto unexpected_end_of_string;
790-
}
806+
assert(*str < end);
791807
if (**str == ':') {
792808
*str += 1;
793809
if (*str >= end) {

0 commit comments

Comments
 (0)