Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

type[Any] not considered Hashable #11470

Open
KotlinIsland opened this issue Nov 5, 2021 · 15 comments
Open

type[Any] not considered Hashable #11470

KotlinIsland opened this issue Nov 5, 2021 · 15 comments
Labels
bug mypy got something wrong

Comments

@KotlinIsland
Copy link
Contributor

KotlinIsland commented Nov 5, 2021

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.

@sobolevn
Copy link
Member

sobolevn commented Nov 5, 2021

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
Copy link
Member

sobolevn commented Nov 5, 2021

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
Copy link
Member

sobolevn commented Nov 5, 2021

Moreover, this also works:

from typing import Hashable, Any

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

@KotlinIsland
Copy link
Contributor Author

Related #11469

@gwk
Copy link

gwk commented Mar 21, 2022

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.

@KotlinIsland
Copy link
Contributor Author

KotlinIsland commented Mar 21, 2022

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.

@finite-state-machine
Copy link

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.

@sg495
Copy link

sg495 commented Nov 13, 2023

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

@Dreamsorcerer
Copy link
Contributor

Dreamsorcerer commented Feb 12, 2024

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.

@finite-state-machine
Copy link

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
Copy link

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

@Dreamsorcerer
Copy link
Contributor

Dreamsorcerer commented Feb 13, 2024

(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...

@finite-state-machine
Copy link

[...] 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
Copy link

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.

@chadrik
Copy link
Contributor

chadrik commented Oct 17, 2024

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

I'm not sure if this is true. Below are some tests with mypy 1.12. type[int] is not considered Hashable.

from dataclasses import dataclass
from typing import Hashable, Any, NamedTuple

t1: type[Any] = type
h1: Hashable = t1  # error: Incompatible types in assignment


t2: type[str] = str
h2: Hashable = t2  # success!


@dataclass(frozen=True, unsafe_hash=True)
class Foo:
    x: int

t3: type[Foo] = Foo
h3: Hashable = t3  # error: Incompatible types in assignment


class Bar(NamedTuple):
    x: int

t4: type[Bar] = Bar
h4: Hashable = t4  # error: Incompatible types in assignment

t5: type[int] = int
h5: Hashable = t5  # error: Incompatible types in assignment

t6: type[type] = type
h6: Hashable = t6  # success!

t7: type = int
h7: Hashable = t7  # success!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants