Repository: python/mypy

Issue:
Fast parser does not produce warnings about tuple argument names
Python 2 tuple arguments may have names which conflict with other arguments.  These are not caught with the fast parser.
For example, this function produces no errors:
```
def f(x, (x, y)):
  # type: (...) -> None
  pass
```


Discussion:
Should a fix to this catch the more general:

```python
def f(x, x):
  pass
```
?
This is currently accepted by both parsers without error.

Resolving patch:
diff --git a/mypy/fastparse.py b/mypy/fastparse.py
--- a/mypy/fastparse.py
+++ b/mypy/fastparse.py
@@ -1,7 +1,7 @@
 from functools import wraps
 import sys
 
-from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, cast, List
+from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, cast, List, Set
 from mypy.sharedparse import special_function_elide_names, argument_elide_name
 from mypy.nodes import (
     MypyFile, Node, ImportBase, Import, ImportAll, ImportFrom, FuncDef, OverloadedFuncDef,
@@ -394,18 +394,22 @@ def make_argument(arg: ast35.arg, default: Optional[ast35.expr], kind: int) -> A
             return Argument(Var(arg.arg), arg_type, self.visit(default), kind)
 
         new_args = []
+        names = []  # type: List[ast35.arg]
         num_no_defaults = len(args.args) - len(args.defaults)
         # positional arguments without defaults
         for a in args.args[:num_no_defaults]:
             new_args.append(make_argument(a, None, ARG_POS))
+            names.append(a)
 
         # positional arguments with defaults
         for a, d in zip(args.args[num_no_defaults:], args.defaults):
             new_args.append(make_argument(a, d, ARG_OPT))
+            names.append(a)
 
         # *arg
         if args.vararg is not None:
             new_args.append(make_argument(args.vararg, None, ARG_STAR))
+            names.append(args.vararg)
 
         # keyword-only arguments with defaults
         for a, d in zip(args.kwonlyargs, args.kw_defaults):
@@ -413,10 +417,20 @@ def make_argument(arg: ast35.arg, default: Optional[ast35.expr], kind: int) -> A
                 a,
                 d,
                 ARG_NAMED if d is None else ARG_NAMED_OPT))
+            names.append(a)
 
         # **kwarg
         if args.kwarg is not None:
             new_args.append(make_argument(args.kwarg, None, ARG_STAR2))
+            names.append(args.kwarg)
+
+        seen_names = set()  # type: Set[str]
+        for name in names:
+            if name.arg in seen_names:
+                self.fail("duplicate argument '{}' in function definition".format(name.arg),
+                          name.lineno, name.col_offset)
+                break
+            seen_names.add(name.arg)
 
         return new_args
 
diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py
--- a/mypy/fastparse2.py
+++ b/mypy/fastparse2.py
@@ -17,7 +17,7 @@
 from functools import wraps
 import sys
 
-from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, cast, List
+from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, cast, List, Set
 from mypy.sharedparse import special_function_elide_names, argument_elide_name
 from mypy.nodes import (
     MypyFile, Node, ImportBase, Import, ImportAll, ImportFrom, FuncDef, OverloadedFuncDef,
@@ -378,6 +378,14 @@ def transform_args(self,
         converter = TypeConverter(self.errors, line=line)
         decompose_stmts = []  # type: List[Statement]
 
+        def extract_names(arg: ast27.expr) -> List[str]:
+            if isinstance(arg, ast27.Name):
+                return [arg.id]
+            elif isinstance(arg, ast27.Tuple):
+                return [name for elt in arg.elts for name in extract_names(elt)]
+            else:
+                return []
+
         def convert_arg(index: int, arg: ast27.expr) -> Var:
             if isinstance(arg, ast27.Name):
                 v = arg.id
@@ -399,6 +407,7 @@ def get_type(i: int) -> Optional[Type]:
 
         args = [(convert_arg(i, arg), get_type(i)) for i, arg in enumerate(n.args)]
         defaults = self.translate_expr_list(n.defaults)
+        names = [name for arg in n.args for name in extract_names(arg)]  # type: List[str]
 
         new_args = []  # type: List[Argument]
         num_no_defaults = len(args) - len(defaults)
@@ -413,11 +422,20 @@ def get_type(i: int) -> Optional[Type]:
         # *arg
         if n.vararg is not None:
             new_args.append(Argument(Var(n.vararg), get_type(len(args)), None, ARG_STAR))
+            names.append(n.vararg)
 
         # **kwarg
         if n.kwarg is not None:
             typ = get_type(len(args) + (0 if n.vararg is None else 1))
             new_args.append(Argument(Var(n.kwarg), typ, None, ARG_STAR2))
+            names.append(n.kwarg)
+
+        seen_names = set()  # type: Set[str]
+        for name in names:
+            if name in seen_names:
+                self.fail("duplicate argument '{}' in function definition".format(name), line, 0)
+                break
+            seen_names.add(name)
 
         return new_args, decompose_stmts
 


Test patch:
diff --git a/test-data/unit/check-fastparse.test b/test-data/unit/check-fastparse.test
--- a/test-data/unit/check-fastparse.test
+++ b/test-data/unit/check-fastparse.test
@@ -249,3 +249,53 @@ def f(x: int, y: int, z: int):
 def f(x: int):  # E: Function has duplicate type signatures
   # type: (int) -> int
   pass
+
+[case testFastParserDuplicateNames]
+# flags: --fast-parser
+def f(x, y, z):
+  pass
+
+def g(x, y, x):  # E: duplicate argument 'x' in function definition
+  pass
+
+def h(x, y, *x):  # E: duplicate argument 'x' in function definition
+  pass
+
+def i(x, y, *z, **z):  # E: duplicate argument 'z' in function definition
+  pass
+
+def j(x: int, y: int, *, x: int = 3):  # E: duplicate argument 'x' in function definition
+  pass
+
+def k(*, y, z, y):  # E: duplicate argument 'y' in function definition
+  pass
+
+lambda x, y, x: ...  # E: duplicate argument 'x' in function definition
+
+[case testFastParserDuplicateNames_python2]
+# flags: --fast-parser
+def f(x, y, z):
+  pass
+
+def g(x, y, x):  # E: duplicate argument 'x' in function definition
+  pass
+
+def h(x, y, *x):  # E: duplicate argument 'x' in function definition
+  pass
+
+def i(x, y, *z, **z):  # E: duplicate argument 'z' in function definition
+  pass
+
+def j(x, (y, y), z):  # E: duplicate argument 'y' in function definition
+  pass
+
+def k(x, (y, x)):  # E: duplicate argument 'x' in function definition
+  pass
+
+def l((x, y), (z, x)):  # E: duplicate argument 'x' in function definition
+  pass
+
+def m(x, ((x, y), z)):  # E: duplicate argument 'x' in function definition
+  pass
+
+lambda x, (y, x): None  # E: duplicate argument 'x' in function definition