Skip to content

Commit

Permalink
Changes how TypeGuard types are checked in subtypes (python#11314)
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn authored and tushar-deepsource committed Jan 20, 2022
1 parent baa55e5 commit c2dc223
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 1 deletion.
5 changes: 4 additions & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -1753,7 +1753,10 @@ def format(typ: Type) -> str:
# return type (this always works).
return format(TypeType.make_normalized(erase_type(func.items[0].ret_type)))
elif isinstance(func, CallableType):
return_type = format(func.ret_type)
if func.type_guard is not None:
return_type = f'TypeGuard[{format(func.type_guard)}]'
else:
return_type = format(func.ret_type)
if func.is_ellipsis_args:
return 'Callable[..., {}]'.format(return_type)
arg_strings = []
Expand Down
7 changes: 7 additions & 0 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,13 @@ def visit_type_var(self, left: TypeVarType) -> bool:
def visit_callable_type(self, left: CallableType) -> bool:
right = self.right
if isinstance(right, CallableType):
if left.type_guard is not None and right.type_guard is not None:
if not self._is_subtype(left.type_guard, right.type_guard):
return False
elif right.type_guard is not None and left.type_guard is None:
# This means that one function has `TypeGuard` and other does not.
# They are not compatible. See https://github.com/python/mypy/issues/11307
return False
return is_callable_compatible(
left, right,
is_compat=self._is_subtype,
Expand Down
89 changes: 89 additions & 0 deletions test-data/unit/check-typeguard.test
Original file line number Diff line number Diff line change
Expand Up @@ -458,3 +458,92 @@ def foobar_typeguard(x: object):
return
reveal_type(x) # N: Revealed type is "__main__.<subclass of "Foo" and "Bar">"
[builtins fixtures/tuple.pyi]

[case testTypeGuardAsFunctionArgAsBoolSubtype]
from typing import Callable
from typing_extensions import TypeGuard

def accepts_bool(f: Callable[[object], bool]): pass

def with_bool_typeguard(o: object) -> TypeGuard[bool]: pass
def with_str_typeguard(o: object) -> TypeGuard[str]: pass
def with_bool(o: object) -> bool: pass

accepts_bool(with_bool_typeguard)
accepts_bool(with_str_typeguard)
accepts_bool(with_bool)
[builtins fixtures/tuple.pyi]

[case testTypeGuardAsFunctionArg]
from typing import Callable
from typing_extensions import TypeGuard

def accepts_typeguard(f: Callable[[object], TypeGuard[bool]]): pass
def different_typeguard(f: Callable[[object], TypeGuard[str]]): pass

def with_typeguard(o: object) -> TypeGuard[bool]: pass
def with_bool(o: object) -> bool: pass

accepts_typeguard(with_typeguard)
accepts_typeguard(with_bool) # E: Argument 1 to "accepts_typeguard" has incompatible type "Callable[[object], bool]"; expected "Callable[[object], TypeGuard[bool]]"

different_typeguard(with_typeguard) # E: Argument 1 to "different_typeguard" has incompatible type "Callable[[object], TypeGuard[bool]]"; expected "Callable[[object], TypeGuard[str]]"
different_typeguard(with_bool) # E: Argument 1 to "different_typeguard" has incompatible type "Callable[[object], bool]"; expected "Callable[[object], TypeGuard[str]]"
[builtins fixtures/tuple.pyi]

[case testTypeGuardAsGenericFunctionArg]
from typing import Callable, TypeVar
from typing_extensions import TypeGuard

T = TypeVar('T')

def accepts_typeguard(f: Callable[[object], TypeGuard[T]]): pass

def with_bool_typeguard(o: object) -> TypeGuard[bool]: pass
def with_str_typeguard(o: object) -> TypeGuard[str]: pass
def with_bool(o: object) -> bool: pass

accepts_typeguard(with_bool_typeguard)
accepts_typeguard(with_str_typeguard)
accepts_typeguard(with_bool) # E: Argument 1 to "accepts_typeguard" has incompatible type "Callable[[object], bool]"; expected "Callable[[object], TypeGuard[bool]]"
[builtins fixtures/tuple.pyi]

[case testTypeGuardAsOverloadedFunctionArg]
# https://github.com/python/mypy/issues/11307
from typing import Callable, TypeVar, Generic, Any, overload
from typing_extensions import TypeGuard

_T = TypeVar('_T')

class filter(Generic[_T]):
@overload
def __init__(self, function: Callable[[object], TypeGuard[_T]]) -> None: pass
@overload
def __init__(self, function: Callable[[_T], Any]) -> None: pass
def __init__(self, function): pass

def is_int_typeguard(a: object) -> TypeGuard[int]: pass
def returns_bool(a: object) -> bool: pass

reveal_type(filter(is_int_typeguard)) # N: Revealed type is "__main__.filter[builtins.int*]"
reveal_type(filter(returns_bool)) # N: Revealed type is "__main__.filter[builtins.object*]"
[builtins fixtures/tuple.pyi]

[case testTypeGuardSubtypingVariance]
from typing import Callable
from typing_extensions import TypeGuard

class A: pass
class B(A): pass
class C(B): pass

def accepts_typeguard(f: Callable[[object], TypeGuard[B]]): pass

def with_typeguard_a(o: object) -> TypeGuard[A]: pass
def with_typeguard_b(o: object) -> TypeGuard[B]: pass
def with_typeguard_c(o: object) -> TypeGuard[C]: pass

accepts_typeguard(with_typeguard_a) # E: Argument 1 to "accepts_typeguard" has incompatible type "Callable[[object], TypeGuard[A]]"; expected "Callable[[object], TypeGuard[B]]"
accepts_typeguard(with_typeguard_b)
accepts_typeguard(with_typeguard_c)
[builtins fixtures/tuple.pyi]

0 comments on commit c2dc223

Please sign in to comment.