-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Fix function is_protocol_implementation
for quite similar protocol classes
#10308
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
Fix function is_protocol_implementation
for quite similar protocol classes
#10308
Conversation
… case that both the potential subtype and the potential supertype are protocols. The simple check only relies on the subset relationship of the member names. Eventually, it avoids the more complicated recursive search for potential discrepancies. I do not know if it positively affects performance, but it fixes python#9771.
…it__` a little more together
Diff from mypy_primer, showing the effect of this PR on open source code: pandas (https://github.com/pandas-dev/pandas.git)
+ pandas/core/frame.py:6045: error: Incompatible types in assignment (expression has type "Iterable[Any]", variable has type "Union[Hashable, Sequence[Hashable], None]") [assignment]
+ pandas/core/frame.py:6054: error: Unsupported right operand type for in ("Union[Index, Hashable]") [operator]
black (https://github.com/psf/black.git)
+ src/black/__init__.py:307:43: error: Argument 1 to "__call__" of "_lru_cache_wrapper" has incompatible type "Iterable[str]"; expected "Hashable"
|
This was found by python/mypy#10308
I'm not sure how, but this PR actually fixes a more basic bug. This code passes on master: from typing import Hashable, Iterable
def f(x: Hashable) -> None:
pass
def g(x: Iterable[str]) -> None:
f(x) But with your PR it correctly produces |
This was found by python/mypy#10308
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 think Jelle's fixed issue is explained by:
Line 598 in 9ab622c
method = info.get_method(name) __hash__
method in its MRO.- The protocol_members attribute doesn't look at object
Line 2497 in fffbe88
for base in self.mro[:-1]: # we skip "object" since everyone implements it
I'm not the most familiar with this code, but your write up makes sense, this passes tests and fixes a crash multiple people have run into, so I'll merge soon if no one else chimes in. Thanks! :-)
I feel like we should add an explicit test for the Hashable/Iterable case to make sure it doesn't regress. |
I tried to add the following regression test you suggested: [case testHashable]
from typing import Hashable, Iterable
def f(x: Hashable) -> None:
pass
def g(x: Iterable[str]) -> None:
f(x) # E: Argument 1 to "f" has incompatible type "Iterable[str]"; expected "Hashable"
[typing fixtures/typing-full.pyi] Additional content of typing-full.pyi: @runtime_checkable
class Hashable(Protocol, metaclass=ABCMeta):
@abstractmethod
def __hash__(self) -> int: pass However, this test passes both with and without the PR. Am I missing something? |
I'm guessing object doesn't have |
See the pull request python#10308 for further information.
Yes, you are right. Defining a fixture for such an essential feature did not come into my mind. I added the minimal stub file |
Diff from mypy_primer, showing the effect of this PR on open source code: pandas (https://github.com/pandas-dev/pandas.git)
+ pandas/core/frame.py:6045: error: Incompatible types in assignment (expression has type "Iterable[Any]", variable has type "Union[Hashable, Sequence[Hashable], None]") [assignment]
+ pandas/core/frame.py:6054: error: Unsupported right operand type for in ("Union[Index, Hashable]") [operator]
|
Thanks for adding the test! |
Diff from mypy_primer, showing the effect of this PR on open source code: pandas (https://github.com/pandas-dev/pandas.git)
+ pandas/core/frame.py:6045: error: Incompatible types in assignment (expression has type "Iterable[Any]", variable has type "Union[Hashable, Sequence[Hashable], None]") [assignment]
+ pandas/core/frame.py:6054: error: Unsupported right operand type for in ("Union[Index, Hashable]") [operator]
|
Thank you! |
Fixes #9771
Method
infer_constraints_from_protocol_members
of classConstraintBuilderVisitor
raises an assertion for the above code when we passP2
asinstance
andP1
astemplate
to it (inline comment: The above is safe since at this point we know that 'instance' is a subtype of (erased) 'template', therefore it defines all protocol members).So, the actual error happens before. Method
visit_instance
of classConstraintBuilderVisitor
should never apply the methodinfer_constraints_from_protocol_members
onP1
andP2
. It nevertheless does so due to functionis_protocol_implementation
indicatingP2
actually implementsP1
.In my naive understanding, the problem seems to lie in the recursive approach underlying function
is_protocol_implementation
. This function first analyses methoda
to check ifP2
is a subclass ofP1
. Therefore, it needs to check ifP1
is a subclass ofP2
. This can result in a situation where the "'assuming' structural subtype matrix" is already filled withP1
andP2
.is_protocol_implementation
then returnsTrue
(for avoiding infinite recursion in other cases) without checking any other members, which is incorrect for the given case.My simple solution is first to check if all members of
P1
(the potential supertype) define a subset ofP2
(the potential subtype). If this does not hold, we do not need to perform the complex recursive approach and can returnFalse
immediately. Maybe not the most general solution, but it works on the additional testtestTwoUncomfortablyIncompatibleProtocolsWithoutRunningInIssue9771
(I can't think of a better name...) and seems not to impact any other tests.