Repository: python/mypy

Issue:
Don't truncate Tuple, Union, and generic types in error messages
This gives the expected error::

```
from typing import List, Tuple
x = [
    ('', [''], 0),
    ]  # type: List[Tuple[str, List[str], str]]
```

Error::

```
z.py, line 2: List item 1 has incompatible type "Tuple[str, List[str], str]"
```

But this shortens the type to just 'tuple'::

```
from typing import List, Tuple
x = [
    ('', '', [''], 0),
    ]  # type: List[Tuple[str, str, List[str], str]]
```

Error::

```
z.py, line 2: List item 1 has incompatible type tuple
```



Discussion:
@JukkaL 
Is this bug still an issue? I ran the above code and got this output:

``` sh
$ mypy z.py
z.py, line 2: List item 1 has incompatible type "Tuple[str, str, List[str], int]"
```

There's some logic to avoid printing out really long types. https://github.com/python/mypy/commit/655eddfab81a9ec25ef4c31af06a118f0e1652f7#diff-7739b07c7ae4561e51cbb36f31984c90L188 increased the limit, but you can still reach the limit by adding a couple more fields to the tuple.

Personally I would prefer always seeing the full type; the details might matter to the message (like here, with `str` vs. `int` in the last component), but even when they aren't directly relevant (like if the declared type of the list was something completely different like `List[bool]`) seeing the contents of the tuple is often a valuable clue to understanding where the type came from and what you did wrong to get it instead of the type you expected.

Yeah, I've encountered a few cases where the truncated message ended up being pure misdirection.

I've been bitten by this as well. In retrospect, truncating long types now seems like a bad idea in general. It might still be occasionally useful in cases like `expected Tuple[...], got int`, where the truncated form would contain enough information about the types to highlight the difference, but the benefits would be marginal and not certain. Maybe just leave out the type truncation -- we can reconsider and add it back if it results in confusing messages.

There are other options such as run length encoding long tuples (for example, `Tuple[str, ..., str] (9 items)` but until we encounter cases where these would be helpful it's uncertain whether these would be worth implementing. 

Another option for cases where we display an expected and an actual type would be to run a more complete type diff algorithm and highlight the differences (e.g., `expected tuple with length 9, got tuple of length 8`). Even then we could display additional lines with the full types. Hypothetical example:

```
x.py:123: error: Argument 1 to "f" has incompatible type tuple (length 9); expected tuple (length 8)
x.py:123: note:     Actual type:   Tuple[int, int, int, int, int, int, int, int, int]
x.py:123: note:     Expected type: Tuple[int, int, int, int, int, int, int, int]
```

By aligning the types it would be easier to see how they are different in case they are similar but not compatible.

Current status: when the full, untruncated rendering of the type is over 40 characters, we abbreviate it to look like `tuple(length 6)`. The logic is in the `format_simple` method in https://github.com/python/mypy/blob/master/mypy/messages.py .

This is something that would/should be fixed as part of #1582, if it's not fixed sooner.

I expanded the scope of this issue slightly in its title. The same `format_simple` method in `mypy/messages.py` applies very similar treatment to `Union` types and generic types as it does to `Tuple` -- search for `len` in the body of the function :-P -- and I think we should stop truncating in all those cases for basically the same reasons.

A nice follow-up would be to present a diff like @JukkaL suggests in a comment above, but that should come separately after making the simpler change to stop truncating.

For unions I just upped the truncation limit from 40 to 400. That should cover most cases. f67bdbd321cb0e5943ebb1dd160c9ed150840281


Resolving patch:
diff --git a/mypy/messages.py b/mypy/messages.py
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -235,7 +235,7 @@ def format_simple(self, typ: Type, verbosity: int = 0) -> str:
                 for arg in itype.args:
                     a.append(strip_quotes(self.format(arg)))
                 s = ', '.join(a)
-                if len((base_str + s)) < 25:
+                if len((base_str + s)) < 150:
                     return '{}[{}]'.format(base_str, s)
                 else:
                     return '{}[...]'.format(base_str)
@@ -250,7 +250,7 @@ def format_simple(self, typ: Type, verbosity: int = 0) -> str:
             for t in typ.items:
                 items.append(strip_quotes(self.format(t)))
             s = '"Tuple[{}]"'.format(', '.join(items))
-            if len(s) < 40:
+            if len(s) < 400:
                 return s
             else:
                 return 'tuple(length {})'.format(len(items))


Test patch:
diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test
--- a/test-data/unit/check-async-await.test
+++ b/test-data/unit/check-async-await.test
@@ -363,7 +363,7 @@ def plain_host_generator() -> Generator[str, None, None]:
     x = yield from plain_generator()
     x = yield from plain_coroutine()  # E: "yield from" can't be applied to Awaitable[int]
     x = yield from decorated_generator()
-    x = yield from decorated_coroutine()  # E: "yield from" can't be applied to AwaitableGenerator[...]
+    x = yield from decorated_coroutine()  # E: "yield from" can't be applied to AwaitableGenerator[Any, Any, int, Awaitable[int]]
     x = yield from other_iterator()
     x = yield from other_coroutine()  # E: "yield from" can't be applied to "Aw"
 
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
@@ -533,14 +533,33 @@ D = TypeVar('D')
 E = TypeVar('E')
 F = TypeVar('F')
 G = TypeVar('G')
-class O: pass
-a = None # type: A[object, object, object, object, object, object]
+H = TypeVar('H')
+I = TypeVar('I')
+J = TypeVar('J')
+K = TypeVar('K')
+L = TypeVar('L')
+M = TypeVar('M')
+N = TypeVar('N')
+O = TypeVar('O')
+P = TypeVar('P')
+Q = TypeVar('Q')
+R = TypeVar('R')
+S = TypeVar('S')
+T = TypeVar('T')
+U = TypeVar('U')
+V = TypeVar('V')
+W = TypeVar('W')
+X = TypeVar('X')
+Y = TypeVar('Y')
+Z = TypeVar('Z')
+class OO: pass
+a = None # type: A[object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object]
 
-f(a) # E: Argument 1 to "f" has incompatible type A[...]; expected "O"
+f(a) # E: Argument 1 to "f" has incompatible type A[...]; expected "OO"
 
-def f(a: O) -> None:
+def f(a: OO) -> None:
     pass
-class A(Generic[B, C, D, E, F, G]): pass
+class A(Generic[B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]): pass
 
 [case testErrorWithShorterGenericTypeName]
 from typing import TypeVar, Generic
@@ -558,7 +577,7 @@ from typing import Callable, TypeVar, Generic
 S = TypeVar('S')
 T = TypeVar('T')
 a = None # type: A[object, Callable[[], None]]
-f(a) # E: Argument 1 to "f" has incompatible type A[...]; expected "B"
+f(a) # E: Argument 1 to "f" has incompatible type A[object, Callable[[], None]]; expected "B"
 
 def f(a: 'B') -> None: pass
 class A(Generic[S, T]): pass
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
@@ -573,13 +573,13 @@ class A:
 [case testLargeTuplesInErrorMessages]
 
 a = None # type: LongTypeName
-a + (a, a, a, a, a, a, a) # Fail
+a + (a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a) # Fail
 
 class LongTypeName:
     def __add__(self, x: 'LongTypeName') -> 'LongTypeName': pass
 [builtins fixtures/tuple.pyi]
 [out]
-main:3: error: Unsupported operand types for + ("LongTypeName" and tuple(length 7))
+main:3: error: Unsupported operand types for + ("LongTypeName" and tuple(length 50))
 
 
 -- Tuple methods
diff --git a/test-data/unit/pythoneval-asyncio.test b/test-data/unit/pythoneval-asyncio.test
--- a/test-data/unit/pythoneval-asyncio.test
+++ b/test-data/unit/pythoneval-asyncio.test
@@ -428,7 +428,7 @@ loop.run_until_complete(h())
 loop.close()
 [out]
 _program.py: note: In function "h3":
-_program.py:18: error: Incompatible return value type (got Future[Future[int]], expected Future[...])
+_program.py:18: error: Incompatible return value type (got Future[Future[int]], expected Future[Future[Future[int]]])
 
 [case testErrorOneLessFutureInReturnType]
 import typing