bpo-33306: Improve syntax error messages for unbalanced parentheses. · python/cpython@3763075

File tree

5 files changed

lines changed

  • Misc/NEWS.d/next/Core and Builtins

5 files changed

lines changed

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

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):

Original file line numberDiff line numberDiff line change

@@ -0,0 +1 @@

1+

Improved syntax error messages for unbalanced parentheses.

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 parenthesis");

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
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;