Return type of function overload is ignored if parameter has type Any
Bug Report
mypy ignores the return type of a function if the annotation uses @overload and one of the parameters has type Any.
Possible duplicate of #14995.
To Reproduce https://mypy-play.net/?mypy=1.1.1&python=3.11&gist=5ca80f2b256e4d31a8c588b15702b085
@overload
def toInt(value: int) -> int: ...
@overload
def toInt(value: float) -> int: ...
@overload
def toInt(value: Any) -> int | None: ...
def toInt(value: Any) -> int | None:
try:
return int(value)
except (TypeError, ValueError):
return None
reveal_type(toInt(1))
any_obj: Any = object()
reveal_type(toInt(any_obj))
Expected Behavior
Revealed type is "builtins.int"
Revealed type is "Union[builtins.int, None]"
Actual Behavior Output of mypy
Revealed type is "builtins.int"
Revealed type is "Any"
vs. output of pyright
Type of "toInt(1)" is "int"
Type of "toInt(any_obj)" is "int | None"
Your Environment
- Mypy version used: 1.1.1
- Mypy command-line flags:
--python-version 3.11 - Mypy configuration options from
mypy.ini(and other config files): None - Python version used: 3.11.2
This is generally expected behavior; we infer Any if multiple overloads match and their return types are different. Perhaps in cases like this (where one of the return types is a subtype of another) it would be OK to infer the wider return type, but in general this is likely to lead to false positives.
Thank you for looking into this.
Unfortunately, I still don't understand, how the return type can change from int | None to Any.
I would assume, a function never returns a type that is different from the annotated one.
In that case I would expect an error message such as
Incompatible return value type (got "Any", expected "Optional[int]")
or
Returning Any from function declared to return "Optional[int]"
If I remove the overloads, the type is correctly inferred as Union[builtins.int, None].
@JelleZijlstra shouldn't it infer a union of return types of all matched overloads instead of Any?
shouldn't it infer a union of return types of all matched overloads instead of Any?
That's an option but it is likely annoying in many cases, as inferring a Union generally forces users to do isinstance() checks to narrow down the type.
Consider this example:
from typing import overload, Any
@overload
def f(x: int) -> int: ...
@overload
def f(x: str) -> str: ...
def f(x): ...
def caller1(x: Any):
return f(x) + 1
def caller2(x: int):
return f(x) + 1
If f(x) was inferred to return int | str here as you propose, caller1 would produce an error but caller2 would not, which goes against the general principle that annotating something as Any instead of a more precise type should not introduce new errors.
That's why mypy currently infers Any in this sort of case. If we can come up with a heuristic for inferring a more precise type in cases like the OP's, I'd be open to changing the behavior, but I do think returning Any is the right thing in the general case.
That's a good point. Thinking more about it, the OPs case can be solved by a "most precise match" rule. Since he has an explicit overload for Any, we can select it as the best match when the argument type is any and ignore other overloads. This will give the behavior he wants without breaking your example.