Repository: PyCQA/pyflakes
Pull request #329 (2018-03-13 05:15:19)

checker.py: Check for invalid print syntax
New warning added in messages.py to check for invalid
python syntax

Closes https://github.com/PyCQA/pyflakes/issues/242

myint: I see a false positive on Python 3:

`foo.py`:
```python
def main():
    log = print
    log('hello')
```

```
$ python -m pyflakes foo.py
foo.py:2: incompatible use of print operator
```
MacBox7: TypeErrors are detected on execution. What we can do is list out all possible usecase where print won't throw an error and report all others. Could there be a more general approach to do this?

For eg: 
We can whitelist print if its parent is ast.Call or ast.Assign and report all other instance.
MacBox7: `Hacking` and `flake8_print` also shows false positive. So, either we can flag every occurent where print is not used as a function or we can stick to the approach that I have updated.
MacBox7: @sigmavirus24 i understand and hence took a different approach which takes into consideration the false positives.
myint: The latest commit addresses my concern based on my local testing. But it would be good to have test cases to verify this. Thanks!
MacBox7: @myint, as I said detecting TypeError with static analysis, is a challenge. We can rely on this heuristic approach to solve the problem. Plus, I don't think of any other case where it might fail. I have already added a test case for this. Do you want me to add more?
MacBox7: @sigmavirus24 added some more test cases and indented the code as you requested :smile: 
myint: I don't think I mentioned any `TypeError`. But the latest test fulfills want I was thinking of. Thanks.
MacBox7: The use case that was mentioned in the issue, was a type error when compiled using compiler.
asmeurer: If the goal is to catch the case specified in the issue, why not check for it exactly? I think print as the left operand of an lshift is the only way a Python 2 `print` can be valid in Python 3. 

Also, this should check if `print` has been redefined and disable the test if it has. 

Patch:
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index 2ce6250..c2f8d2e 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -768,6 +768,13 @@ class Checker(object):
                     # iteration
                     continue
 
+            if (name == 'print' and
+                    isinstance(scope.get(name, None), Builtin)):
+                parent = self.getParent(node)
+                if (isinstance(parent, ast.BinOp) and
+                        isinstance(parent.op, ast.RShift)):
+                    self.report(messages.InvalidPrintSyntax, node)
+
             try:
                 scope[name].used = (self.scope, node)
             except KeyError:
diff --git a/pyflakes/messages.py b/pyflakes/messages.py
index bd70693..4756872 100644
--- a/pyflakes/messages.py
+++ b/pyflakes/messages.py
@@ -250,3 +250,7 @@ class ForwardAnnotationSyntaxError(Message):
 
 class RaiseNotImplemented(Message):
     message = "'raise NotImplemented' should be 'raise NotImplementedError'"
+
+
+class InvalidPrintSyntax(Message):
+    message = 'use of >> is invalid with print function'


Test patch:
diff --git a/pyflakes/test/test_other.py b/pyflakes/test/test_other.py
index fa093ce..2d6bdac 100644
--- a/pyflakes/test/test_other.py
+++ b/pyflakes/test/test_other.py
@@ -2034,3 +2034,53 @@ class TestAsyncStatements(TestCase):
         self.flakes('''
         raise NotImplemented
         ''', m.RaiseNotImplemented)
+
+
+class TestIncompatiblePrintOperator(TestCase):
+    """
+    Tests for warning about invalid use of print function.
+    """
+
+    def test_valid_print(self):
+        self.flakes('''
+        print("Hello")
+        ''')
+
+    def test_invalid_print_when_imported_from_future(self):
+        exc = self.flakes('''
+        from __future__ import print_function
+        import sys
+        print >>sys.stderr, "Hello"
+        ''', m.InvalidPrintSyntax).messages[0]
+
+        self.assertEqual(exc.lineno, 4)
+        self.assertEqual(exc.col, 0)
+
+    def test_print_function_assignment(self):
+        """
+        A valid assignment, tested for catching false positives.
+        """
+        self.flakes('''
+        from __future__ import print_function
+        log = print
+        log("Hello")
+        ''')
+
+    def test_print_in_lambda(self):
+        self.flakes('''
+        from __future__ import print_function
+        a = lambda: print
+        ''')
+
+    def test_print_returned_in_function(self):
+        self.flakes('''
+        from __future__ import print_function
+        def a():
+            return print
+        ''')
+
+    def test_print_as_condition_test(self):
+        self.flakes('''
+        from __future__ import print_function
+        if print: pass
+        ''')