-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Type inference engine simplifies too aggressively #3178
Comments
I have a curious inability to understand general rules stated without concrete examples. In this case I could only understand your point after in my head I replaced Summable with float and MySummable with int (and assuming that int is a subtype of float). Then we see |
Agreed. I'm not concerned that there's a hard use case without a workaround. I just noticed that this oversimplification occasionally emerges in various forms, e.g., the undesirable behavior (#3032, #2993 ), or the one-off fixes ( If it's worth / feasible solving at its root by extending the type inference logic somewhat, that's good. If not, then we can just deal with specific issues as they are discovered. Btw, I used |
It looks like replacing an inferred type with a There is actually something that I would like to change also in constraint inference: constrains for instance type variables are currently inferred ignoring declared variance and assuming invariance. |
If I understood this correctly, the idea would be to infer a generic callable type for Here is another, slightly simpler example: from typing import TypeVar
T = TypeVar('T')
def f(c: T) -> Callable[[T], T]:
return ... # how this is implemented is not important here
g = f(1)
reveal_type(g(1)) # ok, type is int
reveal_type(g(object()) # ok, type is object
g('x') # error It's not immediately clear to me how this would help solve some of the other issues such as #3032 and #2993. A more detailed explanation might help. Also, I'd like to see some real-world code where this would make a difference. |
Yes, your example precisely describes what I am proposing. However, I wouldn't limit this to type variables - I'd allow any inferred type to be a range of types with upper and lower bounds. Due to subtyping, we already allow ranges, but only with an upper bound. Therefore, we can represent every type with its upper bound. The lower bound is always (implicitly) So my proposal is to add a lower bound to each type inferred by the inference engine. The current incarnation of Of course, the hard part is not representation, but updating all the logic to work correctly for types with a lower bound. I think though that it will makes the code cleaner / less awkward, and fix some known and unknown bugs. The reason this will solve #3032 is that currently The #2993 is already fixed by #2997, but in that fix I created the I also think #2008 would benefit from this because the main downside of |
An early predecessor to mypy actually used ranges of types in inference, but it caused enough headache with various corner cases that I moved to the current, simpler approach. Your proposal is more general than what I was doing and I suspect that it would be even harder to implement. The type system was also much simpler back then, but it was still a pain to get right. So basically I'm saying that this is unlikely to happen, but I'd still like to understand what cases this could help with -- maybe we can figure out easier ways to get them to work better. Without concrete real-world examples we likely won't reach very useful conclusions, though. I agree that the |
This issue is broader than, but is closely related to, #3032.
Consider this code:
What should mypy infer for the type of
plus_my_s
?The constraints are simple:
T
must be subtype ofSummable
because we use it inplus
; andT
must be supertype ofMySummable
becauseplus_partial
has to acceptmy_s
as an argument. SoT
has lower bound ofMySummable
and upper bound ofSummable
; any type within this range is fine.Here, there's no such thing as the "most informative" ("optimal") type, since in the expression that
plus_my_s
is bound to ,T
appears both as a return type (covariant position) and as an argument type (contravariant). IOW, sometimes it's better to haveT
lower in the type hierarchy, sometimes higher. Therefore, mypy inference engine should not simplify the constraint any further, and just store the constraint itself internally.However, mypy infers
T == MySummable
(and so, the type forplus_my_s
is inferred asdef (MySummable*) -> MySummable*
). As a result, the following perfectly safe code fails type check:even though direct use of
plus
would (of course) be accepted:This could be fixed by supporting (at least internally) types with both upper and lower bounds (rather than only upper bound). A very similar problem came up in
isinstance
inference (#2993), which I fixed in #2997 by definingTypeRange
that can store either a constraint with an upper bound, or a precise type.Alternatively, this could be fixed by refusing to infer types in this situation and asking the user to provide annotation (although, the user is then out of luck if he actually wants to use
partial_plus
with bothSummable
and its subtypes - since we don't let users provide lower bounds for type variables).The text was updated successfully, but these errors were encountered: