-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Introduce a helper function to map types to protocols + fix for dict kwargs false positive #20419
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
base: master
Are you sure you want to change the base?
Conversation
This comment has been minimized.
This comment has been minimized.
mypy/maptype.py
Outdated
| from mypy.subtypes import is_subtype | ||
| from mypy.typeops import get_all_type_vars |
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.
had to put these there due to circular import issues
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.
This function should definitely not be in this module (ideally we shouldn't even have the existing typeops import below, but that is a separate story).
Also the scope of this function is misleadingly broad. It should probably accept a TypeInfo as target (which probably must be a protocol), and then use fill_typevars(...) as a constraint inference target.
Finally, it is worth doing some performance measurements, we don't want any visible slow-down for something that is only needed for rare edge cases.
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 put it there since the similar map_instance_to_supertype is in that module. Which module would be appropriate?
Regarding the scope, is this due to design philosophy? It seems useful that one could test for instance if something is a Mapping[T, SomeFixedType] for some T <: str.
If we were to use TypeInfo + fill_typevars, how do you suggest passing the necessary extra information that T <: str? I don't really understand what advantage making target a TypeInfo would give here, it seems like it would make things more complicated.
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.
If we were to use
TypeInfo+fill_typevars, how do you suggest passing the necessary extra information thatT <: str?
Very simple: you don't pass it. Say I want to check, can Foo be some kind of Iterable, this functions may say "yes, it is Iterable[str]". Then you decide whether this solution is something that works for you or not.
This comment has been minimized.
This comment has been minimized.
|
Just FYI, #20435 could also benefit from this helper function to map to |
05e0a41 to
8cc1c29
Compare
|
OK, so I renamed the function to I ran some minimal benchmarks with
The first two show no difference (most time likely spent on setup/teardown), the last one show roughly a 10% performance degradation. |
This comment has been minimized.
This comment has been minimized.
|
A few more changes:
|
This comment has been minimized.
This comment has been minimized.
ilevkivskyi
left a comment
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.
Here are some more comments.
| """ | ||
|
|
||
| # 1. get type vars of target | ||
| tvars = get_all_type_vars(target) |
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.
A caller may accidentally pass a type with some unrelated type variables (for example inside a generic function or class). As I said before, target should really be a TypeInfo.
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.
The applytype functions should check the validity of solution and give an appropriate error (i.e. bad solution vs no solution).
Very simple: you don't pass it. Say I want to check, can Foo be some kind of Iterable, this functions may say "yes, it is Iterable[str]". Then you decide whether this solution is something that works for you or not.
I believe this may be an incorrect procedure in general; it seems to implicitly assume that if S={A[X] <: B[X, Y, Z], X <: Y} is solvable, then a solution can be found by first solving X⁎=solve({A[X] <: B[X, Y, Z]}, X) and then considering X⁎ & Y if necessary.
This seems to work in simple cases, for instance, say we need to solve X <: Y such that list[X] <: Iterable[Z]. The solver will return X⁎ = Z. If Z <: Y, then X⁎=Z is fine, and otherwise X⁎&Y / meet(X⁎, Y) is a solution.
But I believe we need to include the upper bound X<:Y in general to find a correct solution in the first place. Take for instance X <: int and list[X] <: list[str] | list[int]. Without the X <: int constraint, the solver may pick1 X⁎=str, but neither str not str & int (↯) are a proper solution of the joint constraints {list[X] <: list[str] | list[int], X <: int}. The proper solution is X⁎=int.
The recursive case when B explicitly depends on X may be problematic as well.
Footnotes
-
in fact, mypy seems to eagerly select the constraints here: https://github.com/python/mypy/blob/0cc21d99b8ab0c587fca66b697a50e6b59abf06d/mypy/constraints.py#L399-L408 ↩
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 opened #20516 with a bug report that reproduces this incorrect behavior.
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.
That issue has literary nothing to do with the current discussion (it is actually a good illustration of the confusion I mentioned). In those examples you have in #20516, X is not something that needs to be solved for. It is simply an unrelated type like anything else (for example, exactly same problem appears if I have class X(int): ...). In that issue we solve for T in def list[T](...) -> ..., that one doesn't have any non-trivial upper bound.
Anyway, do you really think you can convince someone who has been working on mypy for 10 years that you know better how it should work?
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.
Anyway, do you really think you can convince someone who has been working on mypy for 10 years that you know better how it should work?
No, but I'd really like to understand why it should work that way, and what is the error in my thought process in #20419 (comment), so I can learn and grow.
mypy/maptype.py
Outdated
| from mypy.subtypes import is_subtype | ||
| from mypy.typeops import get_all_type_vars |
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.
If we were to use
TypeInfo+fill_typevars, how do you suggest passing the necessary extra information thatT <: str?
Very simple: you don't pass it. Say I want to check, can Foo be some kind of Iterable, this functions may say "yes, it is Iterable[str]". Then you decide whether this solution is something that works for you or not.
Co-authored-by: Ivan Levkivskyi <levkivskyi@gmail.com>
|
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
Fixes: #20424
Following my comments in #20416, this PR introduces a new helper function
as_typethat can be used to determine whether a type can be matched to a certain protocol or not.As an example application, I fix a bug in
is_valid_keyword_var_argthat stems from checkingkwargs <: SupportsKeyAndGetItem[str, Any]This check is too eager, because
SupportsKeyAndGetItemis invariant in the key type, it will produce a false positive whenkwargsis for instancedict[Literal["foo", "bar"], int]. The correct test is:Does there exist
T <: strso thatkwargs <: SupportsKeyAndGetItem[T, Any]which can be checked with the new helper function.
Updated tests
I updated
testLiteralKwargsto test:dictargumentMappingargumentSupportsKeyAndGetitemargument