On Wed, Dec 30, 2020 at 7:56 AM Eric Traut <eric@traut.com> wrote:
The expression passed as the first argument should always take on the type indicated by the TypeGuard within the guarded block. In your example, `x` should take on the type `float` within the `if` statement.

The name of the type guard function in your example would more accurately be `is_nonzero_float` because that's what you're testing for. The expression `isinstance(x, float)` will always evaluate to false if you pass it an `int`. Perhaps you intended for the type guard function to test for both `float` and `int`?

def is_nonzero(x: object) -> TypeGuard[float]:
    return isinstance(x, (float, int)) and x != 0

Yup, that was a typo (float is a supertype of int in the type system but not at runtime, very annoying).

But suppose the return type was TypeGuard[[Union[int, float]] -- shouldn't it preserve that the input is already known to be an int? Otherwise I would have to write it using type variables, e.g.
T = TypeVar('T', bound=float)
def is_nonzero(x: Union[object, T]) -> TypeGuard[T]:
    return isinstance(x, (float, int)) and x != 0
which feels kind of ugly (and I don't know if it'll work).

I propose a new rule: if the type of the variable being tested is a subtype of the type deduced by the type guard, the guard has no effect and the variable keeps its original type. Otherwise, the variable assumes the type guard type (within the guarded scope).

At the very least I'd like the PEP to state explicitly that this is the case (rather than just by example), and explain in the rejected ideas why my proposal is inferior.

--Guido van Rossum (python.org/~guido)