TS has been used as a motivation and justification for the term, and as such I think that we should not deviate that much from how type guards work on Typescript. Unfortunately there is no specification of user-defined typeguards, as TS stopped maintaining the language spec some years ago. However, they do specify what a type guard is (see section 4.2 of https://javascript.xgqfrms.xyz/pdfs/TypeScript%20Language%20Specification.pdf), so I guess the implemented behavior for user-defined type guards is compatible with that definition.

Maybe there should be a prelude PEP norming how a type guard should be treated by a type checker.

There are also some important differences between the two:

- In TS, user defined typeguards are compatible with (...) => bool. However the proposed PEP says: "TypeGuard is a distinct type from bool. It is not a subtype of bool. Therefore, Callable[..., TypeGuard[int]] is not assignable to Callable[..., bool].", this prevents using typeguards as predicates for filtering functions.

- TS does apply narrowing in the negative branch. However, the proposal explicitly mandates not to narrow in such case. "User-defined type guards apply narrowing only in the positive case (the if clause). The type is not narrowed in the negative case."
This is important to clarify, as then the semantics of the type guard would be different. Basically in TS a typeguard MUST return true whenever the condition holds, however in the python proposal returning true means you can prove the condition holds. I think we should require the former and let the type checker decide whether they want to narrow in the negative case. I imagine the narrowing logic for negative cases is already implemented as they currently do it for isinstance checks.

- Not clear if type guards should be applied in and/or expressions: "When a conditional statement includes a call to a user-defined type guard function"

- TS requires "compatible" narrowing. This was ruled out because that "require[s] that we articulate all of the subtle ways in which the rules differ and under what specific circumstances the constrains are relaxed" and "it is safe to assume that they are interested in type safety and will not write their type guard functions in a way that will undermine type safety or produce nonsensical results". However, the latter statement may be true initially, but after refactoring a type one could potentially end up with a non-sensical guard.

- Also there's a possible "quirk" in how typeguards function in TS, which may be related to type narrowing. This is better explained by the following example:

function isFoo(x: string | number): x is string {
    return true;

const x = 3;
if (isFoo(x)) {
    const revealType : never = x
} else {
    const revealType : never = x

In the positive branch x will have a type of never not string, which basically prevents using it in various contexts.

See playground https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABDAzgMTnAFADwFyIpQBOMYA5ogD6JggC2ARgKbECUBOyKhJZlAbwCwAKETjExZlBDEkJEMwDcogL6jREBEURcAvIgDMKkTGCIsqDNhxs2iYWIlawOqQDdmAQwA2AFQBPAAdmRAIwZk9iRAMcEwlEFxQ4H2YAOjJgGzSoOABVIJDiAGEvFGYsOxNVRGYfcodRBKSoSUjvf2DQ8Pbo2LVRIA

On Sun, Feb 14, 2021 at 9:07 PM Joseph Perez <joperez@hotmail.fr> wrote:
> The existing logic in type checkers does not deal with control flow due to
try/except/else, because it's hard to reason about when an exception can or
can't happen. Even if we could address this, to human readers
try/except/else is an unwieldy construct to grasp compared to if/else, plus
the latter allows for multiple elif clauses as well.

Actually, mypy and Pycharm (did not test others) already deal with try/except/else:
def as_integer(x) -> int:...
x = ...
    checked_x = as_integer(x)
    reveal_type(checked_x)  # note: Revealed type is 'builtins.int'
But I completely agree that if/else is more readable and more flexible, notably with elif.

> Note that it's also easy to implement your as_integer() using a type guard:

Indeed, and it's kind of logical as the point of my example was to illustrate a replacement for type guard, the opposite replacement should be easy :-) Currently, I'm using checked cast pattern in place of type guard in my code because this is the best way I've found so far.

> That's not the problem the proposal is solving.

That's not *exactly* the problem the proposal is solving, it's rather a design pattern to get around and solve the problem: instead of refining the type of variable, assign (or fail) a new variable with the refined type and use this new variable.

But you seemed not enclined to this pattern, and I'm fine with that— I'm also waiting this PEP to replace my checked cast by type guards (but I would also have accepted if it was rejected to use checked cast pattern instead).

> I see PEP 593 as a verbose solution to the problem "how do we use
annotations for static typing and for runtime metadata simultaneously".
Type guards solve a problem that's entirely in the realm of static typing,
so IMO it would be an abuse of Annotated.

Fair enough.

Seems my proposals are being rejected. Does the PEP need an update about them?
Typing-sig mailing list -- typing-sig@python.org
To unsubscribe send an email to typing-sig-leave@python.org
Member address: skreft@gmail.com

Sebastian Kreft