Reputation: 53
I run mypy with the disallow-untyped-defs option. When I annotate a function type with @overload and omit the annotation in the definition, mypy still produces an error. For me it seems like the function should be considered annotated.
example.py:
from typing import overload
@overload
def f(arg: int) -> int: ...
@overload
def f(arg: str) -> str: ...
def f(arg):
if type(arg) == int:
return 1
elif type(arg) == str:
return "a"
else:
raise ValueError
Command line:
mypy --disallow-untyped-defs example.py
Output:
example.py:7: error: Function is missing a type annotation
Found 1 error in 1 file (checked 1 source file)
Upvotes: 5
Views: 798
Reputation: 64188
This is intended behavior. Mypy wants you to add type annotations to the implementation of your overloads, like so:
from typing import overload, Union
@overload
def f(arg: int) -> int: ...
@overload
def f(arg: str) -> str: ...
def f(arg: Union[int, str]) -> Union[int, str]:
if type(arg) == int:
return 1
elif type(arg) == str:
return "a"
else:
raise ValueError
This way, mypy will still have all the information it needs to successfully type check the body of f.
The reason why mypy doesn't automatically infer the implementation type signature based on the overload type signatures or try type checking the implementation twice using the overload signatures is because the two signatures can often end up being very different from one another.
For example, here's a more complicated example of an overload:
@overload
def zip(/, i1: Iterable[_T1]) -> Iterator[Tuple[_T1]]: ...
@overload
def zip(/, i1: Iterable[_T1], i2: Iterable[_T2]) -> Iterator[Tuple[_T1, _T2]]: ...
@overload
def zip(/, i1: Iterable[_T1], i2: Iterable[_T2],
i3: Iterable[_T3]) -> Iterator[Tuple[_T1, _T2, _T3]]: ...
@overload
def zip(/, i1: Iterable[_T1], i2: Iterable[_T2], i3: Iterable[_T3],
i4: Iterable[_T4]) -> Iterator[Tuple[_T1, _T2, _T3, _T4]]: ...
@overload
def zip(/, i1: Iterable[_T1], i2: Iterable[_T2], i3: Iterable[_T3],
i4: Iterable[_T4], i5: Iterable[_T5]) -> Iterator[Tuple[_T1, _T2, _T3, _T4, _T5]]: ...
@overload
def zip(/, i1: Iterable[Any], i2: Iterable[Any], i3: Iterable[Any],
i4: Iterable[Any], i5: Iterable[Any], i6: Iterable[Any],
*remainder: Iterable[Any]) -> Iterator[Tuple[Any, ...]]: ...
def zip(*iterables: Iterable[Any]) -> Iterator[Tuple[Any, ...]]:
sentinel = object()
iterators = [iter(it) for it in iterables]
while iterators:
result = []
for it in iterators:
elem = next(it, sentinel)
if elem is sentinel:
return
result.append(elem)
yield tuple(result)
It would be quite challenging for mypy to infer what exactly the implementation signature ought to be in this example: the number of arguments don't match up, figuring out what to do with all the TypeVars (keep them? discard them?) is tricky...
These are all problems mypy could theoretically solve, but it's unclear if it really even saves that much time for the user in the long run. In complicated cases like this, the user would actually probably prefer being able to state what exactly the implementation signature is.
(And if a problem is (a) hard and (b) a minor problem, it tends to go unsolved, especially in open source projects.)
So to keep the experience consistent, mypy does not try automatically inferring the implementation signature in every case.
Upvotes: 2