@@ -313,9 +313,6 @@ def generic_repr(self):
313313 cls .__str__ = generic_str
314314 cls .__repr__ = generic_repr
315315
316- if not hasattr (cls , "__hash__" ):
317- raise AssertionError (f"not hashable: { cls } " )
318-
319316 # }}}
320317
321318 # {{{ Python set-like behavior
@@ -349,6 +346,13 @@ def obj_sub(self, other):
349346
350347 # {{{ Space
351348
349+ def space_hash (self ):
350+ return hash ((type (self ),
351+ self .dim (dim_type .param ),
352+ self .dim (dim_type .in_ ),
353+ self .dim (dim_type .out ),
354+ self .dim (dim_type .div )))
355+
352356 def space_get_id_dict (self , dimtype = None ):
353357 """Return a dictionary mapping variable :class:`Id` instances to tuples
354358 of (:class:`dim_type`, index).
@@ -446,6 +450,7 @@ def space_create_from_names(ctx, set=None, in_=None, out=None, params=()):
446450
447451 return result
448452
453+ Space .__hash__ = space_hash
449454 Space .create_from_names = staticmethod (space_create_from_names )
450455 Space .get_var_dict = space_get_var_dict
451456 Space .get_id_dict = space_get_id_dict
@@ -908,6 +913,12 @@ def val_to_python(self):
908913 # note: automatic upcasts for method arguments are provided through
909914 # 'implicitly_convertible' on the C++ side of the wrapper.
910915
916+ def make_upcasting_hash (special_method , upcast_method ):
917+ def wrapper (basic_instance ):
918+ return hash ((type (basic_instance ), upcast_method (basic_instance )))
919+
920+ return wrapper
921+
911922 def make_new_upcast_wrapper (method , upcast ):
912923 # This function provides a scope in which method and upcast
913924 # are not changed from one iteration of the enclosing for
@@ -916,7 +927,6 @@ def make_new_upcast_wrapper(method, upcast):
916927 def wrapper (basic_instance , * args , ** kwargs ):
917928 special_instance = upcast (basic_instance )
918929 return method (special_instance , * args , ** kwargs )
919-
920930 return wrapper
921931
922932 def make_existing_upcast_wrapper (basic_method , special_method , upcast ):
@@ -938,6 +948,18 @@ def wrapper(basic_instance, *args, **kwargs):
938948 def add_upcasts (basic_class , special_class , upcast_method ):
939949 from functools import update_wrapper
940950
951+ # {{{ implicitly upcast __hash__
952+
953+ # We don't use hasattr() here because in the C++ part of the wrapper
954+ # we overwrite pybind's unwanted default __hash__ implementation
955+ # with None.
956+ if (getattr (basic_class , "__hash__" , None ) is None
957+ and getattr (special_class , "__hash__" , None ) is not None ):
958+ wrapper = make_upcasting_hash (special_class .__hash__ , upcast_method )
959+ basic_class .__hash__ = update_wrapper (wrapper , basic_class .__hash__ )
960+
961+ # }}}
962+
941963 def my_ismethod (class_ , method_name ):
942964 if method_name .startswith ("_" ):
943965 return False
0 commit comments