summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/test_fstring.py36
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2018-12-14-18-02-34.bpo-35494.IWOPtb.rst1
-rw-r--r--Python/ast.c31
3 files changed, 51 insertions, 17 deletions
diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py
index fe3804b..9e45770 100644
--- a/Lib/test/test_fstring.py
+++ b/Lib/test/test_fstring.py
@@ -368,9 +368,27 @@ non-important content
])
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'{((}'",
])
+ 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):
self.assertEqual(f'{{', '{')
@@ -448,7 +466,9 @@ non-important content
["f'{1#}'", # error because the expression becomes "(1#)"
"f'{3(#)}'",
"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.
])
@@ -577,7 +597,7 @@ non-important content
"f'{,}'", # this is (,), which is an error
])
- self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
+ self.assertAllRaise(SyntaxError, r"f-string: unmatched '\)'",
["f'{3)+(4}'",
])
@@ -1003,16 +1023,6 @@ non-important content
self.assertEqual('{d[a]}'.format(d=d), 'string')
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):
# see issue 26287
self.assertAllRaise(TypeError, 'unsupported',
diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-12-14-18-02-34.bpo-35494.IWOPtb.rst b/Misc/NEWS.d/next/Core and Builtins/2018-12-14-18-02-34.bpo-35494.IWOPtb.rst
new file mode 100644
index 0000000..0813b35
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2018-12-14-18-02-34.bpo-35494.IWOPtb.rst
@@ -0,0 +1 @@
+Improved syntax error messages for unbalanced parentheses in f-string.
diff --git a/Python/ast.c b/Python/ast.c
index 8a305a8..69dfe3c 100644
--- a/Python/ast.c
+++ b/Python/ast.c
@@ -13,6 +13,8 @@
#include <assert.h>
#include <stdbool.h>
+#define MAXLEVEL 200 /* Max parentheses level */
+
static int validate_stmts(asdl_seq *);
static int validate_exprs(asdl_seq *, expr_context_ty, int);
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
expressions. */
Py_ssize_t nested_depth = 0;
+ char parenstack[MAXLEVEL];
/* Can only nest one level deep. */
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. */
quote_char = 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++;
- } else if (nested_depth != 0 &&
- (ch == ']' || ch == '}' || ch == ')')) {
- nested_depth--;
} else if (ch == '#') {
/* Error: can't include a comment character, inside parens
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. */
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 {
/* 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;
}
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;
}