Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8a30073
Basic work on TypeNarrower
JelleZijlstra Feb 9, 2024
58e8403
Initial tests (many failing)
JelleZijlstra Feb 9, 2024
c8d2af8
Fewer test failures
JelleZijlstra Feb 10, 2024
f205910
Fix the remaining tests
JelleZijlstra Feb 10, 2024
75c9dec
Did not actually need TypeNarrowerType
JelleZijlstra Feb 10, 2024
4666486
Error for bad narrowing
JelleZijlstra Feb 10, 2024
25a9c79
temp change typeshed
JelleZijlstra Feb 10, 2024
faa4a07
Fixes
JelleZijlstra Feb 10, 2024
c0e0210
Merge branch 'master' into typenarrower
JelleZijlstra Feb 10, 2024
f107e5b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 10, 2024
34700bb
doc
JelleZijlstra Feb 10, 2024
065ec92
fix self check
JelleZijlstra Feb 10, 2024
aef3036
like this maybe
JelleZijlstra Feb 10, 2024
4b19c77
Merge remote-tracking branch 'upstream/master' into typenarrower
JelleZijlstra Feb 10, 2024
6b0e749
Fix and add tests
JelleZijlstra Feb 10, 2024
c9e53e6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 10, 2024
909e53c
Use TypeIs
JelleZijlstra Feb 14, 2024
eb88371
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 14, 2024
1b1e368
Apply suggestions from code review
JelleZijlstra Feb 22, 2024
84c69d2
Code review feedback, new test case, fix incorrect constraints
JelleZijlstra Feb 23, 2024
ae294bf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 23, 2024
7fedbcf
Merge remote-tracking branch 'upstream/master' into typenarrower
JelleZijlstra Mar 1, 2024
dbc229d
Rename error code
JelleZijlstra Mar 1, 2024
8b2fb0b
Quote name
JelleZijlstra Mar 1, 2024
816fd1a
unxfail
JelleZijlstra Mar 1, 2024
d6fcc35
add elif test
JelleZijlstra Mar 1, 2024
ef825ce
type context test
JelleZijlstra Mar 1, 2024
d32956d
Add test
JelleZijlstra Mar 1, 2024
a36a16a
Add error code test case
JelleZijlstra Mar 1, 2024
b32ba80
update docs
JelleZijlstra Mar 1, 2024
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
Prev Previous commit
Next Next commit
Code review feedback, new test case, fix incorrect constraints
  • Loading branch information
JelleZijlstra committed Feb 23, 2024
commit 84c69d26c813319d3fabbd80ecf151f7cf0c18d1
18 changes: 13 additions & 5 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -1018,14 +1018,22 @@ def visit_callable_type(self, template: CallableType) -> list[Constraint]:
param_spec = template.param_spec()

template_ret_type, cactual_ret_type = template.ret_type, cactual.ret_type
if template.type_guard is not None:
if template.type_guard is not None and cactual.type_guard is not None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks! I think this should fix a thing I ran into while testing #16939

template_ret_type = template.type_guard
elif template.type_is is not None:
template_ret_type = template.type_is
if cactual.type_guard is not None:
cactual_ret_type = cactual.type_guard
elif cactual.type_is is not None:
elif template.type_guard is not None:
template_ret_type = AnyType(TypeOfAny.special_form)
elif cactual.type_guard is not None:
cactual_ret_type = AnyType(TypeOfAny.special_form)

if template.type_is is not None and cactual.type_is is not None:
template_ret_type = template.type_is
cactual_ret_type = cactual.type_is
elif template.type_is is not None:
template_ret_type = AnyType(TypeOfAny.special_form)
elif cactual.type_is is not None:
cactual_ret_type = AnyType(TypeOfAny.special_form)

res.extend(infer_constraints(template_ret_type, cactual_ret_type, self.direction))

if param_spec is None:
Expand Down
3 changes: 2 additions & 1 deletion mypy/message_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,5 +325,6 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
"Expected string literal for argument name, got {}", codes.SYNTAX
)
TYPE_NARROWER_NOT_SUBTYPE: Final = ErrorMessage(
"Narrowed type {} is not a subtype of input type {}", codes.SYNTAX
"Narrowed type {} is not a subtype of input type {}",
codes.TYPE_NARROWER_NOT_SUBTYPE,
)
2 changes: 1 addition & 1 deletion test-data/unit/check-typeguard.test
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ 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]]"
accepts_typeguard(with_bool) # E: Argument 1 to "accepts_typeguard" has incompatible type "Callable[[object], bool]"; expected "Callable[[object], TypeGuard[Never]]"
[builtins fixtures/tuple.pyi]

[case testTypeGuardAsOverloadedFunctionArg]
Expand Down
136 changes: 82 additions & 54 deletions test-data/unit/check-typeis.test
Original file line number Diff line number Diff line change
Expand Up @@ -240,14 +240,14 @@ def filter(f: Callable[[T], TypeIs[R]], it: Iterable[T]) -> Iterator[R]: ...
def filter(f: Callable[[T], bool], it: Iterable[T]) -> Iterator[T]: ...
def filter(*args): pass

def is_int_typeguard(a: object) -> TypeIs[int]: pass
def is_int_typeis(a: object) -> TypeIs[int]: pass
def is_int_bool(a: object) -> bool: pass

def main(a: List[Optional[int]]) -> None:
bb = filter(lambda x: x is not None, a)
reveal_type(bb) # N: Revealed type is "typing.Iterator[Union[builtins.int, None]]"
# Also, if you replace 'bool' with 'Any' in the second overload, bb is Iterator[Any]
cc = filter(is_int_typeguard, a)
cc = filter(is_int_typeis, a)
reveal_type(cc) # N: Revealed type is "typing.Iterator[builtins.int]"
dd = filter(is_int_bool, a)
reveal_type(dd) # N: Revealed type is "typing.Iterator[Union[builtins.int, None]]"
Expand Down Expand Up @@ -287,18 +287,23 @@ from typing import Any
from typing_extensions import TypeIs
def isclass(a: object) -> bool:
pass
def ismethod(a: object) -> TypeIs[float]:
def isfloat(a: object) -> TypeIs[float]:
pass
def isfunction(a: object) -> TypeIs[str]:
def isstr(a: object) -> TypeIs[str]:
pass
def isclassmethod(obj: Any) -> bool:
if ismethod(obj) and obj.__self__ is not None and isclass(obj.__self__): # E: "float" has no attribute "__self__"
return True

def coverage1(obj: Any) -> bool:
if isfloat(obj) and obj.__self__ is not None and isclass(obj.__self__): # E: "float" has no attribute "__self__"
reveal_type(obj) # N: Revealed type is "builtins.float"
return True
reveal_type(obj) # N: Revealed type is "Any"
return False
def coverage(obj: Any) -> bool:
if not (ismethod(obj) or isfunction(obj)):

def coverage2(obj: Any) -> bool:
if not (isfloat(obj) or isstr(obj)):
reveal_type(obj) # N: Revealed type is "Any"
return True
reveal_type(obj) # N: Revealed type is "Union[builtins.float, builtins.str]"
return False
[builtins fixtures/classmethod.pyi]

Expand Down Expand Up @@ -431,7 +436,7 @@ def foobar(x: object):
return
reveal_type(x) # N: Revealed type is "__main__.<subclass of "Foo" and "Bar">"

def foobar_typeguard(x: object):
def foobar_typeis(x: object):
if not is_foo(x) or not is_bar(x):
return
reveal_type(x) # N: Revealed type is "__main__.<subclass of "Foo" and "Bar">"
Expand All @@ -443,30 +448,30 @@ from typing_extensions import TypeIs

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

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

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

[case testTypeIsAsFunctionArg]
from typing import Callable
from typing_extensions import TypeIs

def accepts_typeguard(f: Callable[[object], TypeIs[bool]]): pass
def different_typeguard(f: Callable[[object], TypeIs[str]]): pass
def accepts_typeis(f: Callable[[object], TypeIs[bool]]): pass
def different_typeis(f: Callable[[object], TypeIs[str]]): pass

def with_typeguard(o: object) -> TypeIs[bool]: pass
def with_typeis(o: object) -> TypeIs[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], TypeIs[bool]]"
accepts_typeis(with_typeis)
accepts_typeis(with_bool) # E: Argument 1 to "accepts_typeis" has incompatible type "Callable[[object], bool]"; expected "Callable[[object], TypeIs[bool]]"

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

[case testTypeIsAsGenericFunctionArg]
Expand All @@ -475,15 +480,15 @@ from typing_extensions import TypeIs

T = TypeVar('T')

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

def with_bool_typeguard(o: object) -> TypeIs[bool]: pass
def with_str_typeguard(o: object) -> TypeIs[str]: pass
def with_bool_typeis(o: object) -> TypeIs[bool]: pass
def with_str_typeis(o: object) -> TypeIs[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], TypeIs[bool]]"
accepts_typeis(with_bool_typeis)
accepts_typeis(with_str_typeis)
accepts_typeis(with_bool) # E: Argument 1 to "accepts_typeis" has incompatible type "Callable[[object], bool]"; expected "Callable[[object], TypeIs[Never]]"
[builtins fixtures/tuple.pyi]

[case testTypeIsAsOverloadedFunctionArg]
Expand All @@ -500,10 +505,10 @@ class filter(Generic[_T]):
def __init__(self, function: Callable[[_T], Any]) -> None: pass
def __init__(self, function): pass

def is_int_typeguard(a: object) -> TypeIs[int]: pass
def is_int_typeis(a: object) -> TypeIs[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(is_int_typeis)) # 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]

Expand All @@ -515,15 +520,15 @@ class A: pass
class B(A): pass
class C(B): pass

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

def with_typeguard_a(o: object) -> TypeIs[A]: pass
def with_typeguard_b(o: object) -> TypeIs[B]: pass
def with_typeguard_c(o: object) -> TypeIs[C]: pass
def with_typeis_a(o: object) -> TypeIs[A]: pass
def with_typeis_b(o: object) -> TypeIs[B]: pass
def with_typeis_c(o: object) -> TypeIs[C]: pass

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

[case testTypeIsWithIdentityGeneric]
Expand Down Expand Up @@ -580,18 +585,18 @@ def func(names: Tuple[str, ...]):
from typing_extensions import TypeIs

class Z:
def typeguard1(self, *, x: object) -> TypeIs[int]: # E: TypeIs functions must have a positional argument
def typeis1(self, *, x: object) -> TypeIs[int]: # E: TypeIs functions must have a positional argument
...

@staticmethod
def typeguard2(x: object) -> TypeIs[int]:
def typeis2(x: object) -> TypeIs[int]:
...

@staticmethod
def typeguard3(*, x: object) -> TypeIs[int]: # E: TypeIs functions must have a positional argument
def typeis3(*, x: object) -> TypeIs[int]: # E: TypeIs functions must have a positional argument
...

def bad_typeguard(*, x: object) -> TypeIs[int]: # E: TypeIs functions must have a positional argument
def bad_typeis(*, x: object) -> TypeIs[int]: # E: TypeIs functions must have a positional argument
...

[builtins fixtures/classmethod.pyi]
Expand All @@ -600,17 +605,17 @@ def bad_typeguard(*, x: object) -> TypeIs[int]: # E: TypeIs functions must have
from typing_extensions import TypeIs

class Z:
def typeguard(self, x: object) -> TypeIs[int]:
def typeis(self, x: object) -> TypeIs[int]:
...

def typeguard(x: object) -> TypeIs[int]:
def typeis(x: object) -> TypeIs[int]:
...

n: object
if typeguard(x=n):
if typeis(x=n):
reveal_type(n) # N: Revealed type is "builtins.int"

if Z().typeguard(x=n):
if Z().typeis(x=n):
reveal_type(n) # N: Revealed type is "builtins.int"
[builtins fixtures/tuple.pyi]

Expand All @@ -619,13 +624,13 @@ from typing_extensions import TypeIs

class Y:
@staticmethod
def typeguard(h: object) -> TypeIs[int]:
def typeis(h: object) -> TypeIs[int]:
...

x: object
if Y().typeguard(x):
if Y().typeis(x):
reveal_type(x) # N: Revealed type is "builtins.int"
if Y.typeguard(x):
if Y.typeis(x):
reveal_type(x) # N: Revealed type is "builtins.int"
[builtins fixtures/classmethod.pyi]

Expand All @@ -634,27 +639,27 @@ from typing import overload, Union
from typing_extensions import TypeIs

@overload
def typeguard(x: object, y: str) -> TypeIs[str]:
def typeis(x: object, y: str) -> TypeIs[str]:
...

@overload
def typeguard(x: object, y: int) -> TypeIs[int]:
def typeis(x: object, y: int) -> TypeIs[int]:
...

def typeguard(x: object, y: Union[int, str]) -> Union[TypeIs[int], TypeIs[str]]:
def typeis(x: object, y: Union[int, str]) -> Union[TypeIs[int], TypeIs[str]]:
...

x: object
if typeguard(x=x, y=42):
if typeis(x=x, y=42):
reveal_type(x) # N: Revealed type is "builtins.int"

if typeguard(y=42, x=x):
if typeis(y=42, x=x):
reveal_type(x) # N: Revealed type is "builtins.int"

if typeguard(x=x, y="42"):
if typeis(x=x, y="42"):
reveal_type(x) # N: Revealed type is "builtins.str"

if typeguard(y="42", x=x):
if typeis(y="42", x=x):
reveal_type(x) # N: Revealed type is "builtins.str"
[builtins fixtures/tuple.pyi]

Expand Down Expand Up @@ -726,3 +731,26 @@ class NarrowHolder:


[builtins fixtures/classmethod.pyi]

[case testTypeIsTypeGuardNoSubtyping]
from typing_extensions import TypeGuard, TypeIs
from typing import Callable

def accept_typeis(x: Callable[[object], TypeIs[str]]):
pass

def accept_typeguard(x: Callable[[object], TypeGuard[str]]):
pass

def typeis(x: object) -> TypeIs[str]:
pass

def typeguard(x: object) -> TypeGuard[str]:
pass

accept_typeis(typeis)
accept_typeis(typeguard) # E: Argument 1 to "accept_typeis" has incompatible type "Callable[[object], TypeGuard[str]]"; expected "Callable[[object], TypeIs[str]]"
accept_typeguard(typeis) # E: Argument 1 to "accept_typeguard" has incompatible type "Callable[[object], TypeIs[str]]"; expected "Callable[[object], TypeGuard[str]]"
accept_typeguard(typeguard)

[builtins fixtures/tuple.pyi]