])
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'{{', '{')
["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.
])
"f'{,}'", # this is (,), which is an error
])
- self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
+ self.assertAllRaise(SyntaxError, r"f-string: unmatched '\)'",
["f'{3)+(4}'",
])
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',
#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 *);
/* 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) {
/* 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. */
}
/* 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. */
}
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;
}