Description
Bug Report
When taking the and
of multiple isinstances/typeguards, it seems like only the most recent is used to constrain the variable whose type is being checked.
To Reproduce
See Gist: https://gist.github.com/a4cc96c4bb9207a3ae6a344016d2e46d
from typing import Any, TypeGuard, reveal_type
def int_or_str(x: Any) -> TypeGuard[int | str]:
return isinstance(x, int | str)
def int_or_list(x: Any) -> TypeGuard[int | list]:
return isinstance(x, int | list)
def working_in_pyright(x: Any) -> TypeGuard[int]:
if isinstance(x, int | str) and isinstance(x, int | list):
reveal_type(x) # pyright: int, mypy: int | list
return True
return False
def failing_in_both(x: Any) -> TypeGuard[int]:
if int_or_str(x) and int_or_list(x):
reveal_type(x) # int | list
return True
return False
# incidentally, changing `and` to `or` results in the correct output `int | str | list`
Expected Behavior
I think the code reveal_type(x)
should return int
in each of these examples.
I'm not sure about the failing_in_both
example: I vaguely understand that user-defined type guards do not constrain the negative case of an if (but I haven't yet understood the rationale) -- does similar reasoning explain why the type guards on the LHS and RHS of the and
do not combine?
I mean, technically, I could write a TypeGuard that modifies the argument, and so I guess the LHS of the and
could cease to hold when the RHS is called; is this why? Would @erictraut's StrictTypeGuard python/typing#1013 be able to resolve this?
Actual Behavior
main.py:14: note: Revealed type is "Union[builtins.int, builtins.str, builtins.list[Any]]"
main.py:21: note: Revealed type is "Union[builtins.int, builtins.str, builtins.list[Any]]"
Success: no issues found in 1 source file
Your Environment
- Mypy version used: 0.991
- Python version used: 3.11