In astral-sh/ruff#20284, we reworked our logic so that Hashable is treated equivalently to object in subtyping and assignability checks. This is defensible from a theoretical perspective, since all instances of "exactly object" are hashable at runtime, which therefore makes object a subtype of Hashable by all normal rules of protocol assignability and subtyping. However, rules around hashability in Python do not obey the normal rules: hashable classes often inherit from unhashable ones, and unhashable classes often inherit from hashable ones, flying in the face of the Liskov Substitution Principle. Our current approach to Hashable and similar protocols makes these protocols effectively useless; we will not complain about code like this, even though users would expect us to do so (and even though other type checkers issue complaints):
from typing import Hashable
def f(x: Hashable): ...
f([])
Given that the Liskov principle is so frequently and fragrantly violated for hashability, in Python's builtin types, standard library, and across a wide range of Python code, it may be worth us seeing if we can roll back astral-sh/ruff#20284 and find another solution to #1132 that improves our compatibility with other type checkers. The reason why other type checkers do not run into problems such as #1132 is that they do not eagerly simplify unions of Hashable and other types in the same way we do. We should consider doing the same; it would probably lead to more intuitive behaviour from users' perspective, and compatibility with other type checkers is also a useful goal.
In astral-sh/ruff#20284, we reworked our logic so that
Hashableis treated equivalently toobjectin subtyping and assignability checks. This is defensible from a theoretical perspective, since all instances of "exactlyobject" are hashable at runtime, which therefore makesobjecta subtype ofHashableby all normal rules of protocol assignability and subtyping. However, rules around hashability in Python do not obey the normal rules: hashable classes often inherit from unhashable ones, and unhashable classes often inherit from hashable ones, flying in the face of the Liskov Substitution Principle. Our current approach toHashableand similar protocols makes these protocols effectively useless; we will not complain about code like this, even though users would expect us to do so (and even though other type checkers issue complaints):Given that the Liskov principle is so frequently and fragrantly violated for hashability, in Python's builtin types, standard library, and across a wide range of Python code, it may be worth us seeing if we can roll back astral-sh/ruff#20284 and find another solution to #1132 that improves our compatibility with other type checkers. The reason why other type checkers do not run into problems such as #1132 is that they do not eagerly simplify unions of
Hashableand other types in the same way we do. We should consider doing the same; it would probably lead to more intuitive behaviour from users' perspective, and compatibility with other type checkers is also a useful goal.