Repository: python/mypy

Issue:
Sometimes it's better to display argument name instead of number
The current message for invalid argument type is sometimes not very helpful. Example:

```py
def f(a: int, b: int, c: int, d: int, e: str) -> None: pass

f(c=6, b=3, d=2, e=6, a=6)  # Error
```

This is the current message:

```
t.py:2: error: Argument 4 to "f" has incompatible type "int"; expected "str"
```

I think that including the argument name (but only if the caller used a keyword argument) would be more helpful when there are many arguments:

```
t.py:2: error: Argument "e" to "f" has incompatible type "int"; expected "str"
```

The current message is a bit problematic when a function takes many arguments. It's not rare to see functions that take 8+ arguments, and then you'll need to count the arguments pretty carefully to find the correct one.

Including the correct line/column would also help, but it's a separate issue.


Discussion:
I'm interested in these sorts of issues because they are important without requiring too much background.
Can you add the topic-usability label to this and issues of this sort?
I'm listing some other places where we could use names and not numbers for identifying arguments, but I think they are less important than the above example.

We could also display the name and not the number in generic inference failure: https://github.com/python/mypy/blob/5c9181b137dcc9c6177aa58961d0426a7a78b4dc/test-data/unit/check-inference.test#L505-L512

With unfollowed imports, we could display the variable name here as well, and for args and kwargs because it is a function definition, not a call.
https://github.com/python/mypy/blob/e1c569cf46c11d104a7dfa61331a9d71b4c94fcd/test-data/unit/check-flags.test#L586-L589 
and also when subclassing from a generic supertype: https://github.com/python/mypy/blob/3171c05014ea6e40ce0457e5a3d12c67c0fbe2d7/test-data/unit/check-generic-subtyping.test#L173-L175
@elliott-beach Good ideas! I agree that usability issues are good ones for new contributors.

Resolving patch:
diff --git a/mypy/messages.py b/mypy/messages.py
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -25,7 +25,8 @@
 from mypy.nodes import (
     TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases,
     ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2,
-    ReturnStmt, NameExpr, Var, CONTRAVARIANT, COVARIANT, SymbolNode
+    ReturnStmt, NameExpr, Var, CONTRAVARIANT, COVARIANT, SymbolNode,
+    CallExpr
 )
 
 
@@ -634,8 +635,17 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type:
                 arg_type_str = '*' + arg_type_str
             elif arg_kind == ARG_STAR2:
                 arg_type_str = '**' + arg_type_str
+
+            # For function calls with keyword arguments, display the argument name rather than the
+            # number.
+            arg_label = str(n)
+            if isinstance(context, CallExpr):
+                arg_name = context.arg_names[n - 1]
+                if arg_name is not None:
+                    arg_label = '"{}"'.format(arg_name)
+
             msg = 'Argument {} {}has incompatible type {}; expected {}'.format(
-                n, target, self.quote_type_string(arg_type_str),
+                arg_label, target, self.quote_type_string(arg_type_str),
                 self.quote_type_string(expected_type_str))
             if isinstance(arg_type, Instance) and isinstance(expected_type, Instance):
                 notes = append_invariance_notes(notes, arg_type, expected_type)


Test patch:
diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test
--- a/test-data/unit/check-class-namedtuple.test
+++ b/test-data/unit/check-class-namedtuple.test
@@ -114,7 +114,7 @@ class N(NamedTuple):
     b: str
 
 n = N('x', 'x') # E: Argument 1 to "N" has incompatible type "str"; expected "int"
-n = N(1, b=2)   # E: Argument 2 to "N" has incompatible type "int"; expected "str"
+n = N(1, b=2)   # E: Argument "b" to "N" has incompatible type "int"; expected "str"
 N(1, 'x')
 N(b='x', a=1)
 
@@ -283,7 +283,7 @@ class X(NamedTuple):
 x: X
 reveal_type(x._replace())  # E: Revealed type is 'Tuple[builtins.int, builtins.str, fallback=__main__.X]'
 x._replace(x=5)
