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

No type inference for dicts in union #6463

Closed
ikonst opened this issue Feb 21, 2019 · 10 comments · Fixed by #16122
Closed

No type inference for dicts in union #6463

ikonst opened this issue Feb 21, 2019 · 10 comments · Fixed by #16122
Labels
bug mypy got something wrong false-positive mypy gave an error on correct code priority-1-normal topic-type-variables topic-union-types

Comments

@ikonst
Copy link
Contributor

ikonst commented Feb 21, 2019

from typing import *

def foo() -> Dict[str, Any]:
    return {}

def bar() -> Union[Dict[str, Any], Dict[int, Any]]:
    return {}

In foo, {} is inferred as Dict[str, Any].
In bar, I get:

error: Incompatible return value type (got "Dict[<nothing>, <nothing>]", expected "Union[Dict[str, Any], Dict[int, Any]]")
@gvanrossum
Copy link
Member

I’m sure the cause is the union, not the tuple.

@ikonst ikonst changed the title No type inference for dicts in tuples No type inference for dicts in union Feb 22, 2019
@ikonst
Copy link
Contributor Author

ikonst commented Feb 22, 2019

@gvanrossum thanks, you're right - corrected the issue title and the sample code

@ilevkivskyi ilevkivskyi added bug mypy got something wrong topic-type-variables topic-union-types false-positive mypy gave an error on correct code priority-1-normal labels Feb 22, 2019
@oraluben
Copy link

@ikonst you could try this to have a workaround, though the semantics has changed

def bar() -> Dict[Union[str, int], Any]:
    return {}

@ilevkivskyi
Copy link
Member

This may be a duplicate of #2164

@ikonst
Copy link
Contributor Author

ikonst commented Feb 26, 2019

@oraluben - the actual code is more complex:

from typing import *

from flask import Response

JsonDict = Dict[str, Any]

_FlaskResponse = Union[
    str,
    bytes,
    Callable,
    Response,
]
_FlaskRestfulResponse = Union[
    JsonDict,
    _FlaskResponse,
]
FlaskRestfulMethodReturnType = Union[
    _FlaskRestfulResponse,
    Tuple[Optional[_FlaskRestfulResponse], int],  # (response, status_code)
    Tuple[Optional[_FlaskRestfulResponse], int, Dict[str, str]],  # (response, status_code, headers)
    Tuple[Optional[_FlaskRestfulResponse], Dict[str, str]],  # (response, headers)
]

and then this works:

class MyResource(Resource):
   def get() -> FlaskRestfulMethodReturnType:
       return {}

and this works:

class MyResource(Resource):
   def get() -> FlaskRestfulMethodReturnType:
       return {"foo": "bar"}, 200

but this doesn't:

class MyResource(Resource):
   def get() -> FlaskRestfulMethodReturnType:
       return {}, 200

@JukkaL
Copy link
Collaborator

JukkaL commented Mar 29, 2019

This happens with other invariant containers as well, such as lists. I just encountered an instance where mypy couldn't infer the type of a {} function argument when calling string.Template.substitute, since the type of the argument in the signature is a union.

@ruuda
Copy link

ruuda commented Jun 5, 2019

This also happens with dicts inside a tuple:

from typing import Dict, Tuple, Union

T = Dict[str, str]
U = Tuple[T]

def f() -> U:
    return {},

def g() -> Union[U, U]:
    return {},
10: error: Incompatible return value type (got "Tuple[Dict[<nothing>, <nothing>]]", expected "Union[Tuple[Dict[str, str]], Tuple[Dict[str, str]]]")

@JosuaKrause
Copy link

JosuaKrause commented Apr 2, 2021

You can work around this issue with:

from typing import *

def get_empty_list_str() -> List[str]:
    return []

def get_empty_list_int() -> List[int]:
    return []

foo: Union[List[str], List[int]] = get_empty_list_str()  # fine
bar: Union[List[str], List[int]] = get_empty_list_int()  # fine
baz: Union[List[str], List[int]] = []  # error

i.e., create a function to create an empty container with a defined type.
You can solve the original issue like this (note, it doesn't matter what the type of your constructor function is as long as it is one of the Union types):

from typing import *

def foo() -> Dict[str, Any]:
    return {}

def bar() -> Union[Dict[str, Any], Dict[int, Any]]:
    return foo()

DMRobertson pushed a commit to matrix-org/synapse that referenced this issue Oct 7, 2021
@berzi
Copy link

berzi commented May 24, 2022

This false-positive is also there with unions of lists when assigning an empty list (e.g. var: Union[list[int], list[str]] = [].

The issue is a few years old at this point but the case is not obscure enough that it would only occur in very few codebases. What's the status on this?

@klieret
Copy link

klieret commented Oct 26, 2022

Thanks @JosuaKrause for the work around. This actually can be done very concisely in newer python versions by using list[str](), dict[str, int](), etc. as constructors for a typed empty iterators:

foo: list[str] | list[int] = list[str]()  # fine
bar: list[str] | list[int] = list[int]()  # fine
baz:  list[str] | list[int] = []  # error

hauntsaninja pushed a commit that referenced this issue Sep 26, 2023
Fixes #230
Fixes #6463
I bet it fixes some other duplicates, I closed couple yesterday, but
likely there are more.

This may look a bit ad-hoc, but after some thinking this now starts to
make sense to me for two reasons:
* Unless I am missing something, this should be completely safe.
Special-casing only applies to inferred types (i.e. empty collection
literals etc).
* Empty collections _are_ actually special. Even if we solve some
classes of issues with more principled solutions (e.g. I want to re-work
type inference against unions in near future), there will always be some
corner cases involving empty collections.

Similar issues keep coming, so I think it is a good idea to add this
special-casing (especially taking into account how simple it is, and
that it closer some "popular" issues).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong false-positive mypy gave an error on correct code priority-1-normal topic-type-variables topic-union-types
Projects
None yet
9 participants