bpo-33306: Improve SyntaxError messages for unbalanced parentheses. (… · python/cpython@94cf308 (original) (raw)

File tree

5 files changed

lines changed

5 files changed

lines changed

Original file line number Diff line number Diff line change
@@ -1004,10 +1004,14 @@ def test_str_format_differences(self):
1004 1004 self.assertEqual('{d[0]}'.format(d=d), 'integer')
1005 1005
1006 1006 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 '\[' \(, line 1\)",
1010 + [r"f'{a[4)}'"])
1011 +self.assertAllRaise(SyntaxError,
1012 +r"closing parenthesis '\]' does not match "
1013 +r"opening parenthesis '\(' \(, line 1\)",
1014 + [r"f'{a(4]}'"])
1011 1015
1012 1016 def test_errors(self):
1013 1017 # see issue 26287
Original file line number Diff line number Diff line change
@@ -133,7 +133,7 @@ def make_pth(self, contents, pth_dir='.', pth_name=TESTFN):
133 133
134 134 def test_addpackage_import_bad_syntax(self):
135 135 # 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")
137 137 with captured_stderr() as err_out:
138 138 site.addpackage(pth_dir, pth_fn, set())
139 139 self.assertRegex(err_out.getvalue(), "line 1")
@@ -143,7 +143,7 @@ def test_addpackage_import_bad_syntax(self):
143 143 # order doesn't matter. The next three could be a single check
144 144 # but my regex foo isn't good enough to write it.
145 145 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')
147 147 self.assertRegex(err_out.getvalue(), 'SyntaxError')
148 148
149 149 def test_addpackage_import_bad_exec(self):
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1 +Improved syntax error messages for unbalanced parentheses.
Original file line number Diff line number Diff line change
@@ -1842,12 +1842,44 @@ tok_get(struct tok_state *tok, char **p_start, char **p_end)
1842 1842 case '(':
1843 1843 case '[':
1844 1844 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
1845 1852 tok->level++;
1846 1853 break;
1847 1854 case ')':
1848 1855 case ']':
1849 1856 case '}':
1857 +#ifndef PGEN
1858 +if (!tok->level) {
1859 +return syntaxerror(tok, "unmatched '%c'", c);
1860 + }
1861 +#endif
1850 1862 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
1851 1883 break;
1852 1884 }
1853 1885
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ extern "C" {
11 11 #include "token.h" /* For token types */
12 12
13 13 #define MAXINDENT 100 /* Max indentation level */
14 +#define MAXLEVEL 200 /* Max parentheses level */
14 15
15 16 enum decoding_state {
16 17 STATE_INIT,
@@ -39,14 +40,16 @@ struct tok_state {
39 40 int lineno; /* Current line number */
40 41 int level; /* () [] {} Parentheses nesting level */
41 42 /* Used to allow free continuations inside them */
42 -/* Stuff for checking on different tab sizes */
43 43 #ifndef PGEN
44 +char parenstack[MAXLEVEL];
45 +int parenlinenostack[MAXLEVEL];
44 46 /* pgen doesn't have access to Python codecs, it cannot decode the input
45 47 filename. The bytes filename might be kept, but it is only used by
46 48 indenterror() and it is not really needed: pgen only compiles one file
47 49 (Grammar/Grammar). */
48 50 PyObject *filename;
49 51 #endif
52 +/* Stuff for checking on different tab sizes */
50 53 int altindstack[MAXINDENT]; /* Stack of alternate indents */
51 54 /* Stuff for PEP 0263 */
52 55 enum decoding_state decoding_state;