mypy icon indicating copy to clipboard operation
mypy copied to clipboard

`type[Any]` not considered `Hashable`

Open KotlinIsland opened this issue 4 years ago • 15 comments

from typing import Hashable, Any

t1: type[Any] = type
h1: Hashable = t1  # error: Incompatible types in assignment (expression has type "Type[Any]", variable has type "Hashable")

Which seems strange because type[type] is considered Hashable, demonstrating that a type can be Hashable.

KotlinIsland avatar Nov 05 '21 04:11 KotlinIsland

This is why it happens:

from typing import Hashable, Any

t1: type[Any] = type
reveal_type(type.__hash__)  # N: Revealed type is "def (self: builtins.object) -> builtins.int"
reveal_type(t1.__hash__)  # N: Revealed type is "def () -> builtins.int"

And def () -> builtins.int is not compatible with Hashable 🤔

I will try to fix t1.__hash__ inference.

sobolevn avatar Nov 05 '21 14:11 sobolevn

No, I was wrong. The same works with just a slight modification:

from typing import Hashable, Any

t1: type = type
reveal_type(type.__hash__)  # N: Revealed type is "def (self: builtins.object) -> builtins.int"
reveal_type(t1.__hash__)  # N: Revealed type is "def () -> builtins.int"
h1: Hashable = t1   # no error

sobolevn avatar Nov 05 '21 14:11 sobolevn

Moreover, this also works:

from typing import Hashable, Any

t1: type[type] = type
h1: Hashable = t1

sobolevn avatar Nov 05 '21 14:11 sobolevn

Related #11469

KotlinIsland avatar Nov 06 '21 02:11 KotlinIsland

I was about to report the same problem for the @cache decorator case, and just want to chime in that Type[_T] for TypeVar _T is also a problem. I see discussion of Type[Any] and type[type] but it's not clear to me if that covers the type var case as well.

gwk avatar Mar 21 '22 12:03 gwk

I think a type[T] where the upper bound of T produces a Hashable type[T] (default upper bound is object, and type[object] is hashable) should be considered Hashable.

KotlinIsland avatar Mar 21 '22 12:03 KotlinIsland

There are also some problems with using assert statements to work around this, as in the following:

instance: Type[object]
assert isinstance(instance, Hashable)  # mypy thinks this is unsatisfiable
...  # mypy thinks this is unreachable

More details may be found in the duplicate bug #12993.

finite-state-machine avatar Jun 17 '22 17:06 finite-state-machine

Just chiming in that this is still an open issue as of Mypy 1.7.0 (Python 3.12.0).

from typing import Any, Hashable

def foo(t: type) -> None:
    x: Hashable = t # OK

def bar(t: type[Any]) -> None:
    x: Hashable = t # error

sg495 avatar Nov 13 '23 11:11 sg495

To give a reproducer of TypeVar usage (which could be included in tests):

from functools import lru_cache
from typing import TypeVar

T = TypeVar("T")

@lru_cache
def foo(t):
    ...

def check(t: type[T]):
    foo(t)  # error: Argument 1 to "__call__" of "_lru_cache_wrapper" has incompatible type "type[T]"; expected "Hashable"  [arg-type]

check(dict[str, object])

It seems that it only considers type[T] to be hashable if T is hashable. Thus, the error disappears if doing TypeVar("T", bound=Hashable). However, that is not correct as you then can't pass dict[str, object] in the last line.

Dreamsorcerer avatar Feb 12 '24 22:02 Dreamsorcerer

In fact, not all types are hashable: mypy-play.net

from __future__ import annotations
from typing import *


# this might seem to be reasonable:
def test_func(klass: Type[object]) -> None:

    klasses: Set[Type[Any]] = set()

    # ...but this isn't completely sound:
    klasses.add(klass)

    # (mypy 1.8.0 isn't issuing an error here, which might be the right
    #  call because _almost_ all types are hashable)


class SomeMeta(type):
    '''this metaclass defines '__eq__()' but not '__hash__()', making
       instances of it unhashable
    '''
    def __eq__(self, other: object) -> bool:
        return NotImplemented


class SomeClass(metaclass=SomeMeta):
    '''this class uses 'SomeMeta', meaning the class itself is unhashable
    '''


# This will raise an exception at runtime:
hash(SomeClass)                                                # line 31
        # Traceback (most recent call last):
        #     ...
        # TypeError: unhashable type: 'SomeMeta'


# As will the 2nd line of this paragraph (if line 31 is commented out):
some_set: Set[Type[Any]] = set()
some_set.add(SomeClass)
        # Traceback (most recent call last):
        #     ...
        # TypeError: unhashable type: 'SomeMeta'

This issues no mypy errors (ca. v1.8.0), despite being unsound. That's probably the right thing to do, but it should be a carefully weighed choice (and maybe a check which could be enabled with an --enable-flag).

finite-state-machine avatar Feb 13 '24 00:02 finite-state-machine

(That said, there are errors issued by the example in #12993. IMHO this inconsistency is undesirable.)

finite-state-machine avatar Feb 13 '24 03:02 finite-state-machine

(That said, there are errors issued by the example in #12993. IMHO this inconsistency is undesirable.)

Indeed, as per my comment, mypy seems to consider type[X] hashable only if X is hashable, which seems completely unrelated.

Curiously, in my example, if I change from the TypeVar to using a custom class directly (def check(t: type[Bar])), then I still get the same error, yet I don't get the error if I just call foo(Bar), which should be the same thing...

Dreamsorcerer avatar Feb 13 '24 14:02 Dreamsorcerer

[...] mypy seems to consider type[X] hashable only if X is hashable, which seems completely unrelated.

I think we can all agree that the hashability of type[X] has nothing to do with the hashability of X instances.

finite-state-machine avatar Feb 13 '24 15:02 finite-state-machine

One possible approach to determining if a value is likely Hashable: in the entire system being type-checked, is there any known example of the specified type that is not Hashable? For most projects, that would mean Type[Any/object] would be Hashable. OTOH this involves spooky action at a distance: defining a metaclass in one place could cause errors to appear on the other side of the code base.

In a way, it's a pityHashable isn't Generic-like: being able to writeHashable[Type[Any]], Hashable[Mapping[K, V]], Hashable[SomeProtocol], etc., would be quite useful. This is a special case of #213, and would like be solved by the as-yet-unnumbered PEP being drafted for that issue, which will presumably introduce a syntax like Type[Any] & Hashable.

finite-state-machine avatar Feb 13 '24 15:02 finite-state-machine