]> granicus.if.org Git - python/commitdiff
bpo-33306: Improve SyntaxError messages for unbalanced parentheses. (GH-6516)
authorSerhiy Storchaka <storchaka@gmail.com>
Mon, 17 Dec 2018 15:34:14 +0000 (17:34 +0200)
committerGitHub <noreply@github.com>
Mon, 17 Dec 2018 15:34:14 +0000 (17:34 +0200)
Lib/test/test_fstring.py
Lib/test/test_site.py
Misc/NEWS.d/next/Core and Builtins/2018-04-18-12-23-30.bpo-33306.tSM3cp.rst [new file with mode: 0644]
Parser/tokenizer.c
Parser/tokenizer.h

index 09b5ae1fdaee642e5e3b38d54004c03bc29972be..fe3804b2215d5791cd11aefa179816af0ec04286 100644 (file)
@@ -1004,10 +1004,14 @@ non-important content
         self.assertEqual('{d[0]}'.format(d=d), 'integer')
 
     def test_invalid_expressions(self):
-        self.assertAllRaise(SyntaxError, 'invalid syntax',
-                            [r"f'{a[4)}'",
-                             r"f'{a(4]}'",
-                            ])
+        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
index f38e8d853adabd65a660a25ed0e90040cc911e79..735651ec7d75503acc1e3772cb5bf506237f9de0 100644 (file)
@@ -133,7 +133,7 @@ class HelperFunctionsTests(unittest.TestCase):
 
     def test_addpackage_import_bad_syntax(self):
         # Issue 10642
-        pth_dir, pth_fn = self.make_pth("import bad)syntax\n")
+        pth_dir, pth_fn = self.make_pth("import bad-syntax\n")
         with captured_stderr() as err_out:
             site.addpackage(pth_dir, pth_fn, set())
         self.assertRegex(err_out.getvalue(), "line 1")
@@ -143,7 +143,7 @@ class HelperFunctionsTests(unittest.TestCase):
         # order doesn't matter.  The next three could be a single check
         # but my regex foo isn't good enough to write it.
         self.assertRegex(err_out.getvalue(), 'Traceback')
-        self.assertRegex(err_out.getvalue(), r'import bad\)syntax')
+        self.assertRegex(err_out.getvalue(), r'import bad-syntax')
         self.assertRegex(err_out.getvalue(), 'SyntaxError')
 
     def test_addpackage_import_bad_exec(self):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-04-18-12-23-30.bpo-33306.tSM3cp.rst b/Misc/NEWS.d/next/Core and Builtins/2018-04-18-12-23-30.bpo-33306.tSM3cp.rst
new file mode 100644 (file)
index 0000000..2d89106
--- /dev/null
@@ -0,0 +1 @@
+Improved syntax error messages for unbalanced parentheses.
index d319a4c90a9e394ec0e226fa85fb5602342dad88..c246ee204c5d7ecd316e0ced4aa37bec90739765 100644 (file)
@@ -1842,12 +1842,44 @@ tok_get(struct tok_state *tok, char **p_start, char **p_end)
     case '(':
     case '[':
     case '{':
+#ifndef PGEN
+        if (tok->level >= MAXLEVEL) {
+            return syntaxerror(tok, "too many nested parentheses");
+        }
+        tok->parenstack[tok->level] = c;
+        tok->parenlinenostack[tok->level] = tok->lineno;
+#endif
         tok->level++;
         break;
     case ')':
     case ']':
     case '}':
+#ifndef PGEN
+        if (!tok->level) {
+            return syntaxerror(tok, "unmatched '%c'", c);
+        }
+#endif
         tok->level--;
+#ifndef PGEN
+        int opening = tok->parenstack[tok->level];
+        if (!((opening == '(' && c == ')') ||
+              (opening == '[' && c == ']') ||
+              (opening == '{' && c == '}')))
+        {
+            if (tok->parenlinenostack[tok->level] != tok->lineno) {
+                return syntaxerror(tok,
+                        "closing parenthesis '%c' does not match "
+                        "opening parenthesis '%c' on line %d",
+                        c, opening, tok->parenlinenostack[tok->level]);
+            }
+            else {
+                return syntaxerror(tok,
+                        "closing parenthesis '%c' does not match "
+                        "opening parenthesis '%c'",
+                        c, opening);
+            }
+        }
+#endif
         break;
     }
 
index 2e31d8624da7ca67f29f053f413dac78e66028eb..cd18d25dc192eaa6ae3f157f7c04a7b8174cf00a 100644 (file)
@@ -11,6 +11,7 @@ extern "C" {
 #include "token.h"      /* For token types */
 
 #define MAXINDENT 100   /* Max indentation level */
+#define MAXLEVEL 200    /* Max parentheses level */
 
 enum decoding_state {
     STATE_INIT,
@@ -39,14 +40,16 @@ struct tok_state {
     int lineno;         /* Current line number */
     int level;          /* () [] {} Parentheses nesting level */
             /* Used to allow free continuations inside them */
-    /* Stuff for checking on different tab sizes */
 #ifndef PGEN
+    char parenstack[MAXLEVEL];
+    int parenlinenostack[MAXLEVEL];
     /* pgen doesn't have access to Python codecs, it cannot decode the input
        filename. The bytes filename might be kept, but it is only used by
        indenterror() and it is not really needed: pgen only compiles one file
        (Grammar/Grammar). */
     PyObject *filename;
 #endif
+    /* Stuff for checking on different tab sizes */
     int altindstack[MAXINDENT];         /* Stack of alternate indents */
     /* Stuff for PEP 0263 */
     enum decoding_state decoding_state;