python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.32k stars 2.81k forks source link

zip silently returns Any because of loose join with star types #3370

Open ilevkivskyi opened 7 years ago

ilevkivskyi commented 7 years ago
y: List[Tuple[int]]
reveal_type(zip(*y))  # Revealed type is 'Any'

I think this is a bug since we should never silently infer Any from a precisely typed call.

ilevkivskyi commented 7 years ago

Related observations: this passes

y: List[Tuple[int]]
reveal_type(list(*y))  # Revealed type is 'builtins.list[builtins.int*]'

but fails at runtime with:

TypeError: list() takes at most 1 argument (2 given)

and similar for set etc. It looks like this is a general bug with star types.

gvanrossum commented 7 years ago

There may be a few edge cases left here, but in general mypy doesn't know enough about star types to tell its length (Tuple[] excepted), and it will let it through if there is a possible length for which the call would succeed. So for your second example, the type of y is List of some unspecified length, so list(*y) passes because if y were to have length 1 the call would succeed. (BTW your second example doesn't show the initialization for y so I'd expect NameError at runtime, so I really have to side with mypy here. :-)

Regarding the first case (zip(*y)) things are a little subtler, since zip() has many overloads for different argument counts. It appears here mypy finds the call compatible with all of the overloads (due to the above strategy related to star), tries to return something like the join of the return types, and comes up with Any. IITC that join is what it generally does when multiple overloads apply, but in other cases there is probably a better reason for it.

I'm not sure what to do for star-args with overloaded functions -- the "join" behavior seems reasonable enough to me in cases where the join is reasonable, e.g.:

from typing import TypeVar, overload, List
T = TypeVar('T')

@overload
def zap(a: T) -> T: pass
@overload
def zap(a: T, b: T, c: T, *more: T) -> T: pass
def zap(*args): pass

a: List[int]
reveal_type(zap(*a))  # int
b: float
reveal_type(zap(b, *a))  # float
ilevkivskyi commented 7 years ago

I'm not sure what to do for star-args with overloaded functions -- the "join" behavior seems reasonable enough to me in cases where the join is reasonable

I think join should never return plain Any (unless maybe in situation where one of joining types is Any, but this is a separate discussion https://github.com/python/mypy/issues/3194), so that I would just "tighten" the join.

gvanrossum commented 7 years ago

Is this the rigvt tissue for that?

ilevkivskyi commented 7 years ago

I didn't find any other issue for this, so I propose to track this here.

JukkaL commented 7 years ago

This seems pretty similar to #1322. Also, #3256 is related.

erictraut commented 2 years ago

This appears to be fixed. The revealed type in the original post is now builtins.zip[builtins.tuple[Any, ...]]. That's better than the original Any. For comparison, pyright reveals zip[tuple[int]] which is more accurate.