[pypy-commit] pypy py3.5: "print x" now raises a nice SyntaxError("Missing parentheses in call to 'print'")

amauryfa pypy.commits at gmail.com
Sun Oct 9 10:50:20 EDT 2016


Author: Amaury Forgeot d'Arc <amauryfa at gmail.com>
Branch: py3.5
Changeset: r87663:eacce032f81c
Date: 2016-10-01 22:29 +0200
http://bitbucket.org/pypy/pypy/changeset/eacce032f81c/

Log:	"print x" now raises a nice SyntaxError("Missing parentheses in call
	to 'print'")

diff --git a/pypy/module/exceptions/interp_exceptions.py b/pypy/module/exceptions/interp_exceptions.py
--- a/pypy/module/exceptions/interp_exceptions.py
+++ b/pypy/module/exceptions/interp_exceptions.py
@@ -728,6 +728,8 @@
                 args_w = args_w[:]
                 args_w[1] = space.newtuple(values_w[:4])
         W_BaseException.descr_init(self, space, args_w)
+        if self.w_text and space.isinstance_w(self.w_text, space.w_unicode):
+            self._report_missing_parentheses(space)
 
     def descr_str(self, space):
         return space.appexec([self], """(self):
@@ -770,6 +772,42 @@
         else:
             return W_Exception.descr_repr(self, space)
 
+    # CPython Issue #21669: Custom error for 'print' & 'exec' as statements
+    def _report_missing_parentheses(self, space):
+        text = space.unicode_w(self.w_text)
+        if u'(' in text:
+            # Use default error message for any line with an opening paren
+            return
+        # handle the simple statement case
+        if self._check_for_legacy_statements(space, text, 0):
+            return
+        # Handle the one-line complex statement case
+        pos = text.find(u':')
+        if pos < 0:
+            return
+        # Check again, starting from just after the colon
+        self._check_for_legacy_statements(space, text, pos+1)
+
+    def _check_for_legacy_statements(self, space, text, start):
+        # Ignore leading whitespace
+        while start < len(text) and text[start] == u' ':
+            start += 1
+        # Checking against an empty or whitespace-only part of the string
+        if start == len(text):
+            return False
+        if start > 0:
+            text = text[start:]
+        # Check for legacy print statements
+        if text.startswith(u"print "):
+            self.w_msg = space.wrap("Missing parentheses in call to 'print'")
+            return True
+        # Check for legacy exec statements
+        if text.startswith(u"exec "):
+            self.w_msg = space.wrap("Missing parentheses in call to 'exec'")
+            return True
+        return False
+
+
 W_SyntaxError.typedef = TypeDef(
     'SyntaxError',
     W_Exception.typedef,
diff --git a/pypy/module/exceptions/test/test_exc.py b/pypy/module/exceptions/test/test_exc.py
--- a/pypy/module/exceptions/test/test_exc.py
+++ b/pypy/module/exceptions/test/test_exc.py
@@ -375,3 +375,27 @@
         assert e.baz == "baz"
         assert e.args == ("some message",)
 
+    # Check the heuristic for print & exec covers significant cases
+    # As well as placing some limits on false positives
+    def test_former_statements_refer_to_builtins(self):
+        keywords = "print", "exec"
+        def exec_(s): exec(s)
+        # Cases where we want the custom error
+        cases = [
+            "{} foo",
+            "{} {{1:foo}}",
+            "if 1: {} foo",
+            "if 1: {} {{1:foo}}",
+            "if 1:\n    {} foo",
+            "if 1:\n    {} {{1:foo}}",
+        ]
+        for keyword in keywords:
+            custom_msg = "call to '{}'".format(keyword)
+            for case in cases:
+                source = case.format(keyword)
+                exc = raises(SyntaxError, exec_, source)
+                assert custom_msg in exc.value.msg
+                assert exc.value.args[0] == 'invalid syntax'
+                source = source.replace("foo", "(foo.)")
+                exc = raises(SyntaxError, exec_, source)
+                assert custom_msg not in exc.value.msg


More information about the pypy-commit mailing list