Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes how TypeGuard types are checked in subtypes #11314

Merged
merged 3 commits into from
Oct 30, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -1740,7 +1740,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
70 changes: 70 additions & 0 deletions test-data/unit/check-typeguard.test
Original file line number Diff line number Diff line change
Expand Up @@ -458,3 +458,73 @@ 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]]"
sobolevn marked this conversation as resolved.
Show resolved Hide resolved
[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]
sobolevn marked this conversation as resolved.
Show resolved Hide resolved