Repository navigation
Clarify what @overload means #253
Description
In mypy (at least) overlapping @overload variants are not an error as long as the return types match. Examples where this has been reported or discussed include:
- Overloaded function signatures overlap with incompatible return types, class and Callable mypy#1941
- No error for identical overloaded functions mypy#1879
- Overloads with identical signatures not an error mypy#1612
- Type inference problem with overloaded mkdtemp mypy#1541
- Any incorrectly inferred for a call to an overloaded function mypy#1322
- Ordering matters for @overload mypy#1270
- Make operator overloading overlapping checks more lenient mypy#1264
There are probably more, but the point should be clear. We should either decide that overloads are tried in order of occurrence until a match is found, or that mypy's current philosophy is correct. Once we have decided we should update PEP 484 to clarify the desired behavior and explain the motivation. (And fix mypy, if needed.)
I'm not sure that it's always possible to have one unique match; e.g. in python/mypy#1322 the problem is that we're calling f(*some_list) and this is deemed a match for both variants. Mypy then cannot deduce a precise return type and falls back to Any.
I could also imagine that unions or type variables could make it hard to unambiguously pick a variant.
I'd also like to point out that the algorithm chosen by a type checker doesn't have to be the same as that chosen at run time -- at run time the overload variants are irrelevant and a single implementation does all the work using e.g. *args and isinstance() (or maybe using functools.singledispatch). But mypy's approach to overlapping is clearly causing a lot of confusion and it would be good to discuss this and write up in the PEP what a type checker should do.
@JukkaL, anything else we should consider here? Do you have a preference?
@matthiaskramm, is this relevant to pytype?
@vlasovskikh, any feedback from the PyCharm team?
Activity
vlasovskikh commentedon Jul 26, 2016
We use the first match for these cases in PyCharm and we don't warn about overlapping signatures. If no matches are found, we infer a "weak" union of all the return types (we don't check for
isinstanceassertions on this union later).matthiaskramm commentedon Jul 26, 2016
pytype tries
@overloadmethods in order of occurrence and picks the first one that matches.We make heavy use of that in (our version of)
__builtin__.pyi, haven't seen it much elsewhere.matthiaskramm commentedon Jul 26, 2016
Btw. I'm not sure I understand the current mypy approach.
In the mypy's / typeshed's
2.7/__builtin__.pyi, we have:Given that the second function is effectively
divmod(a: Union[int, float], b: Union[int, float])(see python/typeshed#270), aren't those signatures overlapping, with different return types, and mypy should report an error?gvanrossum commentedon Jul 26, 2016
JukkaL commentedon Jul 26, 2016
I wrote about how mypy deals with overloads (python/mypy#1941 (comment)). Copied here for convenience:
Mypy lets you vary the return type even if multiple variants match if your signatures are ordered from narrower to more general. For example, this should be fine:
This would be a problem, though:
JukkaL commentedon Jul 26, 2016
Also, if multiple, non-overlapping overload variants can match due to
Anytypes in arguments, mypy will inferAnyas the return type. Mypy doesn't have weak unions.matthiaskramm commentedon Jul 26, 2016
I like the narrow-to-general (topological) ordering approach. That seems to be the primary use case for having overlapping signatures.
(If you have multiple functions matching and an
Anyis to blame, pytype falls back to return typeAny, same as mypy.)JukkaL commentedon Jul 26, 2016
Another thing to consider is the exact rules for deciding whether signatures are overlapping. Let's start with single-argument functions like this:
Here are some rules that mypy follows:
Ais a subclass ofBor vice versa they are overlapping. Otherwise if they are classes they aren't overlapping.List[int]is overlapping withList[str](rationale: empty list).AandBoverlap if any item fromAis overlapping with some item fromB.Anyoverlaps with everything.These things are less clear:
Tuple[int]overlap withTuple[int, str]?Callable[...]overlap with? I think that mypy currently thinks that it can overlap with anything, since a subclass could define__call__, but this is unclear.matthiaskramm commentedon Jul 27, 2016
pytype models empty lists explicitly as
List[nothing], so for us,List[int]andList[str]can be treated as non-empty and not overlapping.JukkaL commentedon Jul 27, 2016
How does pytype treat code like this:
It seems to me that it's generally impossible to infer when a list could be empty if using PEP 484 annotations.
matthiaskramm commentedon Jul 27, 2016
We treat
nothingas ⊥. SoList[int] ∪ List[⊥]≈List[int ∪ ⊥]=List[int].Whenever the type parameter of a container "touches" (is joined with) another type, the type parameter will have that type. Only very few special operations (like
dict.clear) can set the type parameter back tonothing.So in the above example,
g(f1(n))will beint, andg(f2(n))will bestr. (And without return type annotations,f1()will be inferred to have return typeList[int], andf2()will be inferred to have return typeList[str])I think the real point of contention is
Should this match the first signature, or generate an error? pytype currently opts for the latter, even if the type parameter for
Listwere covariant. (So that ⊥⊂int, henceList[⊥]⊂List[int], causing the first signature to "match")dmoisset commentedon Aug 25, 2016
A possible solution that would be more flexible than the current one but still "symmetrical" (i.e. independent on the order of the overloads and detecting conflicts) would be allow any kind of type definitions, but check the overlap when checking the call. That can also detect the problem of a possible subclass of
AandBpassed to an overloadedf(x: A) -> int ; f(x: B) -> strwhich is the current problem with callable.With this approach, when checking a call, you find all the return types for every matching overload definition, and the return type is the narrower one if that exists, and an error otherwise.
29 remaining items
Overhaul overload semantics, remove erasure, add union math
Ensures overloads are ordered from narrow to broad (python#2138)
Differentiate overload stubs and implementations
Ensures overloads are ordered from narrow to broad (python#2138)
saulshanabrook commentedon Feb 11, 2019
@Michael0x2a Sorry if I missed this, but what are the next steps on your work? Do you have a branch with changes in it? I am hitting some overload issues and would like to see if your work could solve them.
ilevkivskyi commentedon Feb 11, 2019
All the proposed changes landed in mypy several months ago, the next step is to write a mini-PEP for this.
Prefer first overload
srittau commentedon Nov 4, 2021
I am closing this for now. From skimming the comments, I believe there needs to be some clarification in documentation on how overloads are handled, but the problem is mostly resolved. Because this issue became overloaded and hard to follow, if there any actionable items still left to do, please open new issues in the appropriate issue trackers.
jace commentedon Sep 7, 2022
From @Michael0x2a's explanation:
I was hoping this meant the following would work:
However, mypy thinks definitions 1 and 2 overlap with 3. If I remove the overloads, downstream type narrowing fails:
hauntsaninja commentedon Sep 7, 2022
The overlapping overload check is basically a lint, so type ignore it if you know what you're doing. mypy will still do the right thing most of the time, but obviously it'll get tricked by stuff like
x: Collection = "asdf"; is_collection(x)