Skip to content

Commit

Permalink
Recognize Hashable as a real protocol (#11802)
Browse files Browse the repository at this point in the history
When this piece of code was checked:

```python
from typing import Awaitable, Hashable, Union, Tuple, List

obj: Union[Tuple[int], List[int]]
if isinstance(obj, Hashable):
    reveal_type(obj)
```

Mypy revealed that `Hashable` is `() -> object`. It happened, because [`is_subtype(explicit_type, default_ret_type, ignore_type_params=True)`](https://github.com/python/mypy/blob/56684e43a14e3782409c47e99bb47191d631a3de/mypy/typeops.py#L130) was `True`, where `explicit_type=object` and `default_ret_type=Hashable`. It happened because `object` has `__hash__` method.

The only thing that popped out of my head is to simply exclude protocols from this condition.
I guess that we might double check protocols with `__new__` and `__init__` to be sure. But, I am not able to think of proper test cases for this. Any ideas? Or is my single test good enough?

I am adding `pythoneval` test, because of the complexity in how `Hashable` is defined and analyzed in real life.

Closes #11799
  • Loading branch information
sobolevn authored Mar 24, 2022
1 parent fa9921a commit a78e60c
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 0 deletions.
4 changes: 4 additions & 0 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance,
explicit_type = init_ret_type if is_new else orig_self_type
if (
isinstance(explicit_type, (Instance, TupleType))
# We have to skip protocols, because it can can be a subtype of a return type
# by accident. Like `Hashable` is a subtype of `object`. See #11799
and isinstance(default_ret_type, Instance)
and not default_ret_type.type.is_protocol
# Only use the declared return type from __new__ or declared self in __init__
# if it is actually returning a subtype of what we would return otherwise.
and is_subtype(explicit_type, default_ret_type, ignore_type_params=True)
Expand Down
13 changes: 13 additions & 0 deletions test-data/unit/pythoneval.test
Original file line number Diff line number Diff line change
Expand Up @@ -1575,6 +1575,19 @@ reveal_type(x) # Revealed type is "collections.OrderedDict[builtins.str, builti
[out]
_testTypingExtensionsOrderedDictAlias.py:3: note: Revealed type is "collections.OrderedDict[builtins.str, builtins.str]"

[case testSpecialTypingProtocols]
# flags: --warn-unreachable
from typing import Awaitable, Hashable, Union, Tuple, List

obj: Union[Tuple[int], List[int]]
if isinstance(obj, Hashable):
reveal_type(obj)
if isinstance(obj, Awaitable):
reveal_type(obj)
[out]
_testSpecialTypingProtocols.py:6: note: Revealed type is "Tuple[builtins.int]"
_testSpecialTypingProtocols.py:8: error: Statement is unreachable

[case testEnumValueWithPlaceholderNodeType]
# https://github.com/python/mypy/issues/11971
from enum import Enum
Expand Down

0 comments on commit a78e60c

Please sign in to comment.