-x._replace(y=5)  # E: Argument 1 to "_replace" of "X" has incompatible type "int"; expected "str"
+x._replace(y=5)  # E: Argument "y" to "_replace" of "X" has incompatible type "int"; expected "str"
 
 [case testNewNamedTupleFields]
 # flags: --python-version 3.6
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -1978,7 +1978,7 @@ class C:
 
 x = C(foo=12)
 x.a # E: "C" has no attribute "a"
-C(foo='') # E: Argument 1 to "C" has incompatible type "str"; expected "int"
+C(foo='') # E: Argument "foo" to "C" has incompatible type "str"; expected "int"
 [builtins fixtures/__new__.pyi]
 
 [case testConstructInstanceWithDynamicallyTyped__new__]
diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test
--- a/test-data/unit/check-expressions.test
+++ b/test-data/unit/check-expressions.test
@@ -1584,7 +1584,7 @@ d() # E: "Dict[str, int]" not callable
 d2 = dict(it, x='') # E: Cannot infer type argument 2 of "dict"
 d2() # E: "Dict[Any, Any]" not callable
 
-d3 = dict(it, x='') # type: Dict[str, int] # E: Argument 2 to "dict" has incompatible type "str"; expected "int"
+d3 = dict(it, x='') # type: Dict[str, int] # E: Argument "x" to "dict" has incompatible type "str"; expected "int"
 [builtins fixtures/dict.pyi]
 
 [case testDictFromIterableAndKeywordArg2]
diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test
--- a/test-data/unit/check-functions.test
+++ b/test-data/unit/check-functions.test
@@ -1717,7 +1717,7 @@ def a(f: Callable[[NamedArg(int, 'x')], int]):
     f(2) # E: Too many positional arguments
     f() # E: Missing named argument "x"
     f(y=3) # E: Unexpected keyword argument "y"
-    f(x="foo") # E: Argument 1 has incompatible type "str"; expected "int"
+    f(x="foo") # E: Argument "x" has incompatible type "str"; expected "int"
 [builtins fixtures/dict.pyi]
 
 [case testCallableWithKeywordOnlyOptionalArg]
@@ -1729,7 +1729,7 @@ def a(f: Callable[[DefaultNamedArg(int, 'x')], int]):
     f(2) # E: Too many positional arguments
     f()
     f(y=3) # E: Unexpected keyword argument "y"
-    f(x="foo") # E: Argument 1 has incompatible type "str"; expected "int"
+    f(x="foo") # E: Argument "x" has incompatible type "str"; expected "int"
 [builtins fixtures/dict.pyi]
 
 [case testCallableWithKwargs]
@@ -1742,7 +1742,7 @@ def a(f: Callable[[KwArg(int)], int]):
     f()
     f(y=3)
     f(x=4, y=3, z=10)
-    f(x="foo") # E: Argument 1 has incompatible type "str"; expected "int"
+    f(x="foo") # E: Argument "x" has incompatible type "str"; expected "int"
 [builtins fixtures/dict.pyi]
 
 
diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test
--- a/test-data/unit/check-kwargs.test
+++ b/test-data/unit/check-kwargs.test
@@ -10,14 +10,14 @@ f(o=None()) # E: "None" not callable
 import typing
 def f(a: 'A') -> None: pass
 f(a=A())
-f(a=object()) # E: Argument 1 to "f" has incompatible type "object"; expected "A"
+f(a=object()) # E: Argument "a" to "f" has incompatible type "object"; expected "A"
 class A: pass
 
 [case testTwoKeywordArgumentsNotInOrder]
 import typing
 def f(a: 'A', b: 'B') -> None: pass
-f(b=A(), a=A()) # E: Argument 1 to "f" has incompatible type "A"; expected "B"
-f(b=B(), a=B()) # E: Argument 2 to "f" has incompatible type "B"; expected "A"
+f(b=A(), a=A()) # E: Argument "b" to "f" has incompatible type "A"; expected "B"
+f(b=B(), a=B()) # E: Argument "a" to "f" has incompatible type "B"; expected "A"
 f(a=A(), b=B())
 f(b=B(), a=A())
 class A: pass
@@ -30,10 +30,10 @@ f(a=A())
 f(b=B())
 f(c=C())
 f(b=B(), c=C())
-f(a=B()) # E: Argument 1 to "f" has incompatible type "B"; expected "Optional[A]"
-f(b=A()) # E: Argument 1 to "f" has incompatible type "A"; expected "Optional[B]"
-f(c=B()) # E: Argument 1 to "f" has incompatible type "B"; expected "Optional[C]"
-f(b=B(), c=A()) # E: Argument 2 to "f" has incompatible type "A"; expected "Optional[C]"
+f(a=B()) # E: Argument "a" to "f" has incompatible type "B"; expected "Optional[A]"
+f(b=A()) # E: Argument "b" to "f" has incompatible type "A"; expected "Optional[B]"
+f(c=B()) # E: Argument "c" to "f" has incompatible type "B"; expected "Optional[C]"
+f(b=B(), c=A()) # E: Argument "c" to "f" has incompatible type "A"; expected "Optional[C]"
 class A: pass
 class B: pass
 class C: pass
@@ -41,7 +41,7 @@ class C: pass
 [case testBothPositionalAndKeywordArguments]
 import typing
 def f(a: 'A', b: 'B') -> None: pass
-f(A(), b=A()) # E: Argument 2 to "f" has incompatible type "A"; expected "B"
+f(A(), b=A()) # E: Argument "b" to "f" has incompatible type "A"; expected "B"
 f(A(), b=B())
 class A: pass
 class B: pass
@@ -182,7 +182,7 @@ f(A(), b=B())
 f(A(), A(), b=B())
 f(B())      # E: Argument 1 to "f" has incompatible type "B"; expected "A"
 f(A(), B()) # E: Argument 2 to "f" has incompatible type "B"; expected "A"
-f(b=A())    # E: Argument 1 to "f" has incompatible type "A"; expected "Optional[B]"
+f(b=A())    # E: Argument "b" to "f" has incompatible type "A"; expected "Optional[B]"
 class A: pass
 class B: pass
 [builtins fixtures/list.pyi]
@@ -197,8 +197,8 @@ f(b=B())
 f(*a, b=B())
 f(A(), *a, b=B())
 f(A(), B())   # E: Argument 2 to "f" has incompatible type "B"; expected "A"
-f(A(), b=A()) # E: Argument 2 to "f" has incompatible type "A"; expected "Optional[B]"
-f(*a, b=A())  # E: Argument 2 to "f" has incompatible type "A"; expected "Optional[B]"
+f(A(), b=A()) # E: Argument "b" to "f" has incompatible type "A"; expected "Optional[B]"
+f(*a, b=A())  # E: Argument "b" to "f" has incompatible type "A"; expected "Optional[B]"
 class A: pass
 class B: pass
 [builtins fixtures/list.pyi]
@@ -238,7 +238,7 @@ def f( **kwargs: 'A') -> None: pass
 f()
 f(x=A())
 f(y=A(), z=A())
-f(x=B()) # E: Argument 1 to "f" has incompatible type "B"; expected "A"
+f(x=B()) # E: Argument "x" to "f" has incompatible type "B"; expected "A"
 f(A())   # E: Too many arguments for "f"
 # Perhaps a better message would be "Too many *positional* arguments..."
 class A: pass
@@ -334,7 +334,7 @@ class A: pass
 import typing
 def f(x): # type: (int) -> str # N: "f" defined here
     pass
-f(x='') # E: Argument 1 to "f" has incompatible type "str"; expected "int"
+f(x='') # E: Argument "x" to "f" has incompatible type "str"; expected "int"
 f(x=0)
 f(y=0) # E: Unexpected keyword argument "y" for "f"
 
@@ -343,7 +343,7 @@ import typing
 class A:
     def f(self, x): # type: (int) -> str  # N: "f" of "A" defined here
         pass
-A().f(x='') # E: Argument 1 to "f" of "A" has incompatible type "str"; expected "int"
+A().f(x='') # E: Argument "x" to "f" of "A" has incompatible type "str"; expected "int"
 A().f(x=0)
 A().f(y=0) # E: Unexpected keyword argument "y" for "f" of "A"
 
