mypy icon indicating copy to clipboard operation
mypy copied to clipboard

Type narrowed to `Never` when using `TypeIs` with union of callables

Open Viicos opened this issue 1 year ago • 1 comments

playground

from typing import Any, Callable
from typing_extensions import TypeIs, assert_type, assert_never


def is_single_callable(c: Callable[[], Any] | Callable[[int], Any]) -> TypeIs[Callable[[int], Any]]:
    ...


def test(c: Callable[[], Any] | Callable[[int], Any]):
    if is_single_callable(c):
        reveal_type(c)
    else:
        # mypy thinks this is unreachable
        a: int = 1

While c could be narrowed to Callable[[], Any] in the else block, it is currently narrowed to Never. For reference, see this discussion.

Your Environment

  • Mypy version used: 1.12.1
  • Mypy command-line flags: --warn-unreachable
  • Python version used: 3.12

Viicos avatar Oct 21 '24 19:10 Viicos

It turns out this affects any union of a generic type (not just Callable). For example:

def is_int_list(c: list[str] | list[int]) -> TypeIs[list[int]]:
    ...

def foo(x: list[str] | list[int]) -> None:
    if is_int_list(x):
        reveal_type(x)  # N: Revealed type is "builtins.list[builtins.int]"
    else:
        reveal_type(x)  # E: Statement is unreachable
    reveal_type(x)      # N: Revealed type is "builtins.list[builtins.int]"

brianschubert avatar Nov 26 '24 19:11 brianschubert