-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TypeGuard functions not properly recognizing generic types #11428
Comments
friendly ping 👋🏼 @hauntsaninja @ilevkivskyi, since you were involved with |
The current behavior is correct and intended. User-defined type guards intentionally do not provide type narrowing for the negative ("else") case. To quote from PEP 647:
This decision was based on experience with user-defined type guards in TypeScript. It supports the negative case, and this leads to problems in a number of real-world cases. The TypeScript authors admitted that if they able to go back in time, they would probably reverse their decision. We learned from this when drafting PEP 647. Some built-in type guards (such as |
I was too hasty in my response. I think I misunderstood the problem you were reporting. You are correct that mypy isn't correctly applying the solved type variables in the case of the type guard. if is_left(x):
reveal_type(x) # Mypy: Left[L`-1], Pyright: List[str]
x.value + ""
elif is_right(x):
reveal_type(x) # Mypy: Left[str] | Right[int]], Pyright: Right[int]
x.value + 10 |
thanks for the response, @erictraut 🙏🏼 the lack of support for the negative case would account for the errors seen in the however, the positive case also fails currently: if is_left(x):
reveal_type(x) # Revealed type is "Left[L`-1]"
x.value + "" # Unsupported left operand type for + ("L") [operator] ... even though the type should be fully know at this point: the
...which would make |
Yes, I agree. You can address the negative case by adding an |
for full transparency: the problem i'm trying to solve is this one: rustedpy/result#69 ...and it seems that this is not possible to achieve currently. |
I believe I've stumbled upon the same issue, but when using |
Here is a simpler example, taken from PEP647. I ran it using from typing import TypeVar, Set, Any, Type
from typing_extensions import TypeGuard
_T = TypeVar("_T")
def is_set_of(val: Set[Any], type: Type[_T]) -> TypeGuard[Set[_T]]:
return all(isinstance(x, type) for x in val)
def foo() -> Set[Any]:
...
some_set = foo()
if is_set_of(some_set, str):
reveal_type(some_set) # Revealed type is "builtins.set[_T`-1]" I would have expected the revealed type to be |
Same problem on my end, using mypy 0.941. FYI, I'm trying to write a generic I even tried the (very simple) PEP647 example: _T = TypeVar("_T")
def is_two_element_tuple(val: Tuple[_T, ...]) -> TypeGuard[Tuple[_T, _T]]:
return len(val) == 2
def func(names: Tuple[str, ...]):
if is_two_element_tuple(names):
reveal_type(names) # Tuple[str, str]
else:
reveal_type(names) # Tuple[str, ...] which returns
instead of the expected
|
Bug Report
It seems the
typing.TypeGuard
support is not able to properly discriminate a union of two (generic) types.To Reproduce
Left[L]
andRight[R]
, both of which happen to be generic and have a.value
attribute.LeftOrRight
is aUnion
of those types.x
is a variable of typeLeftOrRight[str, int]
. In this example it has valueLeft("")
isinstance()
works fine to figure out whetherx
is eitherLeft
orRight
, and within theif/else
block the types are correct, including the typesL
andR
(bothTypeVar
)is_left()
andis_right()
helpers usetyping.TypeGuard
, but Mypy does not recognize the types like it did with theisinstance()
check.Expected Behavior
is_left()
andis_right()
act as type-safe ways to discriminate between the two members of theUnion
, including the actual type of the (generic).value
attribute.Actual Behavior
reveal_type()
returns wrong resultsYour Environment
This happens with both Mypy 0.910 (current release as time of writing) and today's
git
checkout of the main branch.The text was updated successfully, but these errors were encountered: