I never said that there “wasn’t a case for it”. Just like you, I’m considering the case of keeping the existing semantics, but I personally think they should be removed. (More on why below.)
Maybe we should ask the PEP writer to add a migration guide? Ultimately, any flavor of type guard can be rewritten without type guards. They are typing sugar. The current type guard can always be rewritten:
def is_u(val: T) -> bool: ... # defined as before, but just return a Boolean.
and then used as follows:
def f(val: T):
if is_u(val) is not None:
u = cast(val, U)
# Use u instead of val from here on to get the `U` type you wanted.
else:
# val is unchanged, as desired.
Let’s examine the future under your proposal of keeping TypeGuard versus the future that’s suggested by the PEP and pretend that we’re a new user who has to choose a type guard for a function that she’s writing, and ask which future is a better one to live in.
First, some background on the various type guards:
We have the current TypeGuard:
def is_u(val: T) -> TypeGuard[U]: ...
def f(val: T):
if is_u(val):
# Type of ``val`` is narrowed to ``U``.
else:
# Type of ``val`` remains as ``T``.
Now, the PEP 724 “strict” TypeGuard:
def is_u(val: T) -> TypeGuard[U]: ...
def f(val: T):
if is_u(val):
# Type of ``val`` is narrowed to ``T & U``.
else:
# Type of ``val`` is narrowed to ``T & Not[U]``.
Note that this is extremely logical since it can work exactly like an instance check for U.
And the proposed LaxTypeGuard:
def is_u(val: T) -> LaxTypeGuard[U]: ...
def f(val: T):
if is_u(val):
# Type of ``val`` is narrowed to ``T & U``. Note the difference!
else:
# Type of ``val`` remains as ``T``.
Now let’s compare the benefits for a new user in each future.
In the “stable future” that you’re proposing I guess you want to keep the current type guard, and add the strict type guard? In that case, the stable future has the following problems:
- There is doubt about which type guard is required, which requires a deep understanding of the documentation.
- Using the current type guard requires learning a new reasoning pattern that is unlike instance checks.
- The current type guard has a lot of surprising behavior based on all of the bug reports against it. In particular, it does not narrow
T(replacing it withUin the positive case). This will probably necessitate addingLaxTypeGuard, and maybe deprecating it anyway.
The “progressive” future that I’m proposing would have the strict type guard only. Thus,
- There is no doubt about which type guard is required, which guides new users to the obvious choice.
- Using the type guard can work exactly like an instance check, which makes it easy to understand.
- If there’s a need, a
LaxTypeGuardcan be added. It has the benefit of mirroring the strict type guard–without the surprising behavior. It’s a bit trickier to desugar than the current type guard, so the case for adding it is stronger too.
These are two futures that I was comparing, and this is the basis for my motivation. I understand the desire to mitigate upgrade pains, but I think they’re outweighed by the benefits of creating the better future.