pythonpython-typingmypy

mypy error with conditional optional integers in dictionary


I am trying to add type hints to one of my codes. I am using mypy to check that the types declared in my code are consistent. However, I found the following situation that I don't know how to solve: I have a rather complicated dictionary, which in general has type dict[tuple[tuple[int, int], str], str] However, there are a few cases where the int values could be missing and be None. But at least one of the two will be there. Therefore, I have declared the dict as

dict[
  tuple[
    Union[
      tuple[Optional[int], int],
      tuple[int, Optional[int]]],
    str],
  str]

However, this doesn't seem to work well with mypy, since I have the following code:

for (int1, int2), str in dict:
    if int1 is None:
        int2 + 3
    else:
        ...

and mypy is giving me the following output for the line "int2 + 3":

error: Unsupported operand types for + ("None" and "int")  [operator]
note: Left operand is of type "int | None"

I know the code is working since it is not possible that both int1 and int2 are None simultaneously. Thus, since the line will run only when int1 is None, then int2 is guaranteed to be an int. Is my type hint wrong? Or simply mypy is not smart enough to realise this?


Solution

  • As far as I can tell, neither mypy nor PyRight can assign a static type to int1 and int2 that distinguishes between the two sides of the union. As far is it goes, both can be int|None, and therefore knowing that int1 is None does not imply to the checker that int2 is not None. (Roughly speaking, the types of int1 and int2 are not independent of each other, and you need to assign the pair some kind of "joint" type.)

    You're going to have to help the type checker out, and expicitly assert that int2 has type int when you know that to be the case.

    for (int1, int2), str in dict:
        if int1 is None:
            assert int2 is not None
            int2 + 3
        else:
            ...