From 12e5797c64a3842ea441625fee644fe015166402 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 30 May 2022 14:51:10 +0300 Subject: [PATCH 1/3] gh-93283: Improve error message for f-string with invalid conversion character --- Lib/test/test_fstring.py | 29 ++++++++++++------- ...2-05-30-14-50-03.gh-issue-93283.XDO2ZQ.rst | 2 ++ Parser/string_parser.c | 28 ++++++++++++++---- 3 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-05-30-14-50-03.gh-issue-93283.XDO2ZQ.rst diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index 0c3372f0335517f..d3f6a078ffda279 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,24 @@ 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!'"]) + + self.assertAllRaise(SyntaxError, 'f-string: missed conversion character', + ["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 ', 'ж': + 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 000000000000000..550e86988b25a75 --- /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 9c12d8ca101d00f..f1c4528b4259e74 100644 --- a/Parser/string_parser.c +++ b/Parser/string_parser.c @@ -771,14 +771,30 @@ fstring_find_expr(Parser *p, const char **str, const char *end, int raw, int rec goto unexpected_end_of_string; } - conversion = (unsigned char)**str; - *str += 1; + const char *conv_start = *str; + while (*str < end && **str != '}' && **str != ':') { + *str += 1; + } + if (*str == conv_start) { + RAISE_SYNTAX_ERROR( + "f-string: missed conversion character"); + goto error; + } + 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; } From 2a2d3418b529d76ca1c8389d494ec7bb57098ce1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 30 May 2022 18:58:45 +0300 Subject: [PATCH 2/3] Improve more. --- Lib/test/test_fstring.py | 6 +++++- Parser/string_parser.c | 18 +++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index d3f6a078ffda279..dac79515849e171 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -1015,10 +1015,14 @@ def test_conversions(self): self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!') self.assertAllRaise(SyntaxError, "f-string: expecting '}'", - ["f'{3!'"]) + ["f'{3!'", + "f'{3!s'", + "f'{3!g'", + ]) self.assertAllRaise(SyntaxError, 'f-string: missed conversion character', ["f'{3!}'", + "f'{3!:'", "f'{3!:}'", ]) diff --git a/Parser/string_parser.c b/Parser/string_parser.c index f1c4528b4259e74..c56ed20ad4cfc23 100644 --- a/Parser/string_parser.c +++ b/Parser/string_parser.c @@ -767,12 +767,14 @@ 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 (*str < end && **str != '}' && **str != ':') { + while (1) { + if (*str >= end) { + goto unexpected_end_of_string; + } + if (**str == '}' || **str == ':') { + break; + } *str += 1; } if (*str == conv_start) { @@ -787,7 +789,7 @@ fstring_find_expr(Parser *p, const char **str, const char *end, int raw, int rec !(conversion == 's' || conversion == 'r' || conversion == 'a')) { PyObject *conv_obj = PyUnicode_FromStringAndSize(conv_start, - *str-conv_start); + *str-conv_start); if (conv_obj) { RAISE_SYNTAX_ERROR( "f-string: invalid conversion character %R: " @@ -801,9 +803,7 @@ fstring_find_expr(Parser *p, const char **str, const char *end, int raw, int rec } /* 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) { From 9732c1c0f0d7de4b5ee4133e1791d3ed9e26e9c6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 31 May 2022 08:32:10 +0300 Subject: [PATCH 3/3] More tests. --- Lib/test/test_fstring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index dac79515849e171..e8bf420d699ed0b 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -1026,7 +1026,7 @@ def test_conversions(self): "f'{3!:}'", ]) - for conv in 'g', 'A', '3', 'G', '!', ' s', 's ', 'ж': + 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,