bpo-33306: Improve SyntaxError messages for unbalanced parentheses. (… · python/cpython@94cf308 (original) (raw)
File tree
5 files changed
lines changed
- Misc/NEWS.d/next/Core and Builtins
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; |