Skip to content

Commit 6de51e0

Browse files
committed
lib.data: make constants comparable with compatible initializers.
Before this commit, `data.Const` was only comparable to `data.Const` or `data.View`. After this commit, in addition, it is also comparable to `dict` or `list` provided that such a value is accepted by `layout.const()` where `layout` is the layout of the `data.Const` object. This change greatly reduces boilerplate in tests by enabling e.g.: assert (await stream_get(ctx, stream)) == {"value": 1} instead of: assert (await stream_get(ctx, stream)) == Const({"value": 1}, stream.p.shape()) Note that, unlike `Layout.const`, which accepts arbitrary `Mapping` or `Sequence` objects, only `dict` and `list` are accepted in comparisons. Also, `data.View` continues to be comparable only to `data.View` and `data.Const`. This is to minimize the scope of the change and reduce likelihood of undesirable side effects when backported to the 0.5.x branch. Fixes #1414.
1 parent 94becb5 commit 6de51e0

File tree

2 files changed

+71
-18
lines changed

2 files changed

+71
-18
lines changed

amaranth/lib/data.py

+22-4
Original file line numberDiff line numberDiff line change
@@ -1079,19 +1079,37 @@ def __eq__(self, other):
10791079
elif isinstance(other, Const) and self.__layout == other.__layout:
10801080
return self.__target == other.__target
10811081
else:
1082+
cause = None
1083+
if isinstance(other, (dict, list)):
1084+
try:
1085+
other_as_const = self.__layout.const(other)
1086+
except (TypeError, ValueError) as exc:
1087+
cause = exc
1088+
else:
1089+
return self == other_as_const
10821090
raise TypeError(
1083-
f"Constant with layout {self.__layout!r} can only be compared to another view or "
1084-
f"constant with the same layout, not {other!r}")
1091+
f"Constant with layout {self.__layout!r} can only be compared to another view, "
1092+
f"a constant with the same layout, or a dictionary or a list that can be converted "
1093+
f"to a constant with the same layout, not {other!r}") from cause
10851094

10861095
def __ne__(self, other):
10871096
if isinstance(other, View) and self.__layout == other._View__layout:
10881097
return self.as_value() != other._View__target
10891098
elif isinstance(other, Const) and self.__layout == other.__layout:
10901099
return self.__target != other.__target
10911100
else:
1101+
cause = None
1102+
if isinstance(other, (dict, list)):
1103+
try:
1104+
other_as_const = self.__layout.const(other)
1105+
except (TypeError, ValueError) as exc:
1106+
cause = exc
1107+
else:
1108+
return self != other_as_const
10921109
raise TypeError(
1093-
f"Constant with layout {self.__layout!r} can only be compared to another view or "
1094-
f"constant with the same layout, not {other!r}")
1110+
f"Constant with layout {self.__layout!r} can only be compared to another view, "
1111+
f"a constant with the same layout, or a dictionary or a list that can be converted "
1112+
f"to a constant with the same layout, not {other!r}") from cause
10951113

10961114
def __add__(self, other):
10971115
raise TypeError("Cannot perform arithmetic operations on a lib.data.Const")

tests/test_lib_data.py

+49-14
Original file line numberDiff line numberDiff line change
@@ -740,7 +740,7 @@ def test_bug_837_array_layout_getattr(self):
740740
r"^View with an array layout does not have fields$"):
741741
Signal(data.ArrayLayout(unsigned(1), 1), init=[0]).init
742742

743-
def test_eq(self):
743+
def test_compare(self):
744744
s1 = Signal(data.StructLayout({"a": unsigned(2)}))
745745
s2 = Signal(data.StructLayout({"a": unsigned(2)}))
746746
s3 = Signal(data.StructLayout({"a": unsigned(1), "b": unsigned(1)}))
@@ -969,11 +969,12 @@ def test_bug_837_array_layout_getattr(self):
969969
r"^Constant with an array layout does not have fields$"):
970970
data.Const(data.ArrayLayout(unsigned(1), 1), 0).init
971971

972-
def test_eq(self):
972+
def test_compare(self):
973973
c1 = data.Const(data.StructLayout({"a": unsigned(2)}), 1)
974974
c2 = data.Const(data.StructLayout({"a": unsigned(2)}), 1)
975975
c3 = data.Const(data.StructLayout({"a": unsigned(2)}), 2)
976976
c4 = data.Const(data.StructLayout({"a": unsigned(1), "b": unsigned(1)}), 2)
977+
c5 = data.Const(data.ArrayLayout(2, 4), 0b11100100)
977978
s1 = Signal(data.StructLayout({"a": unsigned(2)}))
978979
self.assertTrue(c1 == c2)
979980
self.assertFalse(c1 != c2)
@@ -983,13 +984,23 @@ def test_eq(self):
983984
self.assertRepr(c1 != s1, "(!= (const 2'd1) (sig s1))")
984985
self.assertRepr(s1 == c1, "(== (sig s1) (const 2'd1))")
985986
self.assertRepr(s1 != c1, "(!= (sig s1) (const 2'd1))")
987+
self.assertTrue(c1 == {"a": 1})
988+
self.assertFalse(c1 == {"a": 2})
989+
self.assertFalse(c1 != {"a": 1})
990+
self.assertTrue(c1 != {"a": 2})
991+
self.assertTrue(c5 == [0,1,2,3])
992+
self.assertFalse(c5 == [0,1,3,3])
993+
self.assertFalse(c5 != [0,1,2,3])
994+
self.assertTrue(c5 != [0,1,3,3])
986995
with self.assertRaisesRegex(TypeError,
987-
r"^Constant with layout .* can only be compared to another view or constant with "
988-
r"the same layout, not .*$"):
996+
r"^Constant with layout .* can only be compared to another view, a constant "
997+
r"with the same layout, or a dictionary or a list that can be converted to "
998+
r"a constant with the same layout, not .*$"):
989999
c1 == c4
9901000
with self.assertRaisesRegex(TypeError,
991-
r"^Constant with layout .* can only be compared to another view or constant with "
992-
r"the same layout, not .*$"):
1001+
r"^Constant with layout .* can only be compared to another view, a constant "
1002+
r"with the same layout, or a dictionary or a list that can be converted to "
1003+
r"a constant with the same layout, not .*$"):
9931004
c1 != c4
9941005
with self.assertRaisesRegex(TypeError,
9951006
r"^View with layout .* can only be compared to another view or constant with "
@@ -1000,21 +1011,45 @@ def test_eq(self):
10001011
r"the same layout, not .*$"):
10011012
s1 != c4
10021013
with self.assertRaisesRegex(TypeError,
1003-
r"^Constant with layout .* can only be compared to another view or constant with "
1004-
r"the same layout, not .*$"):
1014+
r"^Constant with layout .* can only be compared to another view, a constant "
1015+
r"with the same layout, or a dictionary or a list that can be converted to "
1016+
r"a constant with the same layout, not .*$"):
10051017
c4 == s1
10061018
with self.assertRaisesRegex(TypeError,
1007-
r"^Constant with layout .* can only be compared to another view or constant with "
1008-
r"the same layout, not .*$"):
1019+
r"^Constant with layout .* can only be compared to another view, a constant "
1020+
r"with the same layout, or a dictionary or a list that can be converted to "
1021+
r"a constant with the same layout, not .*$"):
10091022
c4 != s1
10101023
with self.assertRaisesRegex(TypeError,
1011-
r"^Constant with layout .* can only be compared to another view or constant with "
1012-
r"the same layout, not .*$"):
1024+
r"^Constant with layout .* can only be compared to another view, a constant "
1025+
r"with the same layout, or a dictionary or a list that can be converted to "
1026+
r"a constant with the same layout, not .*$"):
10131027
c1 == Const(0, 2)
10141028
with self.assertRaisesRegex(TypeError,
1015-
r"^Constant with layout .* can only be compared to another view or constant with "
1016-
r"the same layout, not .*$"):
1029+
r"^Constant with layout .* can only be compared to another view, a constant "
1030+
r"with the same layout, or a dictionary or a list that can be converted to "
1031+
r"a constant with the same layout, not .*$"):
10171032
c1 != Const(0, 2)
1033+
with self.assertRaisesRegex(TypeError,
1034+
r"^Constant with layout .* can only be compared to another view, a constant "
1035+
r"with the same layout, or a dictionary or a list that can be converted to "
1036+
r"a constant with the same layout, not .*$"):
1037+
c1 == {"b": 1}
1038+
with self.assertRaisesRegex(TypeError,
1039+
r"^Constant with layout .* can only be compared to another view, a constant "
1040+
r"with the same layout, or a dictionary or a list that can be converted to "
1041+
r"a constant with the same layout, not .*$"):
1042+
c1 != {"b": 1}
1043+
with self.assertRaisesRegex(TypeError,
1044+
r"^Constant with layout .* can only be compared to another view, a constant "
1045+
r"with the same layout, or a dictionary or a list that can be converted to "
1046+
r"a constant with the same layout, not .*$"):
1047+
c5 == [0,1,2,3,4]
1048+
with self.assertRaisesRegex(TypeError,
1049+
r"^Constant with layout .* can only be compared to another view, a constant "
1050+
r"with the same layout, or a dictionary or a list that can be converted to "
1051+
r"a constant with the same layout, not .*$"):
1052+
c5 != [0,1,2,3,4]
10181053

10191054
def test_operator(self):
10201055
s1 = data.Const(data.StructLayout({"a": unsigned(2)}), 2)

0 commit comments

Comments
 (0)