Skip to content

Commit d6d2606

Browse files
committed
Stop using pybind's implicit __hash__ (closes gh-102)
1 parent cefffb5 commit d6d2606

File tree

3 files changed

+39
-5
lines changed

3 files changed

+39
-5
lines changed

gen_wrap.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1370,6 +1370,10 @@ def write_wrapper(outf, meth):
13701370

13711371
# {{{ exposer generator
13721372

1373+
def is_hash(meth):
1374+
return meth.name == "get_hash" and len(meth.args) == 1
1375+
1376+
13731377
def write_exposer(outf, meth, arg_names, doc_str):
13741378
func_name = f"isl::{meth.cls}_{meth.name}"
13751379
py_name = meth.name
@@ -1385,7 +1389,7 @@ def write_exposer(outf, meth, arg_names, doc_str):
13851389
if meth.name == "size" and len(meth.args) == 1:
13861390
py_name = "__len__"
13871391

1388-
if meth.name == "get_hash" and len(meth.args) == 1:
1392+
if is_hash(meth):
13891393
py_name = "__hash__"
13901394

13911395
extra_py_names = []
@@ -1509,6 +1513,16 @@ def gen_wrapper(include_dirs, include_barvinok=False, isl_version=None):
15091513
for cls in classes
15101514
for meth in fdata.classes_to_methods.get(cls, [])])
15111515

1516+
for cls in classes:
1517+
has_isl_hash = any(
1518+
is_hash(meth) for meth in fdata.classes_to_methods.get(cls, []))
1519+
1520+
if not has_isl_hash:
1521+
# pybind11's C++ object base class has an object identity
1522+
# __hash__ that everyone inherits automatically. We don't
1523+
# want that.
1524+
expf.write(f'wrap_{cls}.attr("__hash__") = py::none();\n')
1525+
15121526
expf.close()
15131527
wrapf.close()
15141528

islpy/__init__.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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
@@ -908,6 +905,12 @@ def val_to_python(self):
908905
# note: automatic upcasts for method arguments are provided through
909906
# 'implicitly_convertible' on the C++ side of the wrapper.
910907

908+
def make_upcasting_hash(special_method, upcast_method):
909+
def wrapper(basic_instance):
910+
return hash((type(basic_instance), upcast_method(basic_instance)))
911+
912+
return wrapper
913+
911914
def make_new_upcast_wrapper(method, upcast):
912915
# This function provides a scope in which method and upcast
913916
# are not changed from one iteration of the enclosing for
@@ -916,7 +919,6 @@ def make_new_upcast_wrapper(method, upcast):
916919
def wrapper(basic_instance, *args, **kwargs):
917920
special_instance = upcast(basic_instance)
918921
return method(special_instance, *args, **kwargs)
919-
920922
return wrapper
921923

922924
def make_existing_upcast_wrapper(basic_method, special_method, upcast):
@@ -938,6 +940,15 @@ def wrapper(basic_instance, *args, **kwargs):
938940
def add_upcasts(basic_class, special_class, upcast_method):
939941
from functools import update_wrapper
940942

943+
# {{{ implicitly upcast __hash__
944+
945+
if (getattr(basic_class, "__hash__", None) is None
946+
and getattr(special_class, "__hash__", None) is not None):
947+
wrapper = make_upcasting_hash(special_class.__hash__, upcast_method)
948+
basic_class.__hash__ = update_wrapper(wrapper, basic_class.__hash__)
949+
950+
# }}}
951+
941952
def my_ismethod(class_, method_name):
942953
if method_name.startswith("_"):
943954
return False

test/test_isl.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,15 @@ def test_sched_constraints_set_validity():
457457
assert str(validity) == str(validity2)
458458

459459

460+
def test_basicset_hash():
461+
# https://github.com/inducer/islpy/issues/102
462+
# isl does not currently (2022-12-30) offer hashing for BasicSet.
463+
464+
a1 = isl.BasicSet("{[i]: 0<=i<512}")
465+
a2 = isl.BasicSet("{[i]: 0<=i<512}")
466+
assert hash(a1) == hash(a2)
467+
468+
460469
if __name__ == "__main__":
461470
import sys
462471
if len(sys.argv) > 1:

0 commit comments

Comments
 (0)