python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.44k stars 2.82k forks source link

mypy incorrectly tags function argument TypeVar with return type when called from a caller with the same signature #16924

Open satyanash opened 8 months ago

satyanash commented 8 months ago

Bug Report

mypy incorrectly tags a function argument of type Mapping[K, V] where K and V are TypeVar with the return value of the caller function.

To Reproduce

The below small program demonstrates the issue:

from typing import Dict, Mapping, TypeVar, Union

K = TypeVar("K")
V = TypeVar("V")
K2 = TypeVar("K2")
V2 = TypeVar("V2")

def func(one: Dict[K, V], two: Mapping[K2, V2]) -> Dict[Union[K, K2], Union[V, V2]]:
    reveal_type(one)
    reveal_type(two)
    return {}

def caller(arg1: Mapping[K, V], arg2: Mapping[K2, V2]) -> Dict[Union[K, K2], Union[V, V2]]:
    reveal_type(arg1)
    reveal_type(arg2)
    _arg1 = arg1 if isinstance(arg1, dict) else dict(arg1)
    return func(_arg1, arg2)

Expected Behavior

mypy should succeed without an error.

$ mypy --strict break_mypy.py
break_mypy.py:10: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:11: note: Revealed type is "typing.Mapping[K2`-3, V2`-4]"
break_mypy.py:16: note: Revealed type is "typing.Mapping[K`-1, V`-2]"
break_mypy.py:17: note: Revealed type is "typing.Mapping[K2`-3, V2`-4]"
Success: no issues found in 1 source file

Actual Behavior

mypy complains with a very weird error, even though the revealed types are correct

$ mypy --strict break_mypy.py
break_mypy.py:10: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:11: note: Revealed type is "typing.Mapping[K2`-3, V2`-4]"
break_mypy.py:16: note: Revealed type is "typing.Mapping[K`-1, V`-2]"
break_mypy.py:17: note: Revealed type is "typing.Mapping[K2`-3, V2`-4]"
break_mypy.py:19: error: Argument 2 to "func" has incompatible type "Mapping[K2, V2]"; expected "Mapping[Union[K, K2], Union[V, V2]]"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

More Info

Note, if I change the type signature of the caller, it ends up changing the type signature of the called function as well:

from typing import Dict, Mapping, TypeVar, Union

K = TypeVar("K")
V = TypeVar("V")
K2 = TypeVar("K2")
V2 = TypeVar("V2")

def func(one: Dict[K, V], two: Mapping[K2, V2]) -> Dict[Union[K, K2], Union[V, V2]]:
    reveal_type(one)
    reveal_type(two)
    return {}

def caller(arg1: Mapping[K, V], arg2: Mapping[K2, V2]) -> None:
    reveal_type(arg1)
    reveal_type(arg2)
    _arg1 = arg1 if isinstance(arg1, dict) else dict(arg1)
    func(_arg1, arg2)

will return:

$ mypy --strict break_mypy.py
break_mypy.py:10: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:11: note: Revealed type is "typing.Mapping[K2`-3, V2`-4]"
break_mypy.py:16: note: Revealed type is "typing.Mapping[K`-1, V`-2]"
break_mypy.py:17: note: Revealed type is "typing.Mapping[K2`-3, V2`-4]"
Success: no issues found in 1 source file

Your Environment

satyanash commented 8 months ago

This is not specific to Mapping and is reproducible with Dict as well. Looks like TypeVar along with having two functions with the same return type will cause this issue.

Below program reproduces this issue with Dict, instead of Mapping

from typing import Dict, TypeVar, Union

K = TypeVar("K")
V = TypeVar("V")
K2 = TypeVar("K2")
V2 = TypeVar("V2")

def func(one: Dict[K, V], two: Dict[K2, V2]) -> Dict[Union[K, K2], Union[V, V2]]:
    reveal_type(one)
    reveal_type(two)
    return {}

def caller(arg1: Dict[K, V], arg2: Dict[K2, V2]) -> Dict[Union[K, K2], Union[V, V2]]:
    reveal_type(arg1)
    reveal_type(arg2)
    _arg1 = arg1 if isinstance(arg1, dict) else dict(arg1)
    return func(_arg1, arg2)

returns

$ mypy --strict break_mypy.py
break_mypy.py:10: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:11: note: Revealed type is "builtins.dict[K2`-3, V2`-4]"
break_mypy.py:16: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:17: note: Revealed type is "builtins.dict[K2`-3, V2`-4]"
break_mypy.py:19: error: Argument 1 to "func" has incompatible type "Dict[K, V]"; expected "Dict[Union[K, K2], Union[V, V2]]"  [arg-type]
break_mypy.py:19: error: Argument 2 to "func" has incompatible type "Dict[K2, V2]"; expected "Dict[Union[K, K2], Union[V, V2]]"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)
satyanash commented 8 months ago

This is reproducible on newest mypy and python versions as well:

$ mypy --version
mypy 1.8.0 (compiled: yes)
$ python --version
Python 3.11.4
$ mypy --strict break_mypy.py
break_mypy.py:10: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:11: note: Revealed type is "builtins.dict[K2`-3, V2`-4]"
break_mypy.py:16: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:17: note: Revealed type is "builtins.dict[K2`-3, V2`-4]"
break_mypy.py:19: error: Argument 1 to "func" has incompatible type "dict[K, V]"; expected "dict[K | K2, V | V2]"  [arg-type]
break_mypy.py:19: error: Argument 2 to "func" has incompatible type "dict[K2, V2]"; expected "dict[K | K2, V | V2]"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)