Skip to content

Commit 579af69

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 be9959d commit 579af69

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
@@ -1083,19 +1083,37 @@ def __eq__(self, other):
10831083
elif isinstance(other, Const) and self.__layout == other.__layout:
10841084
return self.__target == other.__target
10851085
else:
1086+
cause = None
1087+
if isinstance(other, (dict, list)):
1088+
try:
1089+
other_as_const = self.__layout.const(other)
1090+
except (TypeError, ValueError) as exc:
1091+
cause = exc
1092+
else:
1093+
return self == other_as_const
10861094
raise TypeError(
1087-
f"Constant with layout {self.__layout!r} can only be compared to another view or "
1088-
f"constant with the same layout, not {other!r}")
1095+
f"Constant with layout {self.__layout!r} can only be compared to another view, "
1096+
f"a constant with the same layout, or a dictionary or a list that can be converted "
1097+
f"to a constant with the same layout, not {other!r}") from cause
10891098

10901099
def __ne__(self, other):
10911100
if isinstance(other, View) and self.__layout == other._View__layout:
10921101
return self.as_value() != other._View__target
10931102
elif isinstance(other, Const) and self.__layout == other.__layout:
10941103
return self.__target != other.__target
10951104
else:
1105+
cause = None
1106+
if isinstance(other, (dict, list)):
1107+
try:
1108+
other_as_const = self.__layout.const(other)
1109+
except (TypeError, ValueError) as exc:
1110+
cause = exc
1111+
else:
1112+
return self != other_as_const
10961113
raise TypeError(
1097-
f"Constant with layout {self.__layout!r} can only be compared to another view or "
1098-
f"constant with the same layout, not {other!r}")
1114+
f"Constant with layout {self.__layout!r} can only be compared to another view, "
1115+
f"a constant with the same layout, or a dictionary or a list that can be converted "
1116+
f"to a constant with the same layout, not {other!r}") from cause
10991117

11001118
def __add__(self, other):
11011119
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
@@ -744,7 +744,7 @@ def test_bug_837_array_layout_getattr(self):
744744
r"^View with an array layout does not have fields$"):
745745
Signal(data.ArrayLayout(unsigned(1), 1), init=[0]).init
746746

747-
def test_eq(self):
747+
def test_compare(self):
748748
s1 = Signal(data.StructLayout({"a": unsigned(2)}))
749749
s2 = Signal(data.StructLayout({"a": unsigned(2)}))
750750
s3 = Signal(data.StructLayout({"a": unsigned(1), "b": unsigned(1)}))
@@ -973,11 +973,12 @@ def test_bug_837_array_layout_getattr(self):
973973
r"^Constant with an array layout does not have fields$"):
974974
data.Const(data.ArrayLayout(unsigned(1), 1), 0).init
975975

976-
def test_eq(self):
976+
def test_compare(self):
977977
c1 = data.Const(data.StructLayout({"a": unsigned(2)}), 1)
978978
c2 = data.Const(data.StructLayout({"a": unsigned(2)}), 1)
979979
c3 = data.Const(data.StructLayout({"a": unsigned(2)}), 2)
980980
c4 = data.Const(data.StructLayout({"a": unsigned(1), "b": unsigned(1)}), 2)
981+
c5 = data.Const(data.ArrayLayout(2, 4), 0b11100100)
981982
s1 = Signal(data.StructLayout({"a": unsigned(2)}))
982983
self.assertTrue(c1 == c2)
983984
self.assertFalse(c1 != c2)
@@ -987,13 +988,23 @@ def test_eq(self):
987988
self.assertRepr(c1 != s1, "(!= (const 2'd1) (sig s1))")
988989
self.assertRepr(s1 == c1, "(== (sig s1) (const 2'd1))")
989990
self.assertRepr(s1 != c1, "(!= (sig s1) (const 2'd1))")
991+
self.assertTrue(c1 == {"a": 1})
992+
self.assertFalse(c1 == {"a": 2})
993+
self.assertFalse(c1 != {"a": 1})
994+
self.assertTrue(c1 != {"a": 2})
995+
self.assertTrue(c5 == [0,1,2,3])
996+
self.assertFalse(c5 == [0,1,3,3])
997+
self.assertFalse(c5 != [0,1,2,3])
998+
self.assertTrue(c5 != [0,1,3,3])
990999
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 .*$"):
1000+
r"^Constant with layout .* can only be compared to another view, a constant "
1001+
r"with the same layout, or a dictionary or a list that can be converted to "
1002+
r"a constant with the same layout, not .*$"):
9931003
c1 == c4
9941004
with self.assertRaisesRegex(TypeError,
995-
r"^Constant with layout .* can only be compared to another view or constant with "
996-
r"the same layout, not .*$"):
1005+
r"^Constant with layout .* can only be compared to another view, a constant "
1006+
r"with the same layout, or a dictionary or a list that can be converted to "
1007+
r"a constant with the same layout, not .*$"):
9971008
c1 != c4
9981009
with self.assertRaisesRegex(TypeError,
9991010
r"^View with layout .* can only be compared to another view or constant with "
@@ -1004,21 +1015,45 @@ def test_eq(self):
10041015
r"the same layout, not .*$"):
10051016
s1 != c4
10061017
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 .*$"):
1018+
r"^Constant with layout .* can only be compared to another view, a constant "
1019+
r"with the same layout, or a dictionary or a list that can be converted to "
1020+
r"a constant with the same layout, not .*$"):
10091021
c4 == s1
10101022
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 .*$"):
1023+
r"^Constant with layout .* can only be compared to another view, a constant "
1024+
r"with the same layout, or a dictionary or a list that can be converted to "
1025+
r"a constant with the same layout, not .*$"):
10131026
c4 != s1
10141027
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 .*$"):
1028+
r"^Constant with layout .* can only be compared to another view, a constant "
1029+
r"with the same layout, or a dictionary or a list that can be converted to "
1030+
r"a constant with the same layout, not .*$"):
10171031
c1 == Const(0, 2)
10181032
with self.assertRaisesRegex(TypeError,
1019-
r"^Constant with layout .* can only be compared to another view or constant with "
1020-
r"the same layout, not .*$"):
1033+
r"^Constant with layout .* can only be compared to another view, a constant "
1034+
r"with the same layout, or a dictionary or a list that can be converted to "
1035+
r"a constant with the same layout, not .*$"):
10211036
c1 != Const(0, 2)
1037+
with self.assertRaisesRegex(TypeError,
1038+
r"^Constant with layout .* can only be compared to another view, a constant "
1039+
r"with the same layout, or a dictionary or a list that can be converted to "
1040+
r"a constant with the same layout, not .*$"):
1041+
c1 == {"b": 1}
1042+
with self.assertRaisesRegex(TypeError,
1043+
r"^Constant with layout .* can only be compared to another view, a constant "
1044+
r"with the same layout, or a dictionary or a list that can be converted to "
1045+
r"a constant with the same layout, not .*$"):
1046+
c1 != {"b": 1}
1047+
with self.assertRaisesRegex(TypeError,
1048+
r"^Constant with layout .* can only be compared to another view, a constant "
1049+
r"with the same layout, or a dictionary or a list that can be converted to "
1050+
r"a constant with the same layout, not .*$"):
1051+
c5 == [0,1,2,3,4]
1052+
with self.assertRaisesRegex(TypeError,
1053+
r"^Constant with layout .* can only be compared to another view, a constant "
1054+
r"with the same layout, or a dictionary or a list that can be converted to "
1055+
r"a constant with the same layout, not .*$"):
1056+
c5 != [0,1,2,3,4]
10221057

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

0 commit comments

Comments
 (0)