hdl.ast: don't inherit Shape from NamedTuple.
authorawygle <awygle@gmail.com>
Tue, 7 Jul 2020 05:17:03 +0000 (22:17 -0700)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Fri, 31 Dec 2021 14:47:47 +0000 (14:47 +0000)
Fixes #421.

nmigen/hdl/ast.py
nmigen/test/test_hdl_ast.py

index 37a3b46f3847200aa08b24be05631004a78fe686..55b825b2ddcba471fccc8213d8fa0d254aa31609 100644 (file)
@@ -32,7 +32,7 @@ class DUID:
         DUID.__next_uid += 1
 
 
-class Shape(typing.NamedTuple):
+class Shape:
     """Bit width and signedness of a value.
 
     A ``Shape`` can be constructed using:
@@ -55,8 +55,15 @@ class Shape(typing.NamedTuple):
     signed : bool
         If ``False``, the value is unsigned. If ``True``, the value is signed two's complement.
     """
-    width:  int  = 1
-    signed: bool = False
+    def __init__(self, width=1, signed=False):
+        if not isinstance(width, int) or width < 0:
+            raise TypeError("Width must be a non-negative integer, not {!r}"
+                            .format(width))
+        self.width = width
+        self.signed = signed
+
+    def __iter__(self):
+        return iter((self.width, self.signed))
 
     @staticmethod
     def cast(obj, *, src_loc_at=0):
@@ -95,13 +102,20 @@ class Shape(typing.NamedTuple):
         else:
             return "unsigned({})".format(self.width)
 
-
-# TODO: use dataclasses instead of this hack
-def _Shape___init__(self, width=1, signed=False):
-    if not isinstance(width, int) or width < 0:
-        raise TypeError("Width must be a non-negative integer, not {!r}"
-                        .format(width))
-Shape.__init__ = _Shape___init__
+    def __eq__(self, other):
+        if isinstance(other, tuple) and len(other) == 2:
+            width, signed = other
+            if isinstance(width, int) and isinstance(signed, bool):
+                return self.width == width and self.signed == signed
+            else:
+                raise TypeError("Shapes may be compared with other Shapes and (int, bool) tuples, "
+                        "not {!r}"
+                        .format(other))
+        if not isinstance(other, Shape):
+            raise TypeError("Shapes may be compared with other Shapes and (int, bool) tuples, "
+                    "not {!r}"
+                    .format(other))
+        return self.width == other.width and self.signed == other.signed
 
 
 def unsigned(width):
index b724ad6fef3f1ed39ddf6a6fa8b924372eb6658d..691408a0363e9a4d3bdea0f659b0888863420cec 100644 (file)
@@ -39,6 +39,16 @@ class ShapeTestCase(FHDLTestCase):
                 msg="Width must be a non-negative integer, not -1"):
             Shape(-1)
 
+    def test_compare_wrong(self):
+        with self.assertRaises(TypeError,
+                msg="Shapes may be compared with other Shapes and (int, bool) tuples, not 'hi'"):
+            Shape(1, True) == 'hi'
+
+    def test_compare_tuple_wrong(self):
+        with self.assertRaises(TypeError,
+                msg="Shapes may be compared with other Shapes and (int, bool) tuples, not (2, 3)"):
+            Shape(1, True) == (2, 3)
+
     def test_repr(self):
         self.assertEqual(repr(Shape()), "unsigned(1)")
         self.assertEqual(repr(Shape(2, True)), "signed(2)")