Skip to content

No type inference for dicts in union #6463

Description

@ikonst
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]]")

Activity

  1. gvanrossum commented on Feb 22, 2019

    @gvanrossum
    Member

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

  2. changed the title [-]No type inference for dicts in tuples[/-] [+]No type inference for dicts in union[/+] on Feb 22, 2019
  3. ikonst commented on Feb 22, 2019

    @ikonst
    ContributorAuthor

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

  4. oraluben commented on Feb 25, 2019

    @oraluben

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

    def bar() -> Dict[Union[str, int], Any]:
        return {}
    
  5. ilevkivskyi commented on Feb 25, 2019

    @ilevkivskyi
    Member

    This may be a duplicate of #2164

  6. ikonst commented on Feb 26, 2019

    @ikonst
    ContributorAuthor

    @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
  7. JukkaL commented on Mar 29, 2019

    @JukkaL
    Collaborator

    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.

  8. ruuda commented on Jun 5, 2019

    @ruuda

    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]]]")
    
  9. JosuaKrause commented on Apr 2, 2021

    @JosuaKrause

    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()
    
  10. added a commit that references this issue on Oct 7, 2021
  11. berzi commented on May 24, 2022

    @berzi

    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?

  12. klieret commented on Oct 26, 2022

    @klieret

    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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions