Skip to content

Generic TypeGuard inferred as Never when passed as Callable with recursive Union type #18427

Open

Description

@iflan

Bug Report

I'm trying to make a function called ensure that takes a type guard function and an argument and returns the argument if the type guard function returns true:

def ensure(guard: Callable[[X], TypeGuard[Y]], x: X) -> Y:
    if guard(x):
        return x

    raise TypeError(f'{x} is not the right type')

This works fine for simple Union types, but fails for recursive Union types. Both scenarios work fine in Pylance.

To Reproduce

I created a Gist at:

https://mypy-play.net/?mypy=latest&python=3.12&gist=b48835cb0b0a7a3b5933e10593107dd3

that repros the issue. (It is also a valid pytest test.)

The core of the issue is shown by:

Y = TypeVar('Y')

Foo: TypeAlias = list['Foo'] | str | int

def is_str(o: Foo) -> TypeGuard[str]:
    return isinstance(o, str)

def ensure(guard: Callable[[Foo], TypeGuard[Y]], x: Foo) -> Y:
    if guard(x):
        return x

    raise TypeError(f'{x} is not the right type')

def test_ensure_is_str(s: Foo) -> None:
    # Pylance: list[Foo] | str | int; mypy: Union[builtins.list[...], builtins.str, builtins.int]
    reveal_type(s)
    # mypy: error: Need type annotation for "x"  [var-annotated]
    # mypy: error: Argument 1 to "ensure" has incompatible type "Callable[[Foo], TypeGuard[str]]"; expected "Callable[[Foo], TypeGuard[Never]]"  [arg-type]
    x = ensure(is_str, s)
    reveal_type(x)  # Pylance: str; mypy: Any
    # error: Argument 1 to "ensure" has incompatible type "Callable[[Foo], TypeGuard[str]]"; expected "Callable[[Foo], TypeGuard[Never]]"  [arg-type]
    reveal_type(ensure(is_str, s))  # Pylance: str; mypy: Never
    assert x == s

In this example, mypy can't deduce that x is a str because it expects ensure to take a function returning TypeGuard[Never].

Expected Behavior

mypy should be able to:

  • correctly deduce the argument type to ensure
  • deduce that x is a str

Actual Behavior

main.py:32: note: Revealed type is "Union[builtins.list[...], builtins.str, builtins.int]"
main.py:35: error: Need type annotation for "x"  [var-annotated]
main.py:35: error: Argument 1 to "ensure" has incompatible type "Callable[[Foo], TypeGuard[str]]"; expected "Callable[[Foo], TypeGuard[Never]]"  [arg-type]
main.py:36: note: Revealed type is "Any"
main.py:38: note: Revealed type is "Never"
main.py:38: error: Argument 1 to "ensure" has incompatible type "Callable[[Foo], TypeGuard[str]]"; expected "Callable[[Foo], TypeGuard[Never]]"  [arg-type]

Your Environment

  • Mypy version used: 1.14.1
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.11, 3.12

Activity

  1. added
    bugmypy got something wrong
    on Jan 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions