hdl: make all public Value classes other than Record final.
authorwhitequark <whitequark@whitequark.org>
Sun, 12 May 2019 05:36:35 +0000 (05:36 +0000)
committerwhitequark <whitequark@whitequark.org>
Sun, 12 May 2019 05:40:17 +0000 (05:40 +0000)
In some cases, nMigen uses type() instead of isinstance() to dispatch
on types. Make sure all such uses of type() are robust; in addition,
make it clear that nMigen AST classes are not meant to be subclassed.
(Record is an exception.)

Fixes #65.

nmigen/hdl/ast.py
nmigen/hdl/rec.py
nmigen/tools.py

index 37816cecc75706f323eda027f87ee6c8112c78f3..c246af13f5f6bfa4df3c96af03f8dcd772b7ffa2 100644 (file)
@@ -210,6 +210,7 @@ class Value(metaclass=ABCMeta):
     __hash__ = None
 
 
+@final
 class Const(Value):
     """A constant, literal integer value.
 
@@ -283,16 +284,19 @@ class AnyValue(Value, DUID):
         return ValueSet()
 
 
+@final
 class AnyConst(AnyValue):
     def __repr__(self):
         return "(anyconst {}'{})".format(self.nbits, "s" if self.signed else "")
 
 
+@final
 class AnySeq(AnyValue):
     def __repr__(self):
         return "(anyseq {}'{})".format(self.nbits, "s" if self.signed else "")
 
 
+@final
 class Operator(Value):
     def __init__(self, op, operands, src_loc_at=0):
         super().__init__(src_loc_at=1 + src_loc_at)
@@ -387,6 +391,7 @@ def Mux(sel, val1, val0):
     return Operator("m", [sel, val1, val0], src_loc_at=1)
 
 
+@final
 class Slice(Value):
     def __init__(self, value, start, end):
         if not isinstance(start, int):
@@ -424,6 +429,7 @@ class Slice(Value):
         return "(slice {} {}:{})".format(repr(self.value), self.start, self.end)
 
 
+@final
 class Part(Value):
     def __init__(self, value, offset, width):
         if not isinstance(width, int) or width < 0:
@@ -447,6 +453,7 @@ class Part(Value):
         return "(part {} {} {})".format(repr(self.value), repr(self.offset), self.width)
 
 
+@final
 class Cat(Value):
     """Concatenate values.
 
@@ -495,6 +502,7 @@ class Cat(Value):
         return "(cat {})".format(" ".join(map(repr, self.parts)))
 
 
+@final
 class Repl(Value):
     """Replicate a value
 
@@ -534,6 +542,7 @@ class Repl(Value):
         return "(repl {!r} {})".format(self.value, self.count)
 
 
+@final
 class Signal(Value, DUID):
     """A varying integer value.
 
@@ -649,6 +658,7 @@ class Signal(Value, DUID):
         return "(sig {})".format(self.name)
 
 
+@final
 class ClockSignal(Value):
     """Clock signal for a clock domain.
 
@@ -680,6 +690,7 @@ class ClockSignal(Value):
         return "(clk {})".format(self.domain)
 
 
+@final
 class ResetSignal(Value):
     """Reset signal for a clock domain.
 
@@ -802,6 +813,7 @@ class Array(MutableSequence):
                                        ", ".join(map(repr, self._inner)))
 
 
+@final
 class ArrayProxy(Value):
     def __init__(self, elems, index):
         super().__init__(src_loc_at=1)
@@ -836,6 +848,7 @@ class ArrayProxy(Value):
         return "(proxy (array [{}]) {!r})".format(", ".join(map(repr, self.elems)), self.index)
 
 
+@final
 class Sample(Value):
     """Value from the past.
 
@@ -899,6 +912,7 @@ class Statement:
                 raise TypeError("Object '{!r}' is not an nMigen statement".format(obj))
 
 
+@final
 class Assign(Statement):
     def __init__(self, lhs, rhs):
         self.lhs = Value.wrap(lhs)
@@ -940,14 +954,17 @@ class Property(Statement):
         return "({} {!r})".format(self._kind, self.test)
 
 
+@final
 class Assert(Property):
     _kind = "assert"
 
 
+@final
 class Assume(Property):
     _kind = "assume"
 
 
+# @final
 class Switch(Statement):
     def __init__(self, test, cases):
         self.test  = Value.wrap(test)
@@ -981,6 +998,7 @@ class Switch(Statement):
         return "(switch {!r} {})".format(self.test, " ".join(cases))
 
 
+@final
 class Delay(Statement):
     def __init__(self, interval=None):
         self.interval = None if interval is None else float(interval)
@@ -995,6 +1013,7 @@ class Delay(Statement):
             return "(delay {:.3}us)".format(self.interval * 1e6)
 
 
+@final
 class Tick(Statement):
     def __init__(self, domain="sync"):
         self.domain = str(domain)
@@ -1006,6 +1025,7 @@ class Tick(Statement):
         return "(tick {})".format(self.domain)
 
 
+@final
 class Passive(Statement):
     def _rhs_signals(self):
         return ValueSet()
index 2ab00c89886fb470b78958b6dee2c3a01ded53a4..63a977ccf705a260d3ae8182ef32c4eba5da15a4 100644 (file)
@@ -62,6 +62,7 @@ class Layout:
             yield (name, shape, dir)
 
 
+# Unlike most Values, Record *can* be subclassed.
 class Record(Value):
     def __init__(self, layout, name=None):
         if name is None:
index edf21959b2acaa833bcc605fca15070a190ebd04..09c9d318a7ea1e0644ebde2b1d01ca9cb4689c60 100644 (file)
@@ -6,7 +6,7 @@ from collections.abc import Iterable
 from contextlib import contextmanager
 
 
-__all__ = ["flatten", "union", "log2_int", "bits_for", "memoize", "deprecated"]
+__all__ = ["flatten", "union", "log2_int", "bits_for", "memoize", "final", "deprecated"]
 
 
 def flatten(i):
@@ -57,6 +57,14 @@ def memoize(f):
     return g
 
 
+def final(cls):
+    def init_subclass():
+        raise TypeError("Subclassing {}.{} is not supported"
+                        .format(cls.__module__, cls.__name__))
+    cls.__init_subclass__ = init_subclass
+    return cls
+
+
 def deprecated(message, stacklevel=2):
     def decorator(f):
         @functools.wraps(f)