<#18250 Bad `__hash__` overrides in `pants.engine....
# github-notifications
q
#18250 Bad `__hash__` overrides in `pants.engine.{collection.Collection,target.CoarsenedTargets}` Issue created by jsirois These currently both work like so:
Copy code
$ git grep "__hash__ = "
src/python/pants/engine/collection.py:    __hash__ = Tuple.__hash__
src/python/pants/engine/target.py:    __hash__ = Tuple.__hash__
With some instrumentation:
Copy code
diff --git a/src/python/pants/engine/collection.py b/src/python/pants/engine/collection.py
index c340fa2e6..cceaae611 100644
--- a/src/python/pants/engine/collection.py
+++ b/src/python/pants/engine/collection.py
@@ -52,7 +52,16 @@ class Collection(Tuple[T, ...]):
     # Unlike in Python 2 we must explicitly implement __hash__ since we explicitly implement __eq__
     # per the Python 3 data model.
     # See: <https://docs.python.org/3/reference/datamodel.html#object.__hash__>
-    __hash__ = Tuple.__hash__
+    def __hash__(self):
+        import inspect
+        import sys
+        print(f">>> Tuple.__hash__ code:\n{inspect.getsource(Tuple.__hash__)}", file=sys.stderr)
+        print(f">>> Tuple.__origin__: {Tuple.__origin__}", file=sys.stderr)
+        print(f">>> Tuple.__args__: {Tuple.__args__}", file=sys.stderr)
+        print(f">>> {self} delegating __hash__ to super ...", file=sys.stderr)
+        print(f">>> Tuple.__hash__ value: {Tuple.__hash__()}", file=sys.stderr)
+        # return super().__hash__()
+        return Tuple.__hash__()

     def __repr__(self) -> str:
         return f"{self.__class__.__name__}({list(self)})"
That reveals:
Copy code
>>> Tuple.__hash__ code:
    def __hash__(self):
        if self.__origin__ is Union:
            return hash((Union, frozenset(self.__args__)))
        return hash((self.__origin__, self.__args__))
>>> Tuple.__origin__: <class 'tuple'>
>>> Tuple.__args__: ()
>>> Tuple.__hash__ value: -9020823266885220566
...
>>> Tuple.__hash__ value: -9020823266885220566
>>> Tuple.__origin__: <class 'tuple'>
>>> Tuple.__args__: ()
>>> Tuple.__hash__ code:
    def __hash__(self):
        if self.__origin__ is Union:
            return hash((Union, frozenset(self.__args__)))
        return hash((self.__origin__, self.__args__))
...
So the same hash value for every instance of
pants.engine.collection.Collection
. Not good if we have any large collection of these. Adding in a real call of `tuple.__hash__`:
Copy code
$ git diff
diff --git a/src/python/pants/engine/collection.py b/src/python/pants/engine/collection.py
index c340fa2e6..b58e13069 100644
--- a/src/python/pants/engine/collection.py
+++ b/src/python/pants/engine/collection.py
@@ -52,7 +52,8 @@ class Collection(Tuple[T, ...]):
     # Unlike in Python 2 we must explicitly implement __hash__ since we explicitly implement __eq__
     # per the Python 3 data model.
     # See: <https://docs.python.org/3/reference/datamodel.html#object.__hash__>
-    __hash__ = Tuple.__hash__
+    def __hash__(self):
+        return super().__hash__()
Worse, we're attempting to hash unhashable contained things: ``` $ ./pants check src/python/pants/engine:: 124508.69 [ERROR] 1 Exception encountered: Engine traceback: in select .. in pants.core.util_rules.environments.determine_local_environment .. in pants.core.util_rules.environments.determine_all_environments .. Traceback (most recent call last): File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/util/frozendict.py", line 91, in _calculate_hash return hash(tuple(self._data.items())) File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/collection.py", line 56, in hash return super().__hash__() File "<string>", line 3, in hash TypeError: unhashable type: 'dict' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 623, in native_engine_generator_send res = rule.send(arg) if err is None else rule.throw(throw or err) File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/core/util_rules/environments.py", line 592, in determine_all_environments Get(EnvironmentTarget, EnvironmentName(name)) for name in environments_subsystem.names File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 358, in MultiGet return await _MultiGet(tuple(__arg0)) File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 165, in await result = yield self.gets File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 623, in native_engine_generator_send res = rule.send(arg) if err is None else rule.throw(throw or err) File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/core/util_rules/environments.py", line 827, in get_target_for_environment_name WrappedTargetRequest(address, description_of_origin=_description_of_origin), File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 118, in await result = yield self File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 623, in native_engine_generator_send res = rule.send(arg) if err is None else rule.throw(throw or err) File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/graph.py", line 401, in resolve_target_for_bootstrapping description_of_origin=request.description_of_origin, File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/graph.py", line 180, in _determine_target_adaptor_and_type TargetAdaptorRequest(address, description_of_origin=description_of_origin), File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 118, in await result = yield self File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 623, in native_engine_generator_send res = rule.send(arg) if err is None else rule.throw(throw or err) File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/build_files.py", line 395, in find_target_adaptor address_family = await Get(AddressFamily, AddressFamilyDir(address.spec_path)) File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 118, in await result = yield self File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 623, in native_engine_generator_send res = rule.send(arg) if err is None else rule.throw(throw or err) File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/build_files.py", line 395, in find_target_adaptor address_family = await Get(AddressFamily, AddressFamilyDir(address.spec_path)) File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 118, in await result = yield self File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 623, in native_engine_generator_send res = rule.send(arg) if err is None else rule.throw(throw or err) File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/build_files.py", line 260, in parse_address_family Get(SyntheticAddressMaps, SyntheticAddressMapsRequest(directory.path)), File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 509, in MultiGet return await _MultiGet((__arg0, __arg1)) File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 165, in await result = yield self.gets File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/selectors.py", line 623, in native_engine_generator_send res = rule.send(arg) if err is None else rule.throw(throw or err) File "/home/jsirois/dev/pantsbuild/pants/src/python/pants/engine/internals/synthetic_targets.py", line 314, in all_synthetic_targets spec_pat… pantsbuild/pants