Skip to content
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

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

Open
iflan opened this issue Jan 7, 2025 · 0 comments
Labels
bug mypy got something wrong

Comments

@iflan
Copy link

iflan commented Jan 7, 2025

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
@iflan iflan added the bug mypy got something wrong label Jan 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

1 participant