Repository: PyCQA/pyflakes
Pull request #433 (2019-02-21 06:41:52)

Add another test for #422
#422

asottile: given there's already a test which covers this regression -- is there benefit for two?
untitaker: idk if this goes through a different codepath or not, feel free to close
asottile: this is definitely a simpler case of the one I tried to isolate -- in the end it's the same code path though
untitaker: @asottile do you want me to replace your test or close this?
asottile: replace is probably good 👍 

Patch:
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index 4c88af2..0e636c1 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -530,14 +530,21 @@ def getNodeName(node):
         return node.name
 
 
-def is_typing_overload(value, scope):
+def is_typing_overload(value, scope_stack):
+    def name_is_typing_overload(name):  # type: (str) -> bool
+        for scope in reversed(scope_stack):
+            if name in scope:
+                return (
+                    isinstance(scope[name], ImportationFrom) and
+                    scope[name].fullName == 'typing.overload'
+                )
+        else:
+            return False
+
     def is_typing_overload_decorator(node):
         return (
             (
-                isinstance(node, ast.Name) and
-                node.id in scope and
-                isinstance(scope[node.id], ImportationFrom) and
-                scope[node.id].fullName == 'typing.overload'
+                isinstance(node, ast.Name) and name_is_typing_overload(node.id)
             ) or (
                 isinstance(node, ast.Attribute) and
                 isinstance(node.value, ast.Name) and
@@ -548,8 +555,10 @@ def is_typing_overload(value, scope):
 
     return (
         isinstance(value.source, ast.FunctionDef) and
-        len(value.source.decorator_list) == 1 and
-        is_typing_overload_decorator(value.source.decorator_list[0])
+        any(
+            is_typing_overload_decorator(dec)
+            for dec in value.source.decorator_list
+        )
     )
 
 
@@ -888,7 +897,7 @@ class Checker(object):
                                 node, value.name, existing.source)
                 elif not existing.used and value.redefines(existing):
                     if value.name != '_' or isinstance(existing, Importation):
-                        if not is_typing_overload(existing, self.scope):
+                        if not is_typing_overload(existing, self.scopeStack):
                             self.report(messages.RedefinedWhileUnused,
                                         node, value.name, existing.source)
 


Test patch:
diff --git a/pyflakes/test/test_type_annotations.py b/pyflakes/test/test_type_annotations.py
index 48635bb..03f70bb 100644
--- a/pyflakes/test/test_type_annotations.py
+++ b/pyflakes/test/test_type_annotations.py
@@ -39,27 +39,54 @@ class TestTypeAnnotations(TestCase):
             return s
         """)
 
-    def test_not_a_typing_overload(self):
-        """regression test for @typing.overload detection bug in 2.1.0"""
+    def test_overload_with_multiple_decorators(self):
+        self.flakes("""
+            from typing import overload
+            dec = lambda f: f
+
+            @dec
+            @overload
+            def f(x):  # type: (int) -> int
+                pass
+
+            @dec
+            @overload
+            def f(x):  # type: (str) -> str
+                pass
+
+            @dec
+            def f(x): return x
+       """)
+
+    def test_overload_in_class(self):
         self.flakes("""
-            x = lambda f: f
+        from typing import overload
 
-            @x
-            def t():
+        class C:
+            @overload
+            def f(self, x):  # type: (int) -> int
                 pass
 
-            y = lambda f: f
+            @overload
+            def f(self, x):  # type: (str) -> str
+                pass
+
+            def f(self, x): return x
+        """)
+
+    def test_not_a_typing_overload(self):
+        """regression test for @typing.overload detection bug in 2.1.0"""
+        self.flakes("""
+            def foo(x):
+                return x
 
-            @x
-            @y
-            def t():
+            @foo
+            def bar():
                 pass
 
-            @x
-            @y
-            def t():
+            def bar():
                 pass
-        """, m.RedefinedWhileUnused, m.RedefinedWhileUnused)
+        """, m.RedefinedWhileUnused)
 
     @skipIf(version_info < (3, 6), 'new in Python 3.6')
     def test_variable_annotations(self):