Skip to content

Support narrowing Union of List types via index and isinstance #9362

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

Closed
sbdchd opened this issue Aug 26, 2020 · 4 comments
Closed

Support narrowing Union of List types via index and isinstance #9362

sbdchd opened this issue Aug 26, 2020 · 4 comments
Labels

Comments

@sbdchd
Copy link
Contributor

sbdchd commented Aug 26, 2020

It would be nice to be able to narrow a union of two different list types via indexing into the array and using an isinstance check

from typing import Union, List

x: Union[List[str], List[int]] = [10]

if isinstance(x[0], str):
    reveal_type(x)
    # expected: builtins.list[builtins.str]
    # actual: Union[builtins.list[builtins.str], builtins.list[builtins.int]]
else:
    reveal_type(x)
    # expected: builtins.list[builtins.int]
    # actual: Union[builtins.list[builtins.str], builtins.list[builtins.int]]

One caveat is that the array could be empty, but let's assume we make an emptiness check before indexing

@gvanrossum
Copy link
Member

This is a special case in mypy. If you rewrite the initialization like this it works:

x: Union[List[str], List[int]]
x = [10]

Honestly I don't think this is documented very well -- you might be able to infer this from Explicit types for variables but it could be more explicit.

@sbdchd
Copy link
Contributor Author

sbdchd commented Aug 27, 2020

I think I may have made my example too simple.

In my current use case I parse some data from a network response which is a union of two different list types: Union[List[List[str]], List[SearchResult]]

I'm hoping to refine this response via indexing and an isinstance check

from typing import Union, List
from dataclasses import dataclass


@dataclass
class SearchResult:
    id: str
    score: float
    match: str


def get_search_results() -> Union[List[List[str]], List[SearchResult]]:
    ...


x = get_search_results()

if isinstance(x[0], SearchResult):
    reveal_type(x)
    # expected: builtins.list[main.SearchResult]
    # actual: Union[builtins.list[builtins.list[builtins.str]], builtins.list[main.SearchResult]]
else:
    reveal_type(x)
    # expected: builtins.list[builtins.str]
    # actual: Union[builtins.list[builtins.list[builtins.str]], builtins.list[main.SearchResult]]

which I think can be simplified to the following as a test case

from typing import Union, List


def foo() -> Union[List[str], List[int]]:
    ...


x = foo()

if isinstance(x[0], str):
    reveal_type(x)
    # expected: builtins.list[builtins.str]
    # actual: Union[builtins.list[builtins.str], builtins.list[builtins.int]]
else:
    reveal_type(x)
    # expected: builtins.list[builtins.int]
    # actual: Union[builtins.list[builtins.str], builtins.list[builtins.int]]

@gvanrossum
Copy link
Member

Ah, okay. I think this is a feature request.

sbdchd added a commit to sbdchd/mypy that referenced this issue Aug 30, 2020
Allows for narrowing a union of list types by indexing into the list
and checking the type of an element.

mypy already narrows the specific index expression.
Since mypy already narrows the index expression's type, we use the type
to compare against the type arguments of the lists and generate a new
type that is narrower than the original.

```
from typing import Union, List

a: Union[List[List[int]], List[int]]

# before
if isinstance(a[0], list):
    reveal_type(a) # N: Revealed type is 'Union[builtins.list[builtins.list[builtins.int]], builtins.list[builtins.int]]'
else:
    reveal_type(a) # N: Revealed type is 'Union[builtins.list[builtins.list[builtins.int]], builtins.list[builtins.int]]'

# after
if isinstance(a[0], list):
    reveal_type(a) # N: Revealed type is 'builtins.list[builtins.list[builtins.int]]'
else:
    reveal_type(a) # N: Revealed type is 'builtins.list[builtins.int]'
```

fixes: python#9362
@hauntsaninja
Copy link
Collaborator

Duplicate of #7339 and #2458

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants