Open erictraut opened 1 month ago
The same happens with TypeGuard
as well:
from typing import Any, overload
from typing_extensions import TypeGuard
@overload
def func1(x: str) -> TypeGuard[str]:
...
@overload
def func1(x: int) -> TypeGuard[int]:
...
def func1(x: Any) -> Any:
return True
def func2(val: Any):
if func1(val):
reveal_type(val) # Should be `Any`, but is `str`
The problem is that we don't check for overloads here: https://github.com/python/mypy/blob/db9837fa240918fa90f2d9a3952314bd3207ed20/mypy/checker.py#L5873-L5890
The same happens if TypeGuard
and TypeIs
are both used in an overload. This is an edge case that's unlikely to ever be used in real-world code, but it might as well be fixed along with the other cases.
from typing import Any, overload
from typing_extensions import TypeGuard, TypeIs
@overload
def func1(x: int | str) -> TypeIs[int]:
...
@overload
def func1(x: list) -> TypeGuard[int]:
...
def func1(x: Any) -> Any:
return True
def func2(val: Any):
if func1(val):
reveal_type(val) # Should be `Any`, but is `int`
I have a prototype implementation, will create a PR tomorrow 🎉
When overload matching is ambiguous due to an
Any
argument, mypy typically looks at the return types of the potential matches. If the return types differ, it evaluates the return type asAny
to preserve the "gradual type guarantee". It apparently doesn't do this when the return types of the potential overload matches useTypeIs
. See the example below, where mypy matches the first overload rather than detecting the ambiguity and evaluatingAny
.I discovered this problem because pyright has the same issue. I'm guessing that the underlying cause of the bug in mypy is similar. Pyright's logic was treating
TypeIs[T]
as equivalent tobool
in this case, butTypeIs[T]
should be treated as a subtype ofbool
. Also,TypeIs[X]
is not equivalent toTypeIs[Y]
unlessX
is equivalent toY
.This bug affects a recent change in typeshed to the
dataclasses.isdataclass
function, as discussed here.