Skip to content

dataclasses with generated __hash__ subclassing Hashable are considered abstract #18962

Open

Description

@ralismark

Bug Report

Dataclasses that are hashable (e.g. frozen=True) that extend any abc/protocol requiring __hash__ (e.g. collections.abc.Hashable) is considered abstract and gives an error when trying to be instantiated.

In addition to frozen=True/Hashable, you also get the same error with unsafe_hash=True, as well as using your own protocol or abc that requires __hash__.

To Reproduce

https://mypy-play.net/?mypy=latest&python=3.12&gist=390504f01055e489b12b388eec1de256

from dataclasses import dataclass
from collections.abc import Hashable


@dataclass(frozen=True)
class A:
    a: int


@dataclass(frozen=True)
class B(Hashable):
    a: int


a: Hashable = A(1)  # ok
b = B(1)  # error: Cannot instantiate abstract class "A" with abstract attribute "__hash__"  [abstract]

print(hash(b))  # prints -6644214454873602895

Expected Behavior

This works at runtime -- running the script prints the hash, demonstrating that it has __hash__ defined.

Actual Behavior

main.py:16: error: Cannot instantiate abstract class "B" with abstract attribute "__hash__"  [abstract]

Your Environment

  • Mypy version used: 1.15.0
  • Mypy command-line flags: N/A
  • Mypy configuration options from mypy.ini (and other config files): N/A
  • Python version used: 3.12

Activity

  1. A5rocks commented on Apr 24, 2025

    @A5rocks
    Collaborator

    More generally:

    from abc import ABC, abstractmethod
    import dataclasses
    
    class Hashable(ABC):
        @abstractmethod
        def __hash__(self) -> int:
            ...
    
    @dataclasses.dataclass(frozen=True)
    class X(Hashable):
        a: int
    
    X(4)

    I suspect this is that mypy doesn't check what methods a plugin adds when looking at whether a class is abstract.

  2. sterliakov commented on Apr 25, 2025

    @sterliakov
    Collaborator

    No, there's no distinction between plugin-generated and regular methods for this check. is_abstract is set after semanal, so plugin should have already been called at that point.

    This problem is more obvious: mypy does not generate __hash__ on dataclasses at all. At least grepping for hash finds no matches in dataclass plugin code. A is accepted because we have object.__hash__, so A is indeed hashable from mypy perspective. B explicitly inherits from Hashable and thus Hashable.__hash__ is the highest MRO entry, and it's abstract.

    Probably we should just generate __hash__ on dataclasses. This should be easy to do, dunder-hash is simple and non-generic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions