Repository: python/mypy

Issue:
"Incompatible index" message could be more informative
```
from typing import Dict

def f(d):
    # type: (Dict[str, str]) -> None
    d[1] = 'hello'
```
results in
```
incompatible_index.py: note: In function "f":
incompatible_index.py:5: error: Invalid index type "int" for "dict"
```
This error would be more helpful if it indicated the expected type as well--as is, it seems like it might be saying that dicts can't have ints as keys.


Discussion:
Agreed!

I'm taking a swing at this, though I'm not very confident in my understanding of the context.
How about this for an error message:

```
Index has incompatible type "int"; expected type "str" when indexing into "dict"
```

I was tempted to remove the mention of the type because it's not very helpful in this case. But when the index type isn't generic, it's more useful: `expected type "int" when indexing into "list"`. For the generic case, it might be better if we could show the whole type (`expected type "str" when indexing into "Dict[str, str]"`), but I don't think that's easily available.

Resolving patch:
diff --git a/mypy/messages.py b/mypy/messages.py
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -451,12 +451,12 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type:
                         return
 
             if name.startswith('"__getitem__" of'):
-                self.invalid_index_type(arg_type, base, context)
+                self.invalid_index_type(arg_type, callee.arg_types[n - 1], base, context)
                 return
 
             if name.startswith('"__setitem__" of'):
                 if n == 1:
-                    self.invalid_index_type(arg_type, base, context)
+                    self.invalid_index_type(arg_type, callee.arg_types[n - 1], base, context)
                 else:
                     msg = '{} (expression has type {}, target has type {})'
                     arg_type_str, callee_type_str = self.format_distinctly(arg_type,
@@ -503,10 +503,10 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type:
                 n, target, arg_type_str, expected_type_str)
         self.fail(msg, context)
 
-    def invalid_index_type(self, index_type: Type, base_str: str,
+    def invalid_index_type(self, index_type: Type, expected_type: Type, base_str: str,
                            context: Context) -> None:
-        self.fail('Invalid index type {} for {}'.format(
-            self.format(index_type), base_str), context)
+        self.fail('Invalid index type {} for {}; expected type {}'.format(
+            self.format(index_type), base_str, self.format(expected_type)), context)
 
     def too_few_arguments(self, callee: CallableType, context: Context,
                           argument_names: List[str]) -> None:


Test patch:
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
@@ -678,11 +678,11 @@ class B:
 A()[1] # Error
 [out]
 main: note: In function "f":
-main:5: error: Invalid index type "int" for "A"
+main:5: error: Invalid index type "int" for "A"; expected type "str"
 main: note: In class "B":
-main:7: error: Invalid index type "int" for "A"
+main:7: error: Invalid index type "int" for "A"; expected type "str"
 main: note: At top level:
-main:8: error: Invalid index type "int" for "A"
+main:8: error: Invalid index type "int" for "A"; expected type "str"
 
 [case testErrorContextAndBinaryOperators2]
 import m
@@ -698,11 +698,11 @@ A()[1] # Error
 [out]
 main:1: note: In module imported here:
 tmp/m.py: note: In function "f":
-tmp/m.py:5: error: Invalid index type "int" for "A"
+tmp/m.py:5: error: Invalid index type "int" for "A"; expected type "str"
 tmp/m.py: note: In class "B":
-tmp/m.py:7: error: Invalid index type "int" for "A"
+tmp/m.py:7: error: Invalid index type "int" for "A"; expected type "str"
 tmp/m.py: note: At top level:
-tmp/m.py:8: error: Invalid index type "int" for "A"
+tmp/m.py:8: error: Invalid index type "int" for "A"; expected type "str"
 
 
 -- Unary operators
@@ -788,7 +788,7 @@ class A:
 class B: pass
 class C: pass
 [out]
-main:3: error: Invalid index type "C" for "A"
+main:3: error: Invalid index type "C" for "A"; expected type "B"
 main:4: error: Incompatible types in assignment (expression has type "C", variable has type "A")
 main:5: error: Value of type "B" is not indexable
 
@@ -808,10 +808,36 @@ class B:
 class C:
     pass
 [out]
-main:3: error: Invalid index type "C" for "A"
+main:3: error: Invalid index type "C" for "A"; expected type "B"
 main:4: error: Incompatible types in assignment (expression has type "A", target has type "C")
 main:5: error: Unsupported target for indexed assignment
 
+[case testOverloadedIndexing]
+
+from typing import overload
+
+a, b, c = None, None, None  # type: (A, B, C)
+a[b]
+a[c]
+a[1]  # E: No overload variant of "__getitem__" of "A" matches argument types [builtins.int]
+
+i, s = None, None  # type: (int, str)
+i = a[b]
+s = a[b]  # E: Incompatible types in assignment (expression has type "int", variable has type "str")
+i = a[c]  # E: Incompatible types in assignment (expression has type "str", variable has type "int")
+s = a[c]
+
+class A:
+    @overload
+    def __getitem__(self, x: 'B') -> int:
+        pass
+    @overload
+    def __getitem__(self, x: 'C') -> str:
+        pass
+class B: pass
+class C: pass
+[out]
+
 
 -- Cast expression
 -- ---------------
@@ -956,8 +982,8 @@ class B: pass
 [case testSlicingWithInvalidBase]
 
 a = None # type: A
-a[1:2] # E: Invalid index type "slice" for "A"
-a[:]   # E: Invalid index type "slice" for "A"
+a[1:2] # E: Invalid index type "slice" for "A"; expected type "int"
+a[:]   # E: Invalid index type "slice" for "A"; expected type "int"
 class A:
   def __getitem__(self, n: int) -> 'A': pass
 [builtins fixtures/slice.pyi]
diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test
--- a/test-data/unit/check-generics.test
+++ b/test-data/unit/check-generics.test
@@ -288,7 +288,7 @@ class C: pass
 main:8: error: Incompatible types in assignment (expression has type "C", variable has type "B")
 main:9: error: Unsupported operand types for + ("A" and "C")
 main:10: error: Incompatible types in assignment (expression has type "B", variable has type "C")
-main:11: error: Invalid index type "B" for "A"
+main:11: error: Invalid index type "B" for "A"; expected type "C"
 
 [case testOperatorAssignmentWithIndexLvalue1]
 from typing import TypeVar, Generic
@@ -312,7 +312,7 @@ class C:
 [out]
 main:7: error: Unsupported operand types for + ("C" and "B")
 main:7: error: Incompatible types in assignment (expression has type "B", target has type "C")
-main:8: error: Invalid index type "C" for "A"
+main:8: error: Invalid index type "C" for "A"; expected type "B"
 
 [case testOperatorAssignmentWithIndexLvalue2]
 from typing import TypeVar, Generic
@@ -333,9 +333,9 @@ class B: pass
 class C:
     def __add__(self, o: 'C') -> 'C': pass
 [out]
-main:7: error: Invalid index type "B" for "A"
-main:8: error: Invalid index type "C" for "A"
-main:9: error: Invalid index type "B" for "A"
+main:7: error: Invalid index type "B" for "A"; expected type "C"
+main:8: error: Invalid index type "C" for "A"; expected type "B"
+main:9: error: Invalid index type "B" for "A"; expected type "C"
 
 
 -- Nested generic types
diff --git a/test-data/unit/check-newsyntax.test b/test-data/unit/check-newsyntax.test
--- a/test-data/unit/check-newsyntax.test
+++ b/test-data/unit/check-newsyntax.test
@@ -29,7 +29,7 @@ from typing import Dict, Any
 d: Dict[int, str] = {}
 d[42] = 'ab'
 d[42] = 42  # E: Incompatible types in assignment (expression has type "int", target has type "str")
-d['ab'] = 'ab'  # E: Invalid index type "str" for "dict"
+d['ab'] = 'ab'  # E: Invalid index type "str" for "dict"; expected type "int"
 [builtins fixtures/dict.pyi]
 [out]
 
diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test
--- a/test-data/unit/check-optional.test
+++ b/test-data/unit/check-optional.test
@@ -181,7 +181,7 @@ x = {None: None}
 x["bar"] = 1
 [builtins fixtures/dict.pyi]
 [out]
-main:2: error: Invalid index type "str" for "dict"
+main:2: error: Invalid index type "str" for "dict"; expected type None
 main:2: error: Incompatible types in assignment (expression has type "int", target has type None)
 
 [case testInferNonOptionalDictType]
diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test
--- a/test-data/unit/check-tuples.test
+++ b/test-data/unit/check-tuples.test
@@ -318,8 +318,8 @@ aa, bb = None, None # type: (AA, BB)
 
 a[a], b[b] = a, bb   # E: Incompatible types in assignment (expression has type "A", target has type "AA")
 a[a], b[b] = aa, b   # E: Incompatible types in assignment (expression has type "B", target has type "BB")
-a[aa], b[b] = aa, bb # E: Invalid index type "AA" for "A"
-a[a], b[bb] = aa, bb # E: Invalid index type "BB" for "B"
+a[aa], b[b] = aa, bb # E: Invalid index type "AA" for "A"; expected type "A"
+a[a], b[bb] = aa, bb # E: Invalid index type "BB" for "B"; expected type "B"
 a[a], b[b] = aa, bb
 
 class A:
diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test
--- a/test-data/unit/pythoneval.test
+++ b/test-data/unit/pythoneval.test
@@ -1103,10 +1103,10 @@ MyDDict(dict)['0']
 MyDDict(dict)[0]
 [out]
 _program.py:6: error: Argument 1 to "defaultdict" has incompatible type List[_T]; expected Callable[[], str]
-_program.py:9: error: Invalid index type "str" for "dict"
+_program.py:9: error: Invalid index type "str" for "dict"; expected type "int"
 _program.py:9: error: Incompatible types in assignment (expression has type "int", target has type "str")
 _program.py:19: error: List item 0 has incompatible type "Tuple[str, List[None]]"
-_program.py:23: error: Invalid index type "str" for "dict"
+_program.py:23: error: Invalid index type "str" for "dict"; expected type "int"
 
 [case testDictUpdateInference]
 from typing import Dict, Optional