`next(iter(<Tuple[...]>))` should be a `Union` of the `Tuple` element types, rather than their most descended common ancestor
Bug Report
When iterating over a Tuple with mixed types, mypy infers the element type based on the lowest common denominator, rather than a Union of element types. Mypy's behavior is sound, but not as helpful as it could be.
To Reproduce
Gist: mypy-play.net
from __future__ import annotations
from typing import *
A = TypeVar('A')
B = TypeVar('B')
C = TypeVar('C')
def some_func(seq: Tuple[A, B, C]) -> None:
for element in seq:
reveal_type(element) # got: object
# expected: Union[A, B, C]
This isn't specific to TypeVars:
Gist: mypy-play.net
from __future__ import annotations
from typing import *
as_us: Union[Sequence[str], Sequence[bytes]]
as_su: Sequence[Union[str, bytes]]
as_tuple: Union[Tuple[str, bytes], Tuple[bytes, str]]
for v1 in as_us:
reveal_type(v1) # Union[str, bytes] (as expected)
for v2 in as_su:
reveal_type(v2) # Union[str, bytes] (as expected)
for v3 in as_tuple:
reveal_type(v3) # Sequence[object] (better: Union[str, bytes])
The behaviour is particularly unhelpful because bytes and str have many interfaces in common, and are accepted interchangeably in many contexts (e.g. re.match(...)), even though they don't share an ancestor class.
Note: mixed tuples (e.g. Tuple[str, bytes]) seem to be essential; this typically won't reproduce if the Tuple only mentions one type (regardless of multiplicity), presumably because it's then handled as a Sequence.
Expected Behavior
Iterating over Tuples with mixed types should be handled as iterating over a Union of those types.
Mypy would ideally identify v3 in the second example as having type Union[str, bytes].
Actual Behavior
Iterating over Tuples with mixed types infers elements to be a common base type of those types.
Mypy identifies v3 has being a Sequence[object]. This is not technically wrong, but it is far less helpful.
Your Environment
- Mypy version used: 1.8.0, master(2024-02-10)
- Mypy command-line flags: (none)
- Mypy configuration options from
mypy.ini(and other config files): (none) - Python version used: 3.8, 3.12
Other remarks
I've tried to find any existing reports of this issue without success – the keywords are quite general. To assist others in finding this issue, I'll mention Tuple.__iter__().