@@ -353,8 +353,8 @@ def f(**kwargs): # type: (**int) -> None
     pass
 f(z=1)
 f(x=1, y=1)
-f(x='', y=1) # E: Argument 1 to "f" has incompatible type "str"; expected "int"
-f(x=1, y='') # E: Argument 2 to "f" has incompatible type "str"; expected "int"
+f(x='', y=1) # E: Argument "x" to "f" has incompatible type "str"; expected "int"
+f(x=1, y='') # E: Argument "y" to "f" has incompatible type "str"; expected "int"
 [builtins fixtures/dict.pyi]
 
 [case testCallsWithStars]
diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test
--- a/test-data/unit/check-namedtuple.test
+++ b/test-data/unit/check-namedtuple.test
@@ -111,7 +111,7 @@ from typing import NamedTuple
 N = NamedTuple('N', [('a', int),
                      ('b', str)])
 n = N('x', 'x') # E: Argument 1 to "N" has incompatible type "str"; expected "int"
-n = N(1, b=2)   # E: Argument 2 to "N" has incompatible type "int"; expected "str"
+n = N(1, b=2)   # E: Argument "b" to "N" has incompatible type "int"; expected "str"
 N(1, 'x')
 N(b='x', a=1)
 
@@ -279,7 +279,7 @@ X = NamedTuple('X', [('x', int), ('y', str)])
 x = None  # type: X
 reveal_type(x._replace())  # E: Revealed type is 'Tuple[builtins.int, builtins.str, fallback=__main__.X]'
 x._replace(x=5)
-x._replace(y=5)  # E: Argument 1 to "_replace" of "X" has incompatible type "int"; expected "str"
+x._replace(y=5)  # E: Argument "y" to "_replace" of "X" has incompatible type "int"; expected "str"
 
 [case testNamedTupleMake]
 from typing import NamedTuple
diff --git a/test-data/unit/check-serialize.test b/test-data/unit/check-serialize.test
--- a/test-data/unit/check-serialize.test
+++ b/test-data/unit/check-serialize.test
@@ -183,11 +183,11 @@ def f(x: int,
 [out2]
 tmp/a.py:2: error: Argument 1 to "f" has incompatible type "str"; expected "int"
 tmp/a.py:3: error: Argument 2 to "f" has incompatible type "int"; expected "str"
-tmp/a.py:4: error: Argument 2 to "f" has incompatible type "int"; expected "str"
+tmp/a.py:4: error: Argument "y" to "f" has incompatible type "int"; expected "str"
 tmp/a.py:5: error: Argument 4 to "f" has incompatible type "str"; expected "int"
-tmp/a.py:6: error: Argument 3 to "f" has incompatible type "str"; expected "int"
-tmp/a.py:7: error: Argument 3 to "f" has incompatible type "str"; expected "int"
-tmp/a.py:8: error: Argument 4 to "f" has incompatible type "int"; expected "str"
+tmp/a.py:6: error: Argument "z" to "f" has incompatible type "str"; expected "int"
+tmp/a.py:7: error: Argument "zz" to "f" has incompatible type "str"; expected "int"
+tmp/a.py:8: error: Argument "foo" to "f" has incompatible type "int"; expected "str"
 
 [case testSerializeOverloadedFunction]
 import a
@@ -920,7 +920,7 @@ tmp/a.py:5: error: Incompatible types in assignment (expression has type "Tuple[
 tmp/a.py:6: error: Incompatible types in assignment (expression has type "Tuple[int]", variable has type "N")
 tmp/a.py:9: error: Revealed type is 'Tuple[builtins.int, fallback=b.N]'
 tmp/a.py:10: error: Revealed type is 'builtins.int'
-tmp/a.py:11: error: Argument 1 to "N" has incompatible type "str"; expected "int"
+tmp/a.py:11: error: Argument "x" to "N" has incompatible type "str"; expected "int"
 
 --
 -- Types and type aliases