Skip to content

Commit a78e60c

Browse files
authored
Recognize Hashable as a real protocol (#11802)
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
1 parent fa9921a commit a78e60c

File tree

2 files changed

+17
-0
lines changed

2 files changed

+17
-0
lines changed

mypy/typeops.py

+4
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance,
107107
explicit_type = init_ret_type if is_new else orig_self_type
108108
if (
109109
isinstance(explicit_type, (Instance, TupleType))
110+
# We have to skip protocols, because it can can be a subtype of a return type
111+
# by accident. Like `Hashable` is a subtype of `object`. See #11799
112+
and isinstance(default_ret_type, Instance)
113+
and not default_ret_type.type.is_protocol
110114
# Only use the declared return type from __new__ or declared self in __init__
111115
# if it is actually returning a subtype of what we would return otherwise.
112116
and is_subtype(explicit_type, default_ret_type, ignore_type_params=True)

test-data/unit/pythoneval.test

+13
Original file line numberDiff line numberDiff line change
@@ -1575,6 +1575,19 @@ reveal_type(x) # Revealed type is "collections.OrderedDict[builtins.str, builti
15751575
[out]
15761576
_testTypingExtensionsOrderedDictAlias.py:3: note: Revealed type is "collections.OrderedDict[builtins.str, builtins.str]"
15771577

1578+
[case testSpecialTypingProtocols]
1579+
# flags: --warn-unreachable
1580+
from typing import Awaitable, Hashable, Union, Tuple, List
1581+
1582+
obj: Union[Tuple[int], List[int]]
1583+
if isinstance(obj, Hashable):
1584+
reveal_type(obj)
1585+
if isinstance(obj, Awaitable):
1586+
reveal_type(obj)
1587+
[out]
1588+
_testSpecialTypingProtocols.py:6: note: Revealed type is "Tuple[builtins.int]"
1589+
_testSpecialTypingProtocols.py:8: error: Statement is unreachable
1590+
15781591
[case testEnumValueWithPlaceholderNodeType]
15791592
# https://github.com/python/mypy/issues/11971
15801593
from enum import Enum

0 commit comments

Comments
 (0)