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

typing.TypeGuard not eliminating type possibility when branching. #3450

Closed
AlexanderHott opened this issue May 10, 2022 · 2 comments
Closed
Labels
as designed Not a bug, working as intended

Comments

@AlexanderHott
Copy link

Note: if you are reporting a wrong signature of a function or a class in the standard library, then the typeshed tracker is better suited for this report: https://github.com/python/typeshed/issues.

Describe the bug
When using TypeGuards to determine a type, they do not exhaust the type in following type checks.

Expected behavior
After a type is used in one branch, it will not show up in another.

Screenshots or Code
Current

import typing as t

def str_not_int(x: str | int) -> t.TypeGuard[str]:
    return isinstance(x, str)

def f(x: str | int) -> None:
    if str_not_int(x):
        reveal_type(x)  # Type of "x" is "str"
    else:
        reveal_type(x)  # Type of "x" is "str | int"

def g(x: str| int) -> None:
    if str_not_int(x):
        reveal_type(x)  # Type of "x" is "str"
        return

    reveal_type(x)  # Type of "x" is "str | int"

Expected

import typing as t

def str_not_int(x: str | int) -> t.TypeGuard[str]:
    return isinstance(x, str)

def f(x: str | int) -> None:
    if str_not_int(x):
        reveal_type(x)  # Type of "x" is "str"
    else:
        # if x was of type "str" it would have been caught by the first "if"
        reveal_type(x)  # Type of "x" is "int"

def g(x: str| int) -> None:
    if str_not_int(x):
        reveal_type(x)  # Type of "x" is "str"
        return

    # if x was of type "str" the function would have returned
    reveal_type(x)  # Type of "x" is "int"

VS Code extension or command-line
Both the cli and the VSCode extension report the same behavior.
CLI: pyright 1.1.245
Extension: v1.1.245

Additional context
I noticed that other type checkers like mypy and pylance both have this "issue", and was wondering if this is the intended behavior. If so, is there any way to get rid of more complex types when branching (such as Callable[..., int] vs Callable[..., Awaitable[int]]?

@erictraut
Copy link
Collaborator

erictraut commented May 10, 2022

Yes, this is the documented behavior. TypeGuard does not perform type narrowing in the negative (else) case. PEP 647 explains why we chose not to do this.

There has been an active discussion within the typing-sig (the email group where Python typing matters are discussed) about possibly expanding on the initial TypeGuard support. I've proposed that we add a new form called StrictTypeGuard. This form would be less flexible than the original TypeGuard but would allow for negative narrowing.

Support for StrictTypeGuard is prototyped within pyright, so you can try it today. However, there is no runtime support for it because it hasn't been finalized or accepted as a PEP. You could use a quoted type annotation if you want to use it in your real code. Just be forewarned that it could go away or change.

Here's what that would look like:

import typing_extensions as te

def str_not_int(x: str | int) -> "te.StrictTypeGuard[str]":
    return isinstance(x, str)

def f(x: str | int) -> None:
    if str_not_int(x):
        reveal_type(x)  # Type of "x" is "str"
    else:
        reveal_type(x)  # Type of "x" is "int"

def g(x: str | int) -> None:
    if str_not_int(x):
        reveal_type(x)  # Type of "x" is "str"
        return

    reveal_type(x)  # Type of "x" is "int"

@erictraut erictraut added the as designed Not a bug, working as intended label May 10, 2022
@AlexanderHott
Copy link
Author

I read your proposal and they look interesting, I'll keep a lookout for the PEP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
as designed Not a bug, working as intended
Projects
None yet
Development

No branches or pull requests

2 participants