pytype icon indicating copy to clipboard operation
pytype copied to clipboard

Inferred type info is lost when constructing tuple

Open dominickpastore opened this issue 3 years ago • 0 comments

I'm not sure the title of this issue is very good. I had some trouble deciding what to call it. But hopefully this small example will make the issue more clear:

from typing import Optional, Dict, Tuple, Union, Any

def foo(d: Dict[Union[str, Tuple[str, str]], Any],
        opt_k: Optional[str],
        k: str):
    reveal_type(opt_k)  # reveals Optional[str]

    if opt_k is None:
        d[k] = object()
        reveal_type(opt_k)  # reveals None
        return

    reveal_type(opt_k)  # reveals str
    reveal_type((opt_k, k))  # reveals Tuple[Optional[str], str] (too broad)
    d[(opt_k, k)] = object()  # container-types-mismatch (false positive)

Here is the output from pytype:

File "/home/dominickpastore/test.py", line 6, in foo: Optional[str] [reveal-type]
File "/home/dominickpastore/test.py", line 9, in foo: None [reveal-type]
File "/home/dominickpastore/test.py", line 11, in foo: str [reveal-type]
File "/home/dominickpastore/test.py", line 12, in foo: Tuple[Optional[str], str] [reveal-type]
File "/home/dominickpastore/test.py", line 13, in foo: New container type for d does not match type annotation [container-type-mismatch]
  Container: Dict[_K, _V]
  Allowed contained types (from annotation Dict[Union[str, Tuple[str, str]], Any]):
    _K: Union[Tuple[str, str], str]
  New contained types:
    _K: Tuple[Optional[str], str]

As we can see, it's correctly inferring that opt_k must not be None after the if block. Yet, if we make a tuple out of it, it seems to forget that it knows that. And if we try to use that tuple as a key in the dictionary, we get a false positive.

It seems to be a bit more complex than that, though. If we were to try to .append the tuple to a list instead, like this, the tuple's type is still revealed as Tuple[Optional[str], str], but there's no false positive:

from typing import Optional, List, Tuple, Union
  
def foo(l: List[Union[str, Tuple[str, str]]],
        opt_k: Optional[str],
        k: str):
    reveal_type(opt_k)  # reveals Optional[str]
    if opt_k is None:
        l.append(k)
        reveal_type(opt_k)  # reveals None
        return
    reveal_type(opt_k)  # reveals str
    reveal_type((opt_k, k))  # reveals Tuple[Optional[str], str] (too broad)
    l.append((opt_k, k))  # No false positive

dominickpastore avatar Jan 03 '23 08:01 dominickpastore