@@ -247,6 +247,7 @@ def transform(self) -> bool:
247247 "order" : self ._get_bool_arg ("order" , self ._spec .order_default ),
248248 "frozen" : self ._get_bool_arg ("frozen" , self ._spec .frozen_default ),
249249 "slots" : self ._get_bool_arg ("slots" , False ),
250+ "unsafe_hash" : self ._get_bool_arg ("unsafe_hash" , False ),
250251 "match_args" : self ._get_bool_arg ("match_args" , True ),
251252 }
252253 py_version = self ._api .options .python_version
@@ -291,6 +292,14 @@ def transform(self) -> bool:
291292 self ._api , self ._cls , "__init__" , args = args , return_type = NoneType ()
292293 )
293294
295+ if "__hash__" not in info .names or info .names ["__hash__" ].plugin_generated :
296+ # Presence of __hash__ usually isn't checked. However, when inheriting from
297+ # an abstract Hashable, we need to override the abstract __hash__ to avoid
298+ # false positives.
299+ self ._add_dunder_hash (
300+ is_hashable = decorator_arguments ["unsafe_hash" ] or decorator_arguments ["frozen" ]
301+ )
302+
294303 if (
295304 decorator_arguments ["eq" ]
296305 and info .get ("__eq__" ) is None
@@ -446,6 +455,29 @@ def _add_internal_post_init_method(self, attributes: list[DataclassAttribute]) -
446455 return_type = NoneType (),
447456 )
448457
458+ def _add_dunder_hash (self , is_hashable : bool ) -> None :
459+ if is_hashable :
460+ add_method_to_class (
461+ self ._api ,
462+ self ._cls ,
463+ "__hash__" ,
464+ args = [],
465+ return_type = self ._api .named_type ("builtins.int" ),
466+ )
467+ else :
468+ # Python sets `__hash__ = None` otherwise, do the same.
469+ parent_method = self ._cls .info .get_method ("__hash__" )
470+ if parent_method is not None :
471+ # If we inherited `__hash__`, ensure it isn't overridden
472+ self ._api .fail (
473+ "Incompatible override of '__hash__': dataclasses without"
474+ " 'frozen' or 'unsafe_hash' have '__hash__' set to None" ,
475+ self ._cls ,
476+ )
477+ add_attribute_to_class (
478+ self ._api , self ._cls , "__hash__" , typ = NoneType (), is_classvar = True
479+ )
480+
449481 def add_slots (
450482 self , info : TypeInfo , attributes : list [DataclassAttribute ], * , correct_version : bool
451483 ) -> None :
0 commit comments