mirror of
https://github.com/python/cpython.git
synced 2025-08-30 13:38:43 +00:00
bpo-35494: Improve syntax error messages for unbalanced parentheses in f-string. (GH-11161)
This commit is contained in:
parent
44cc4822bb
commit
58159ef856
3 changed files with 51 additions and 17 deletions
|
@ -368,9 +368,27 @@ non-important content
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_mismatched_parens(self):
|
def test_mismatched_parens(self):
|
||||||
self.assertAllRaise(SyntaxError, 'f-string: mismatched',
|
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' "
|
||||||
|
r"does not match opening parenthesis '\('",
|
||||||
["f'{((}'",
|
["f'{((}'",
|
||||||
])
|
])
|
||||||
|
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\)' "
|
||||||
|
r"does not match opening parenthesis '\['",
|
||||||
|
["f'{a[4)}'",
|
||||||
|
])
|
||||||
|
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\]' "
|
||||||
|
r"does not match opening parenthesis '\('",
|
||||||
|
["f'{a(4]}'",
|
||||||
|
])
|
||||||
|
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' "
|
||||||
|
r"does not match opening parenthesis '\['",
|
||||||
|
["f'{a[4}'",
|
||||||
|
])
|
||||||
|
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' "
|
||||||
|
r"does not match opening parenthesis '\('",
|
||||||
|
["f'{a(4}'",
|
||||||
|
])
|
||||||
|
self.assertRaises(SyntaxError, eval, "f'{" + "("*500 + "}'")
|
||||||
|
|
||||||
def test_double_braces(self):
|
def test_double_braces(self):
|
||||||
self.assertEqual(f'{{', '{')
|
self.assertEqual(f'{{', '{')
|
||||||
|
@ -448,7 +466,9 @@ non-important content
|
||||||
["f'{1#}'", # error because the expression becomes "(1#)"
|
["f'{1#}'", # error because the expression becomes "(1#)"
|
||||||
"f'{3(#)}'",
|
"f'{3(#)}'",
|
||||||
"f'{#}'",
|
"f'{#}'",
|
||||||
"f'{)#}'", # When wrapped in parens, this becomes
|
])
|
||||||
|
self.assertAllRaise(SyntaxError, r"f-string: unmatched '\)'",
|
||||||
|
["f'{)#}'", # When wrapped in parens, this becomes
|
||||||
# '()#)'. Make sure that doesn't compile.
|
# '()#)'. Make sure that doesn't compile.
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -577,7 +597,7 @@ non-important content
|
||||||
"f'{,}'", # this is (,), which is an error
|
"f'{,}'", # this is (,), which is an error
|
||||||
])
|
])
|
||||||
|
|
||||||
self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
|
self.assertAllRaise(SyntaxError, r"f-string: unmatched '\)'",
|
||||||
["f'{3)+(4}'",
|
["f'{3)+(4}'",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -1003,16 +1023,6 @@ non-important content
|
||||||
self.assertEqual('{d[a]}'.format(d=d), 'string')
|
self.assertEqual('{d[a]}'.format(d=d), 'string')
|
||||||
self.assertEqual('{d[0]}'.format(d=d), 'integer')
|
self.assertEqual('{d[0]}'.format(d=d), 'integer')
|
||||||
|
|
||||||
def test_invalid_expressions(self):
|
|
||||||
self.assertAllRaise(SyntaxError,
|
|
||||||
r"closing parenthesis '\)' does not match "
|
|
||||||
r"opening parenthesis '\[' \(<fstring>, line 1\)",
|
|
||||||
[r"f'{a[4)}'"])
|
|
||||||
self.assertAllRaise(SyntaxError,
|
|
||||||
r"closing parenthesis '\]' does not match "
|
|
||||||
r"opening parenthesis '\(' \(<fstring>, line 1\)",
|
|
||||||
[r"f'{a(4]}'"])
|
|
||||||
|
|
||||||
def test_errors(self):
|
def test_errors(self):
|
||||||
# see issue 26287
|
# see issue 26287
|
||||||
self.assertAllRaise(TypeError, 'unsupported',
|
self.assertAllRaise(TypeError, 'unsupported',
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Improved syntax error messages for unbalanced parentheses in f-string.
|
31
Python/ast.c
31
Python/ast.c
|
@ -13,6 +13,8 @@
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#define MAXLEVEL 200 /* Max parentheses level */
|
||||||
|
|
||||||
static int validate_stmts(asdl_seq *);
|
static int validate_stmts(asdl_seq *);
|
||||||
static int validate_exprs(asdl_seq *, expr_context_ty, int);
|
static int validate_exprs(asdl_seq *, expr_context_ty, int);
|
||||||
static int validate_nonempty_seq(asdl_seq *, const char *, const char *);
|
static int validate_nonempty_seq(asdl_seq *, const char *, const char *);
|
||||||
|
@ -4479,6 +4481,7 @@ fstring_find_expr(const char **str, const char *end, int raw, int recurse_lvl,
|
||||||
/* Keep track of nesting level for braces/parens/brackets in
|
/* Keep track of nesting level for braces/parens/brackets in
|
||||||
expressions. */
|
expressions. */
|
||||||
Py_ssize_t nested_depth = 0;
|
Py_ssize_t nested_depth = 0;
|
||||||
|
char parenstack[MAXLEVEL];
|
||||||
|
|
||||||
/* Can only nest one level deep. */
|
/* Can only nest one level deep. */
|
||||||
if (recurse_lvl >= 2) {
|
if (recurse_lvl >= 2) {
|
||||||
|
@ -4553,10 +4556,12 @@ fstring_find_expr(const char **str, const char *end, int raw, int recurse_lvl,
|
||||||
/* Start looking for the end of the string. */
|
/* Start looking for the end of the string. */
|
||||||
quote_char = ch;
|
quote_char = ch;
|
||||||
} else if (ch == '[' || ch == '{' || ch == '(') {
|
} else if (ch == '[' || ch == '{' || ch == '(') {
|
||||||
|
if (nested_depth >= MAXLEVEL) {
|
||||||
|
ast_error(c, n, "f-string: too many nested parenthesis");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
parenstack[nested_depth] = ch;
|
||||||
nested_depth++;
|
nested_depth++;
|
||||||
} else if (nested_depth != 0 &&
|
|
||||||
(ch == ']' || ch == '}' || ch == ')')) {
|
|
||||||
nested_depth--;
|
|
||||||
} else if (ch == '#') {
|
} else if (ch == '#') {
|
||||||
/* Error: can't include a comment character, inside parens
|
/* Error: can't include a comment character, inside parens
|
||||||
or not. */
|
or not. */
|
||||||
|
@ -4573,6 +4578,23 @@ fstring_find_expr(const char **str, const char *end, int raw, int recurse_lvl,
|
||||||
}
|
}
|
||||||
/* Normal way out of this loop. */
|
/* Normal way out of this loop. */
|
||||||
break;
|
break;
|
||||||
|
} else if (ch == ']' || ch == '}' || ch == ')') {
|
||||||
|
if (!nested_depth) {
|
||||||
|
ast_error(c, n, "f-string: unmatched '%c'", ch);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
nested_depth--;
|
||||||
|
int opening = parenstack[nested_depth];
|
||||||
|
if (!((opening == '(' && ch == ')') ||
|
||||||
|
(opening == '[' && ch == ']') ||
|
||||||
|
(opening == '{' && ch == '}')))
|
||||||
|
{
|
||||||
|
ast_error(c, n,
|
||||||
|
"f-string: closing parenthesis '%c' "
|
||||||
|
"does not match opening parenthesis '%c'",
|
||||||
|
ch, opening);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Just consume this char and loop around. */
|
/* Just consume this char and loop around. */
|
||||||
}
|
}
|
||||||
|
@ -4587,7 +4609,8 @@ fstring_find_expr(const char **str, const char *end, int raw, int recurse_lvl,
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (nested_depth) {
|
if (nested_depth) {
|
||||||
ast_error(c, n, "f-string: mismatched '(', '{', or '['");
|
int opening = parenstack[nested_depth - 1];
|
||||||
|
ast_error(c, n, "f-string: unmatched '%c'", opening);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue