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

Narrow types after 'in' operator #4072

Merged
merged 6 commits into from Oct 13, 2017
Jump to file or symbol
Failed to load files and symbols.
+28 −2
Diff settings

Always

Just for now

Viewing a subset of changes. View all
View
@@ -2875,7 +2875,19 @@ def remove_optional(typ: Type) -> Type:
def builtin_item_type(tp: Type) -> Optional[Type]:
# This is only OK for built-in containers, where we know the behavior of __contains__.
"""Get the item type of a builtin container.
If 'tp' is not one of the built containers (these includes NamedTuple and TypedDict)
or if the container is not parameterized (like List or List[Any])
return None. This function is used to narrow optional types in situations like this:
x: Optional[int]
if x in (1, 2, 3):
x + 42 # OK
Note: this is only OK for built-in containers, where we know the behavior
of __contains__.
"""
if isinstance(tp, Instance):
if tp.type.fullname() in ['builtins.list', 'builtins.tuple', 'builtins.dict',
'builtins.set', 'builtins.frozenset']:
@@ -1788,6 +1788,20 @@ else:
[builtins fixtures/list.pyi]
[out]
[case testNarrowTypeAfterInListOfOptional]
# flags: --strict-optional
from typing import List, Optional
x: List[Optional[int]]

This comment has been minimized.

@JukkaL

JukkaL Oct 13, 2017

Collaborator

Here's another special case: What if the container is a nested one, say List[List[x]], where x might be Any? How do we deal with various item types, such as List[int] and List[Any]?

@JukkaL

JukkaL Oct 13, 2017

Collaborator

Here's another special case: What if the container is a nested one, say List[List[x]], where x might be Any? How do we deal with various item types, such as List[int] and List[Any]?

This comment has been minimized.

@ilevkivskyi

ilevkivskyi Oct 13, 2017

Collaborator

a) If the container is List[Any], then we do nothing (there is a test for this).
b) If the container is List[List[Any]] we narrow the type (provided there is an overlap, this is consistent with how == currently treated), for example:

x: Optional[int]
lst: Optional[List[int]]
nested: List[List[Any]]
if lst in nested:
    reveal_type(lst) # List[int]
if x in nested:
    reveal_type(x) # Optional[int]

There is already a test for non-overlapping items int vs str. I will add one more test for other (nested) types?

@ilevkivskyi

ilevkivskyi Oct 13, 2017

Collaborator

a) If the container is List[Any], then we do nothing (there is a test for this).
b) If the container is List[List[Any]] we narrow the type (provided there is an overlap, this is consistent with how == currently treated), for example:

x: Optional[int]
lst: Optional[List[int]]
nested: List[List[Any]]
if lst in nested:
    reveal_type(lst) # List[int]
if x in nested:
    reveal_type(x) # Optional[int]

There is already a test for non-overlapping items int vs str. I will add one more test for other (nested) types?

This comment has been minimized.

@ilevkivskyi

ilevkivskyi Oct 13, 2017

Collaborator

OK, added one more test.

@ilevkivskyi

ilevkivskyi Oct 13, 2017

Collaborator

OK, added one more test.

y: Optional[int]
if y not in x:
reveal_type(y) # E: Revealed type is 'Union[builtins.int, builtins.None]'
else:
reveal_type(y) # E: Revealed type is 'Union[builtins.int, builtins.None]'
[builtins fixtures/list.pyi]
[out]
[case testNarrowTypeAfterInListNonOverlapping]
# flags: --strict-optional
from typing import List, Optional
@@ -1836,7 +1850,7 @@ else:
[case testNarrowTypeAfterInDict]
# flags: --strict-optional
from typing import Dict, Optional
x: Dict[str, str]
x: Dict[str, int]
y: Optional[str]
if y in x:
ProTip! Use n and p to navigate between commits in a pull request.