-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Make overload checks more strict when there are multiple 'Any's #5254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make overload checks more strict when there are multiple 'Any's #5254
Conversation
Resolves python#5250 This makes the "multiple overload matches due to Any" even more strict: we now return a non-Any type only if all of the return types are the same.
d0bee85
to
1455530
Compare
As an addendum: my initial solution was to convert the |
@@ -1400,8 +1400,8 @@ a: Any | |||
b: List[Any] | |||
c: List[str] | |||
d: List[int] | |||
reveal_type(f(a)) # E: Revealed type is 'builtins.list[Any]' | |||
reveal_type(f(b)) # E: Revealed type is 'builtins.list[Any]' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH, I don't like this change. I think the original issue only appears when one of the returns is (an explicit or inferred) Any
. In this case List[Any]
is perfectly safe. So a better fix would be to check if there are Any
s among returns and return Any
in this case. Otherwise keep the current logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not so sure anymore if that'll necessarily be safe in all cases though. For example:
from typing import Any, overload, TypeVar, Generic
T = TypeVar('T')
class Wrapper(Generic[T]): pass
class A(Generic[T]):
@overload
def f(self, x: int) -> Wrapper[T]: ...
@overload
def f(self, x: slice) -> Wrapper[A[T]]: ...
def f(self, x): ...
i: Any
a: A[Any]
reveal_type(a.f(i)) # Wrapper[A[Any]], but should be Wrapper[Any] or Any
It's basically the same example from #5250, except I added a Wrapper class around the return types and we get the same sort of problem, except with no top-level Any.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I see, but on the contrary there is another problem with current logic (and can be even worse with proposed):
class B: ...
class C(B): ... # a subclass
@overload
def f(x: C) -> C: ...
@overload
def f(x: B) -> B: ...
x: Any
f(x) # currently and with this change - Any, but B is totally OK
I think a solution that will solve both problem is to return the least precise type of all return (if such a type exists). This is of course a bit larger change, but I think it is a well defined problem. And this PR is not urgent (the current bug only produces very few errors). What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addition: if you prefer it is totally OK to just use heuristics to find if there is the least precise type among all matching returns, if we can't find such, just return Any
. I am fine if we will just cover the above case and the test case I mentioned. (I could imagine a careful treatment of all possibilities may be tedious).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't that algorithm cause a regression with #5232 though? (e.g. suppose we replace B with 'unicode' and C with 'bytes' in your test case)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sigh. Yes, this would be problematic. But at least can we "save" the test case? For example we allow a fallback option if "erased" types (with all args replace with Any
) are the same for all matching returns, then we return the erased type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I think that would probably work. (Or more precisely, I can't think of a way that would break yet, lol)
I'll implementing that change in a bit.
This change also modifies how mypy erases callables. Previously, callables of type 'Callable[[A, B, ...], R]' were erased to 'Callable[[], None]'. This change will now make the erasure be 'Callable[..., Any]', largely on the grounds that it seems more useful.
Another addendum: while I was reviewing the I'm happy to change it back if you think that's best though -- the type erasure code has stood largely untouched for 6 years, and I'm a little worried that changing it now might unexpectedly break something. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me!
Resolves #5250
This makes the "multiple overload matches due to Any" even more strict: we now return a non-Any type only if all of the return types are the same.