Tuple of iterables are not narrowed after converting it to tuple of tuple
Bug Report
If there is a function that takes *args: Iterable[float] as an argument, mypy still thinks it is a tuple of iterates after converting it to tuple of tuple via either map or tuple comprehension.
To Reproduce
https://mypy-play.net/?mypy=latest&python=3.11&gist=f70e818d8b82887c5d7f0b19cf51b504
from typing import Iterable
def foo(*args: Iterable[float]):
x = tuple(map(tuple, args))
reveal_type(x) # Revealed type is "builtins.tuple[builtins.tuple[builtins.float, ...], ...]"
args = tuple(map(tuple, args))
reveal_type(args) # Revealed type is "builtins.tuple[typing.Iterable[builtins.float], ...]"
Expected Behavior
args should have the same type as x.
Actual Behavior
args's type is not narrowed down after conversion.
Your Environment
- Mypy version used: 1.10
- Mypy command-line flags: N/A
- Mypy configuration options from
mypy.ini(and other config files): N/A - Python version used: 3.11
Mypy is correct here. The annotation Iterable[float] for *args means that each argument passed to *args must be an Iterable[float]. If your intention is that each argument passed to *args is a float, then you should simply specify *args: float. See this section of the Python typing spec for details.
No, my intention is args being tuple[Iterable[float], …] when passed in. float is possibly not important here, i.e. can be any type, it is just my actual case.
I need to convert such args to tuple[tuple[float, …], …], which is what I did for both x (assign to a new variable) and args (reassignment) inside foo, I expect they both have the converted/narrowed type, but the reassignment somehow does not work.
Hope it is clearer now.
Ah, I missed the fact that you were assigning the expression back to args in the second case.
I agree that mypy should be narrowing the type of args on assignment.
Incidentally, the same bug exists in pyright. I've logged a similar bug for pyright.
@erictraut, thanks for the investigation, I saw your comments in the pyright repo.
I feel that it is still a bug and does not fall into bidirectional type inference. My understanding from the mypy document is that bidirectional inference is involved when assigning a literal value to a previously annotated variable, which is not the case here.
consider another case:
from typing import Iterable
def bar(arg: Iterable[float]):
arg = tuple(arg)
reveal_type(arg)
Mypy sees arg converted from Iterable[float] to tuple[float, …] no problem.
My understanding ... is that bidirectional inference is involved when assigning a literal value to a previously annotated variable.
Bidirectional inference is used whenever the target of an assignment provides additional context about the "expected type". It's not limited to the assignment of literal values.
In any case, I don't consider this a bug, and I've closed the corresponding issue in pyright. I'll leave it to the maintainers of mypy to decide whether they consider this a bug in mypy. In my opinion, it's not.
Thanks for clarification. In that case, I find this surprising and somewhat against the runtime behaviour.
In my use case, since I explicitly convert the iterable to tuple, I would expect it to be indexable (as it is in runtime), but mypy complains later when I tried to index it. Workarounds like cast seems redundant in such case.