Skip to content

Commit 94cf308

Browse files
bpo-33306: Improve SyntaxError messages for unbalanced parentheses. (GH-6516)
1 parent bdabb07 commit 94cf308

5 files changed

Lines changed: 47 additions & 7 deletions

File tree

Lib/test/test_fstring.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,10 +1004,14 @@ def test_str_format_differences(self):
10041004
self.assertEqual('{d[0]}'.format(d=d), 'integer')
10051005

10061006
def test_invalid_expressions(self):
1007-
self.assertAllRaise(SyntaxError, 'invalid syntax',
1008-
[r"f'{a[4)}'",
1009-
r"f'{a(4]}'",
1010-
])
1007+
self.assertAllRaise(SyntaxError,
1008+
r"closing parenthesis '\)' does not match "
1009+
r"opening parenthesis '\[' \(<fstring>, line 1\)",
1010+
[r"f'{a[4)}'"])
1011+
self.assertAllRaise(SyntaxError,
1012+
r"closing parenthesis '\]' does not match "
1013+
r"opening parenthesis '\(' \(<fstring>, line 1\)",
1014+
[r"f'{a(4]}'"])
10111015

10121016
def test_errors(self):
10131017
# see issue 26287

Lib/test/test_site.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def make_pth(self, contents, pth_dir='.', pth_name=TESTFN):
133133

134134
def test_addpackage_import_bad_syntax(self):
135135
# Issue 10642
136-
pth_dir, pth_fn = self.make_pth("import bad)syntax\n")
136+
pth_dir, pth_fn = self.make_pth("import bad-syntax\n")
137137
with captured_stderr() as err_out:
138138
site.addpackage(pth_dir, pth_fn, set())
139139
self.assertRegex(err_out.getvalue(), "line 1")
@@ -143,7 +143,7 @@ def test_addpackage_import_bad_syntax(self):
143143
# order doesn't matter. The next three could be a single check
144144
# but my regex foo isn't good enough to write it.
145145
self.assertRegex(err_out.getvalue(), 'Traceback')
146-
self.assertRegex(err_out.getvalue(), r'import bad\)syntax')
146+
self.assertRegex(err_out.getvalue(), r'import bad-syntax')
147147
self.assertRegex(err_out.getvalue(), 'SyntaxError')
148148

149149
def test_addpackage_import_bad_exec(self):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improved syntax error messages for unbalanced parentheses.

Parser/tokenizer.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1842,12 +1842,44 @@ tok_get(struct tok_state *tok, char **p_start, char **p_end)
18421842
case '(':
18431843
case '[':
18441844
case '{':
1845+
#ifndef PGEN
1846+
if (tok->level >= MAXLEVEL) {
1847+
return syntaxerror(tok, "too many nested parentheses");
1848+
}
1849+
tok->parenstack[tok->level] = c;
1850+
tok->parenlinenostack[tok->level] = tok->lineno;
1851+
#endif
18451852
tok->level++;
18461853
break;
18471854
case ')':
18481855
case ']':
18491856
case '}':
1857+
#ifndef PGEN
1858+
if (!tok->level) {
1859+
return syntaxerror(tok, "unmatched '%c'", c);
1860+
}
1861+
#endif
18501862
tok->level--;
1863+
#ifndef PGEN
1864+
int opening = tok->parenstack[tok->level];
1865+
if (!((opening == '(' && c == ')') ||
1866+
(opening == '[' && c == ']') ||
1867+
(opening == '{' && c == '}')))
1868+
{
1869+
if (tok->parenlinenostack[tok->level] != tok->lineno) {
1870+
return syntaxerror(tok,
1871+
"closing parenthesis '%c' does not match "
1872+
"opening parenthesis '%c' on line %d",
1873+
c, opening, tok->parenlinenostack[tok->level]);
1874+
}
1875+
else {
1876+
return syntaxerror(tok,
1877+
"closing parenthesis '%c' does not match "
1878+
"opening parenthesis '%c'",
1879+
c, opening);
1880+
}
1881+
}
1882+
#endif
18511883
break;
18521884
}
18531885

Parser/tokenizer.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ extern "C" {
1111
#include "token.h" /* For token types */
1212

1313
#define MAXINDENT 100 /* Max indentation level */
14+
#define MAXLEVEL 200 /* Max parentheses level */
1415

1516
enum decoding_state {
1617
STATE_INIT,
@@ -39,14 +40,16 @@ struct tok_state {
3940
int lineno; /* Current line number */
4041
int level; /* () [] {} Parentheses nesting level */
4142
/* Used to allow free continuations inside them */
42-
/* Stuff for checking on different tab sizes */
4343
#ifndef PGEN
44+
char parenstack[MAXLEVEL];
45+
int parenlinenostack[MAXLEVEL];
4446
/* pgen doesn't have access to Python codecs, it cannot decode the input
4547
filename. The bytes filename might be kept, but it is only used by
4648
indenterror() and it is not really needed: pgen only compiles one file
4749
(Grammar/Grammar). */
4850
PyObject *filename;
4951
#endif
52+
/* Stuff for checking on different tab sizes */
5053
int altindstack[MAXINDENT]; /* Stack of alternate indents */
5154
/* Stuff for PEP 0263 */
5255
enum decoding_state decoding_state;

0 commit comments

Comments
 (0)