python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.28k stars 2.8k forks source link

TypeGuard does not narrow when statement compares for identity (`is True`) #17483

Open flaeppe opened 3 months ago

flaeppe commented 3 months ago

Bug Report

In the example below f is a TypeGuard that mypy fails to narrow the type with when the statement compares with identity (is True)

To Reproduce

https://mypy-play.net/?mypy=master&python=3.12&flags=strict&gist=688d7597c968ed90ecdd8d4bf2b3a8a3

from typing import TypeGuard

def f(value: str) -> TypeGuard[int]:
    return True

# This should narrow to "builtins.int"
by_identity = "123"
assert f(by_identity) is True
reveal_type(by_identity)  # Revealed type is "builtins.str"

Expected Behavior

Revealed type is "builtins.int"

Actual Behavior

Revealed type is "builtins.str"

Your Environment

sobolevn commented 3 months ago

The same happens for

by_identity = "123"
assert f(by_identity) == True
reveal_type(by_identity)  # Revealed type is "builtins.str"
sobolevn commented 3 months ago

This happens because we only check for type guards here: https://github.com/python/mypy/blob/ec00fb8db7f5c72faccde55a7f48dc6023c65411/mypy/checker.py#L5837-L5839

But, is True / == True is checked in this branch: https://github.com/python/mypy/blob/ec00fb8db7f5c72faccde55a7f48dc6023c65411/mypy/checker.py#L5889

It never really calls the first branch for its operands.

I think it would make sense to move the type guard part into a helper function and call it from both branches.

@flaeppe I can assign this on you if you want :)

flaeppe commented 3 months ago

@flaeppe I can assign this on you if you want :)

Sure, go ahead.

And thank you for the insight, I'll try to have a look