diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index 0c3372f0335517..e8bf420d699ed0 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -590,7 +590,9 @@ def test_format_specifier_expressions(self): self.assertEqual(f'{-10:{"-"}#{1}0{"x"}}', ' -0xa') self.assertEqual(f'{10:#{3 != {4:5} and width}x}', ' 0xa') - self.assertAllRaise(SyntaxError, "f-string: expecting '}'", + self.assertAllRaise(SyntaxError, + """f-string: invalid conversion character 'r{"': """ + """expected 's', 'r', or 'a'""", ["""f'{"s"!r{":10"}}'""", # This looks like a nested format spec. @@ -1012,19 +1014,28 @@ def test_conversions(self): # Not a conversion, but show that ! is allowed in a format spec. self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!') - self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character', - ["f'{3!g}'", - "f'{3!A}'", - "f'{3!3}'", - "f'{3!G}'", - "f'{3!!}'", + self.assertAllRaise(SyntaxError, "f-string: expecting '}'", + ["f'{3!'", + "f'{3!s'", + "f'{3!g'", + ]) + + self.assertAllRaise(SyntaxError, 'f-string: missed conversion character', + ["f'{3!}'", + "f'{3!:'", "f'{3!:}'", - "f'{3! s}'", # no space before conversion char ]) - self.assertAllRaise(SyntaxError, "f-string: expecting '}'", - ["f'{x!s{y}}'", - "f'{3!ss}'", + for conv in 'g', 'A', '3', 'G', '!', ' s', 's ', ' s ', 'ä', 'ɐ', 'ª': + self.assertAllRaise(SyntaxError, + "f-string: invalid conversion character %r: " + "expected 's', 'r', or 'a'" % conv, + ["f'{3!" + conv + "}'"]) + + self.assertAllRaise(SyntaxError, + "f-string: invalid conversion character 'ss': " + "expected 's', 'r', or 'a'", + ["f'{3!ss}'", "f'{3!ss:}'", "f'{3!ss:s}'", ]) diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-05-30-14-50-03.gh-issue-93283.XDO2ZQ.rst b/Misc/NEWS.d/next/Core and Builtins/2022-05-30-14-50-03.gh-issue-93283.XDO2ZQ.rst new file mode 100644 index 00000000000000..550e86988b25a7 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-05-30-14-50-03.gh-issue-93283.XDO2ZQ.rst @@ -0,0 +1,2 @@ +Improve error message for invalid syntax of conversion character in f-string +expressions. diff --git a/Parser/string_parser.c b/Parser/string_parser.c index 9c12d8ca101d00..c56ed20ad4cfc2 100644 --- a/Parser/string_parser.c +++ b/Parser/string_parser.c @@ -767,27 +767,43 @@ fstring_find_expr(Parser *p, const char **str, const char *end, int raw, int rec /* Check for a conversion char, if present. */ if (**str == '!') { *str += 1; - if (*str >= end) { - goto unexpected_end_of_string; + const char *conv_start = *str; + while (1) { + if (*str >= end) { + goto unexpected_end_of_string; + } + if (**str == '}' || **str == ':') { + break; + } + *str += 1; + } + if (*str == conv_start) { + RAISE_SYNTAX_ERROR( + "f-string: missed conversion character"); + goto error; } - conversion = (unsigned char)**str; - *str += 1; - + conversion = (unsigned char)*conv_start; /* Validate the conversion. */ - if (!(conversion == 's' || conversion == 'r' || conversion == 'a')) { - RAISE_SYNTAX_ERROR( - "f-string: invalid conversion character: " - "expected 's', 'r', or 'a'"); + if ((*str != conv_start + 1) || + !(conversion == 's' || conversion == 'r' || conversion == 'a')) + { + PyObject *conv_obj = PyUnicode_FromStringAndSize(conv_start, + *str-conv_start); + if (conv_obj) { + RAISE_SYNTAX_ERROR( + "f-string: invalid conversion character %R: " + "expected 's', 'r', or 'a'", + conv_obj); + Py_DECREF(conv_obj); + } goto error; } } /* Check for the format spec, if present. */ - if (*str >= end) { - goto unexpected_end_of_string; - } + assert(*str < end); if (**str == ':') { *str += 1; if (*str >= end) {