diff --git a/amaranth/lib/data.py b/amaranth/lib/data.py index 0360f9b5f..f6e512548 100644 --- a/amaranth/lib/data.py +++ b/amaranth/lib/data.py @@ -65,6 +65,9 @@ def width(self): """ return Shape.cast(self.shape).width + def __hash__(self): + return hash((self.shape, self.offset)) + def __eq__(self, other): """Compare fields. @@ -166,6 +169,11 @@ def as_shape(self): """ return unsigned(self.size) + def __hash__(self): + if not hasattr(self, "_Layout__hash"): + setattr(self, "_Layout__hash", hash((self.size, frozenset(iter(self))))) + return self.__hash + def __eq__(self, other): """Compare layouts. @@ -1123,6 +1131,9 @@ def __len__(self): f"`len()` can only be used on constants of array layout, not {self.__layout!r}") return self.__layout.length + def __hash__(self): + return hash((self.__target, self.__layout)) + def __eq__(self, other): if isinstance(other, View) and self.__layout == other._View__layout: return self.as_value() == other._View__target diff --git a/docs/changes.rst b/docs/changes.rst index 9a22400d9..d9ac51fcd 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -64,6 +64,7 @@ Standard library changes * Added: :py:`payload_init=` argument in :class:`amaranth.lib.stream.Signature`. * Added: :meth:`enum.EnumView.matches`. (`RFC 71`_) +* Added: :class:`data.Field`, :class:`data.Layout` and :class:`data.Const` are hashable. * Changed: (deprecated in 0.5.1) providing :meth:`io.PortLike.__add__` is now mandatory. (`RFC 69`_) * Removed: (deprecated in 0.5.0) :mod:`amaranth.lib.coding`. (`RFC 63`_) diff --git a/tests/test_lib_data.py b/tests/test_lib_data.py index bf2583eeb..d34987978 100644 --- a/tests/test_lib_data.py +++ b/tests/test_lib_data.py @@ -74,6 +74,9 @@ def test_immutable(self): with self.assertRaises(AttributeError): data.Field(1, 0).offset = 1 + def test_hash(self): + hash(data.Field(unsigned(2), 1)) + class StructLayoutTestCase(FHDLTestCase): def test_construct(self): @@ -520,6 +523,9 @@ def test_signal_init(self): self.assertEqual(Signal(sl).as_value().init, 0) self.assertEqual(Signal(sl, init={"a": 0b1, "b": 0b10}).as_value().init, 5) + def test_hash(self): + hash(data.StructLayout({})) + class ViewTestCase(FHDLTestCase): def test_construct(self): @@ -1167,6 +1173,9 @@ def test_repr(self): s1 = data.Const(data.StructLayout({"a": unsigned(2)}), 2) self.assertRepr(s1, "Const(StructLayout({'a': unsigned(2)}), 2)") + def test_hash(self): + hash(data.Const(data.StructLayout({"a": unsigned(2)}), 2)) + class StructTestCase(FHDLTestCase): def test_construct(self):