Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1245,9 +1245,21 @@ def f(self) -> A: ...
left = mypy.typeops.tuple_fallback(left)
# We need to record this check to generate protocol fine-grained dependencies.
type_state.record_protocol_subtype_check(left.type, right.type)
# Iterator instances inherit object.__hash__ at runtime, but Iterator is a
# protocol and therefore doesn't expose that inherited member structurally.
# Keep the special handling limited to the standard Hashable protocol: a
# concrete unhashable iterator must still be rejected. See #21813.
if is_named_instance(left, "typing.Iterator") and is_named_instance(right, "typing.Hashable"):
return True
# nominal subtyping currently ignores '__init__' and '__new__' signatures
members_not_to_check = {"__init__", "__new__"}
members_not_to_check.update(skip)
# Iterator instances inherit object.__hash__ at runtime, but Iterator is a
# protocol and therefore doesn't expose that inherited member structurally.
# Keep the special handling limited to the standard Hashable protocol: a
# concrete unhashable iterator must still be rejected. See #21813.
if is_named_instance(left, "typing.Iterator") and is_named_instance(right, "typing.Hashable"):
members_not_to_check.add("__hash__")
# Trivial check that circumvents the bug described in issue 9771:
if left.type.is_protocol:
members_right = set(right.type.protocol_members) - members_not_to_check
Expand Down
16 changes: 15 additions & 1 deletion test-data/unit/check-protocols.test
Original file line number Diff line number Diff line change
Expand Up @@ -1025,14 +1025,28 @@ main:15: note: def [T2] a(self, other: P1[T2]) -> Any

[case testHashable]

from typing import Hashable, Iterable
from typing import Hashable, Iterable, Iterator

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"

def h(x: Iterator[str]) -> None:
f(x)

class UnhashableIterator(Iterator[str]):
__hash__ = None # type: ignore[assignment]

def __next__(self) -> str:
return ""

def k(x: UnhashableIterator) -> None:
f(x) # E: Argument 1 to "f" has incompatible type "UnhashableIterator"; expected "Hashable" \
# N: Following member(s) of "UnhashableIterator" have conflicts: \
# N: __hash__: expected "Callable[[], int]", got "None"

[builtins fixtures/object_hashable.pyi]
[typing fixtures/typing-full.pyi]

Expand Down
Loading