Repository: python/mypy

Issue:
Unhelpful message with a very long tuple initializer
This program genenerates an unhelpful error message:
```py
from typing import Tuple
t: Tuple[int, ...] = (
    1, 2, 3, 4, 5, 6, 7, 8, 9., 10, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
    30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
    3, 31, 32, 33, 34, 35, 36, 37, 3, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
    30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 4, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
    30, 31, 32, 33, 34, 234234, 36, 37, 38, 39, 40, 1, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
    57, 58, 59, 60, 61, 62, 63
)
```

This is the message:

```
t.py:3: error: Incompatible types in assignment (expression has type <tuple: 148 items>, variable has type "Tuple[int, ...]")
```

This is derived from a real example, where the type was `Tuple[Type[x], ...]` and the initializer contained a long tuple of type objects. Figuring out which type object is incompatible was tricky.

Some ideas about how to improve this:
* Point to individual tuple items that are incompatible. For example: `Tuple item 8 has type "float"; "int" expected` (also point the error message to the relevant line/column).
* If there are many incompatibilities, only show a fixed number of them and hide the remaining ones.


Discussion:

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -4027,7 +4027,9 @@ def check_subtype(self,
 
         subtype = get_proper_type(subtype)
         supertype = get_proper_type(supertype)
-
+        if self.msg.try_report_long_tuple_assignment_error(subtype, supertype, context, msg,
+                                       subtype_label, supertype_label, code=code):
+            return False
         if self.should_suppress_optional_error([subtype]):
             return False
         extra_info = []  # type: List[str]
diff --git a/mypy/messages.py b/mypy/messages.py
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -1387,6 +1387,77 @@ def print_more(self,
                       .format(len(conflicts) - max_items),
                       context, offset=offset, code=code)
 
+    def try_report_long_tuple_assignment_error(self,
+                                               subtype: ProperType,
+                                               supertype: ProperType,
+                                               context: Context,
+                                               msg: str = message_registry.INCOMPATIBLE_TYPES,
+                                               subtype_label: Optional[str] = None,
+                                               supertype_label: Optional[str] = None,
+                                               code: Optional[ErrorCode] = None) -> bool:
+        """Try to generate meaningful error message for very long tuple assignment
+
+        Returns a bool: True when generating long tuple assignment error,
+        False when no such error reported
+        """
+        if isinstance(subtype, TupleType):
+            if (len(subtype.items) > 10 and
+                isinstance(supertype, Instance) and
+                    supertype.type.fullname == 'builtins.tuple'):
+                lhs_type = supertype.args[0]
+                lhs_types = [lhs_type] * len(subtype.items)
+                self.generate_incompatible_tuple_error(lhs_types,
+                                    subtype.items, context, msg, code)
+                return True
+            elif (isinstance(supertype, TupleType) and
+                    (len(subtype.items) > 10 or len(supertype.items) > 10)):
+                if len(subtype.items) != len(supertype.items):
+                    if supertype_label is not None and subtype_label is not None:
+                        error_msg = "{} ({} {}, {} {})".format(msg, subtype_label,
+                                        self.format_long_tuple_type(subtype), supertype_label,
+                                        self.format_long_tuple_type(supertype))
+                        self.fail(error_msg, context, code=code)
+                        return True
+                self.generate_incompatible_tuple_error(supertype.items,
+                                    subtype.items, context, msg, code)
+                return True
+        return False
+
+    def format_long_tuple_type(self, typ: TupleType) -> str:
+        """Format very long tuple type using an ellipsis notation"""
+        item_cnt = len(typ.items)
+        if item_cnt > 10:
+            return 'Tuple[{}, {}, ... <{} more items>]'\
+                    .format(format_type_bare(typ.items[0]),
+                        format_type_bare(typ.items[1]), str(item_cnt - 2))
+        else:
+            return format_type_bare(typ)
+
+    def generate_incompatible_tuple_error(self,
+                                          lhs_types: List[Type],
+                                          rhs_types: List[Type],
+                                          context: Context,
+                                          msg: str = message_registry.INCOMPATIBLE_TYPES,
+                                          code: Optional[ErrorCode] = None) -> None:
+        """Generate error message for individual incompatible tuple pairs"""
+        error_cnt = 0
+        notes = []  # List[str]
+        for i, (lhs_t, rhs_t) in enumerate(zip(lhs_types, rhs_types)):
+            if not is_subtype(lhs_t, rhs_t):
+                if error_cnt < 3:
+                    notes.append('Expression tuple item {} has type "{}"; "{}" expected; '
+                        .format(str(i), format_type_bare(rhs_t), format_type_bare(lhs_t)))
+                error_cnt += 1
+
+        error_msg = msg + ' ({} tuple items are incompatible'.format(str(error_cnt))
+        if error_cnt - 3 > 0:
+            error_msg += '; {} items are omitted)'.format(str(error_cnt - 3))
+        else:
+            error_msg += ')'
+        self.fail(error_msg, context, code=code)
+        for note in notes:
+            self.note(note, context, code=code)
+
 
 def quote_type_string(type_string: str) -> str:
     """Quotes a type representation for use in messages."""


Test patch:
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
@@ -1241,3 +1241,46 @@ b = ("bar", 7)
 reveal_type(a + b)  # N: Revealed type is 'Tuple[builtins.int, builtins.str, builtins.int, builtins.str, builtins.int]'
 
 [builtins fixtures/tuple.pyi]
+
+[case testAssigningWithLongTupleInitializer]
+from typing import Tuple
+
+# long initializer assignment with few mismatches
+t: Tuple[int, ...] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", 11) \
+                                                                    # E: Incompatible types in assignment (3 tuple items are incompatible) \
+                                                                    # N: Expression tuple item 8 has type "str"; "int" expected; \
+                                                                    # N: Expression tuple item 9 has type "str"; "int" expected; \
+                                                                    # N: Expression tuple item 10 has type "str"; "int" expected;
+
+# long initializer assignment with more mismatches
+t1: Tuple[int, ...] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str") \
+                                                                    # E: Incompatible types in assignment (4 tuple items are incompatible; 1 items are omitted) \
+                                                                    # N: Expression tuple item 8 has type "str"; "int" expected; \
+                                                                    # N: Expression tuple item 9 has type "str"; "int" expected; \
+                                                                    # N: Expression tuple item 10 has type "str"; "int" expected;
+
+# short tuple initializer assignment
+t2: Tuple[int, ...] = (1, 2, "s", 4) \
+                                # E: Incompatible types in assignment (expression has type "Tuple[int, int, str, int]", variable has type "Tuple[int, ...]")
+
+# long initializer assignment with few mismatches, no ellipsis
+t3: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, "str", "str") \
+                                                                    # E: Incompatible types in assignment (2 tuple items are incompatible) \
+                                                                    # N: Expression tuple item 10 has type "str"; "int" expected; \
+                                                                    # N: Expression tuple item 11 has type "str"; "int" expected;
+
+# long initializer assignment with more mismatches, no ellipsis
+t4: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str") \
+                                                                    # E: Incompatible types in assignment (4 tuple items are incompatible; 1 items are omitted) \
+                                                                    # N: Expression tuple item 8 has type "str"; "int" expected; \
+                                                                    # N: Expression tuple item 9 has type "str"; "int" expected; \
+                                                                    # N: Expression tuple item 10 has type "str"; "int" expected;
+
+# short tuple initializer assignment, no ellipsis
+t5: Tuple[int, int] = (1, 2, "s", 4)  # E: Incompatible types in assignment (expression has type "Tuple[int, int, str, int]", variable has type "Tuple[int, int]")
+
+# long initializer assignment with mismatched pairs
+t6: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str", 1, 1, 1, 1, 1) \
+                                                                    # E: Incompatible types in assignment (expression has type Tuple[int, int, ... <15 more items>], variable has type Tuple[int, int, ... <10 more items>]) 
+
+[builtins fixtures/tuple.pyi]