Thanks Eric for the answers, in general I think it would be valuable to add some of these reasons to the PEP itself.

One point that hasn't been raised is what happens in multithreaded code, as nothing would guarantee that the guard is still met. 

Another point not mentioned is whether typeguards will work for async functions.

On Sat, Nov 14, 2020 at 2:03 PM Eric Traut <> wrote:
> What's the rationale behind ["the first explicit argument maps to the second parameter"]?

Without this, it would be impossible to implement a user-defined type guard function as a method. If you want to apply a type guard to self, you can pass `self` as the first argument, which maps to the first explicit parameter.
That would be really not ergonomic to define and use. Imagine looking at something like result.has_data(result) or def has_data(self, self) (see example below for more context).

It's hard for me to imagine type guards really using multiple arguments, especially self or class. I get your example about allowing empty when checking for a list of strings, but that could be written as two different guards.

What would be the semantics of evaluating multi-argument type guards which are instance or classmethods. What would happen if you modify self or the class, which may affect the result of the typeguard itself. (The same applies for any multi-argument typeguard.).

For what is worth, I couldn't find any typeguards with multiple arguments in any of the JS dependencies we are using at work (node, google apis, lodash, etc).


> What's the rationale for user-defined type guards to only apply in the positive case?

The problem is that `T - guarded_type` is not well defined in the general case. This sort of type algebra is defined only in very specific cases (such as with unions). In general, the negative case must assume no narrowing. For comparison, this is how user-defined type guards work in TypeScript, by necessity.

Thanks, I was looking for something like this. However that's not how TypeScript works in all cases. In fact the very documentation explains that they do narrow the type in the negative case at least for unions. See

Maybe a comparison with user-defined typeguards in TS would be really helpful.

Can you think of an example of a user-defined type guard (one that is not handled by built-in type guards) that could handle the negative case? None of the examples I provided in the draft PEP would work.

All the PEP examples would work in the negative case, however expressing the resulting type is not possible with current types.

One case would be something like the following snippet based on some real TS code written by a third party.

class QueryResult(Generic[T]):
  _data: Optional[bytes]
  def data(self) -> Optional[T]:
    return expensive_parse_operation(self._data)
  def empty(self) -> TypeGuard[MissingQueryResult]:
    return self._data is None


class MissingQueryResult(Protocol):
  def data(self) -> None: ...
  def empty(self) -> Literal[True]: ...

Note that without an option to provide the types for the negative case we have to implement another guard

def has_data(self) -> TypeGuard[ExistingQueryResult]:
    return self._data is not None

which is not that terrible from the implementor's POV, however for the user of the API, it's a problem as now they do need to remember which variant to use in which case.

Incidentally, I've posted more detailed documentation for the [type narrowing behaviors and all of the built-in type guards that pyright supports](
Typing-sig mailing list --
To unsubscribe send an email to
Member address:

In the rejected section Enforcing Strict Narrowing (, could you provide some examples of use cases that would be eliminated? Maybe mentioning that the variance of the type, and maybe some other restrictions could make this impractical.

However, it may be worth considering enforcing this by default and skip it if another argument is passed, or if the passed type is a protocol or a generic.

For example, I would expect the type checker to complain if it finds code like:

def unsound_guard(arg: int) -> TypeGuard[str]:
  return True

def unsound_guard_union(arg: TypeA | TypeB) -> TypeGuard[Typea]: # Note the typo here
  return True

For both of the cases above TS does emit an error:

A type predicate's type must be assignable to its parameter's type. Type 'string' is not assignable to type 'boolean'.
A type predicate's type must be assignable to its parameter's type. Type 'string' is not assignable to type 'number | boolean'.

It even checks compatibility of interfaces (protocols).
Sebastian Kreft