Repository: amaranth-lang/amaranth
Pull request #983 (2023-12-04 05:45:03)

Implement RFC 35: Add `ShapeLike`, `ValueLike`.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/983?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) Report
Attention: `2 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`ab6503e`)](https://app.codecov.io/gh/amaranth-lang/amaranth/commit/ab6503e352825b36bb29f1a8622b9e98aac9a6c6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) 83.96% compared to head [(`480cec2`)](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/983?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) 83.97%.

| [Files](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/983?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) | Patch % | Lines |
|---|---|---|
| [amaranth/hdl/ast.py](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/983?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang#diff-YW1hcmFudGgvaGRsL2FzdC5weQ==) | 93.75% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/983?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #983      +/-   ##
==========================================
+ Coverage   83.96%   83.97%   +0.01%     
==========================================
  Files          55       55              
  Lines        8064     8095      +31     
  Branches     1966     1975       +9     
==========================================
+ Hits         6771     6798      +27     
- Misses       1081     1083       +2     
- Partials      212      214       +2     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/983?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).


Patch:
diff --git a/amaranth/hdl/ast.py b/amaranth/hdl/ast.py
index 6d5107e..c698f0c 100644
--- a/amaranth/hdl/ast.py
+++ b/amaranth/hdl/ast.py
@@ -4,7 +4,7 @@ import warnings
 import functools
 from collections import OrderedDict
 from collections.abc import Iterable, MutableMapping, MutableSet, MutableSequence
-from enum import Enum
+from enum import Enum, EnumMeta
 from itertools import chain
 
 from ._repr import *
@@ -15,11 +15,11 @@ from .._unused import *
 
 
 __all__ = [
-    "Shape", "signed", "unsigned", "ShapeCastable",
+    "Shape", "signed", "unsigned", "ShapeCastable", "ShapeLike",
     "Value", "Const", "C", "AnyConst", "AnySeq", "Operator", "Mux", "Part", "Slice", "Cat", "Repl",
     "Array", "ArrayProxy",
     "Signal", "ClockSignal", "ResetSignal",
-    "ValueCastable",
+    "ValueCastable", "ValueLike",
     "Sample", "Past", "Stable", "Rose", "Fell", "Initial",
     "Statement", "Switch",
     "Property", "Assign", "Assert", "Assume", "Cover",
@@ -150,6 +150,52 @@ class Shape:
                 self.width == other.width and self.signed == other.signed)
 
 
+class _ShapeLikeMeta(type):
+    def __subclasscheck__(cls, subclass):
+        return issubclass(subclass, (Shape, ShapeCastable, int, range, EnumMeta)) or subclass is ShapeLike
+
+    def __instancecheck__(cls, instance):
+        if isinstance(instance, (Shape, ShapeCastable, range)):
+            return True
+        if isinstance(instance, int):
+            return instance >= 0
+        if isinstance(instance, EnumMeta):
+            for member in instance:
+                if not isinstance(member.value, ValueLike):
+                    return False
+            return True
+        return False
+
+
+@final
+class ShapeLike(metaclass=_ShapeLikeMeta):
+    """An abstract class representing all objects that can be cast to a :class:`Shape`.
+
+    ``issubclass(cls, ShapeLike)`` returns ``True`` for:
+
+    - :class:`Shape`
+    - :class:`ShapeCastable` and its subclasses
+    - ``int`` and its subclasses
+    - ``range`` and its subclasses
+    - :class:`enum.EnumMeta` and its subclasses
+    - :class:`ShapeLike` itself
+
+    ``isinstance(obj, ShapeLike)`` returns ``True`` for:
+
+    - :class:`Shape` instances
+    - :class:`ShapeCastable` instances
+    - non-negative ``int`` values
+    - ``range`` instances
+    - :class:`enum.Enum` subclasses where all values are :ref:`value-like <lang-valuelike>`
+
+    This class is only usable for the above checks — no instances and no (non-virtual)
+    subclasses can be created.
+    """
+
+    def __new__(cls, *args, **kwargs):
+        raise TypeError("ShapeLike is an abstract class and cannot be constructed")
+
+
 def unsigned(width):
     """Shorthand for ``Shape(width, signed=False)``."""
     return Shape(width, signed=False)
@@ -1479,6 +1525,40 @@ class ValueCastable:
         return wrapper_memoized
 
 
+class _ValueLikeMeta(type):
+    """An abstract class representing all objects that can be cast to a :class:`Value`.
+
+    ``issubclass(cls, ValueLike)`` returns ``True`` for:
+
+    - :class:`Value`
+    - :class:`ValueCastable` and its subclasses
+    - ``int`` and its subclasses
+    - :class:`enum.Enum` subclasses where all values are :ref:`value-like <lang-valuelike>`
+    - :class:`ValueLike` itself
+
+    ``isinstance(obj, ValueLike)`` returns the same value as ``issubclass(type(obj), ValueLike)``.
+
+    This class is only usable for the above checks — no instances and no (non-virtual)
+    subclasses can be created.
+    """
+
+    def __subclasscheck__(cls, subclass):
+        if issubclass(subclass, (Value, ValueCastable, int)) or subclass is ValueLike:
+            return True
+        if issubclass(subclass, Enum):
+            return isinstance(subclass, ShapeLike)
+        return False
+
+    def __instancecheck__(cls, instance):
+        return issubclass(type(instance), cls)
+
+
+@final
+class ValueLike(metaclass=_ValueLikeMeta):
+    def __new__(cls, *args, **kwargs):
+        raise TypeError("ValueLike is an abstract class and cannot be constructed")
+
+
 # TODO(amaranth-0.5): remove
 @final
 class Sample(Value):
diff --git a/amaranth/lib/data.py b/amaranth/lib/data.py
index 697566c..e1c35db 100644
--- a/amaranth/lib/data.py
+++ b/amaranth/lib/data.py
@@ -26,7 +26,7 @@ class Field:
 
     Attributes
     ----------
-    shape : :ref:`shape-castable <lang-shapecasting>`
+    shape : :ref:`shape-like <lang-shapelike>`
         Shape of the field. When initialized or assigned, the object is stored as-is.
     offset : :class:`int`, >=0
         Index of the least significant bit of the field.
@@ -56,7 +56,7 @@ class Field:
         """Width of the field.
 
         This property should be used over ``self.shape.width`` because ``self.shape`` can be
-        an arbitrary :ref:`shape-castable <lang-shapecasting>` object, which may not have
+        an arbitrary :ref:`shape-like <lang-shapelike>` object, which may not have
         a ``width`` property.
 
         Returns
@@ -82,7 +82,7 @@ class Field:
 class Layout(ShapeCastable, metaclass=ABCMeta):
     """Description of a data layout.
 
-    The :ref:`shape-castable <lang-shapecasting>` :class:`Layout` interface associates keys
+    The :ref:`shape-like <lang-shapelike>` :class:`Layout` interface associates keys
     (string names or integer indexes) with fields, giving identifiers to spans of bits in
     an Amaranth value.
 
@@ -96,7 +96,7 @@ class Layout(ShapeCastable, metaclass=ABCMeta):
 
     @staticmethod
     def cast(obj):
-        """Cast a :ref:`shape-castable <lang-shapecasting>` object to a layout.
+        """Cast a :ref:`shape-like <lang-shapelike>` object to a layout.
 
         This method performs a subset of the operations done by :meth:`Shape.cast`; it will
         recursively call ``.as_shape()``, but only until a layout is returned.
@@ -279,7 +279,7 @@ class StructLayout(Layout):
 
     Attributes
     ----------
-    members : mapping of :class:`str` to :ref:`shape-castable <lang-shapecasting>`
+    members : mapping of :class:`str` to :ref:`shape-like <lang-shapelike>`
         Dictionary of structure members.
     """
 
@@ -350,7 +350,7 @@ class UnionLayout(Layout):
 
     Attributes
     ----------
-    members : mapping of :class:`str` to :ref:`shape-castable <lang-shapecasting>`
+    members : mapping of :class:`str` to :ref:`shape-like <lang-shapelike>`
         Dictionary of union members.
     """
     def __init__(self, members):
@@ -425,7 +425,7 @@ class ArrayLayout(Layout):
 
     Attributes
     ----------
-    elem_shape : :ref:`shape-castable <lang-shapecasting>`
+    elem_shape : :ref:`shape-like <lang-shapelike>`
         Shape of an individual element.
     length : :class:`int`
         Amount of elements.
@@ -567,7 +567,7 @@ class FlexibleLayout(Layout):
 class View(ValueCastable):
     """A value viewed through the lens of a layout.
 
-    The :ref:`value-castable <lang-valuecasting>` class :class:`View` provides access to the fields
+    The :ref:`value-like <lang-valuelike>` class :class:`View` provides access to the fields
     of an underlying Amaranth value via the names or indexes defined in the provided layout.
 
     Creating a view
@@ -583,7 +583,7 @@ class View(ValueCastable):
     a value-castable object. If the shape of the field is a :class:`Layout`, it will be
     a :class:`View`; if it is a class deriving from :class:`Struct` or :class:`Union`, it
     will be an instance of that data class; if it is another
-    :ref:`shape-castable <lang-shapecasting>` object implementing ``__call__``, it will be
+    :ref:`shape-like <lang-shapelike>` object implementing ``__call__``, it will be
     the result of calling that method.
 
     Slicing a view whose layout is an :class:`ArrayLayout` can be done with an index that is
@@ -859,7 +859,7 @@ class Struct(View, metaclass=_AggregateMeta):
     to describe the structure layout and reset values for the fields using Python
     :term:`variable annotations <python:variable annotation>`.
 
-    Any annotations containing :ref:`shape-castable <lang-shapecasting>` objects are used,
+    Any annotations containing :ref:`shape-like <lang-shapelike>` objects are used,
     in the order in which they appear in the source code, to construct a :class:`StructLayout`.
     The values assigned to such annotations are used to populate the reset value of the signal
     created by the view. Any other annotations are kept as-is.
diff --git a/amaranth/lib/enum.py b/amaranth/lib/enum.py
index d8b7416..b15d6d5 100644
--- a/amaranth/lib/enum.py
+++ b/amaranth/lib/enum.py
@@ -19,13 +19,13 @@ class EnumMeta(ShapeCastable, py_enum.EnumMeta):
     protocol.
 
     This metaclass provides the :meth:`as_shape` method, making its instances
-    :ref:`shape-castable <lang-shapecasting>`, and accepts a ``shape=`` keyword argument
+    :ref:`shape-like <lang-shapelike>`, and accepts a ``shape=`` keyword argument
     to specify a shape explicitly. Other than this, it acts the same as the standard
     :class:`enum.EnumMeta` class; if the ``shape=`` argument is not specified and
     :meth:`as_shape` is never called, it places no restrictions on the enumeration class
     or the values of its members.
 
-    When a :ref:`value-castable <lang-valuecasting>` is cast to an enum type that is an instance
+    When a :ref:`value-like <lang-valuelike>` is cast to an enum type that is an instance
     of this metaclass, it can be automatically wrapped in a view class. A custom view class
     can be specified by passing the ``view_class=`` keyword argument when creating the enum class.
     """
@@ -139,7 +139,7 @@ class EnumMeta(ShapeCastable, py_enum.EnumMeta):
         When given an integer constant, it returns the corresponding enum value, like a standard
         Python enumeration.
 
-        When given a :ref:`value-castable <lang-valuecasting>`, it is cast to a value, then wrapped
+        When given a :ref:`value-like <lang-valuelike>`, it is cast to a value, then wrapped
         in the ``view_class`` specified for this enum type (:class:`EnumView` for :class:`Enum`,
         :class:`FlagView` for :class:`Flag`, or a custom user-defined class). If the type has no
         ``view_class`` (like :class:`IntEnum` or :class:`IntFlag`), a plain
@@ -214,7 +214,7 @@ class EnumView(ValueCastable):
 
     def __init__(self, enum, target):
         """Constructs a view with the given enum type and target
-        (a :ref:`value-castable <lang-valuecasting>`).
+        (a :ref:`value-like <lang-valuelike>`).
         """
         if not isinstance(enum, EnumMeta) or not hasattr(enum, "_amaranth_shape_"):
             raise TypeError(f"EnumView type must be an enum with shape, not {enum!r}")
@@ -312,7 +312,7 @@ class FlagView(EnumView):
     values of the same enum type."""
 
     def __invert__(self):
-        """Inverts all flags in this value and returns another :ref:`FlagView`.
+        """Inverts all flags in this value and returns another :class:`FlagView`.
 
         Note that this is not equivalent to applying bitwise negation to the underlying value:
         just like the Python :class:`enum.Flag` class, only bits corresponding to flags actually
diff --git a/docs/lang.rst b/docs/lang.rst
index 9a87443..bde2509 100644
--- a/docs/lang.rst
+++ b/docs/lang.rst
@@ -121,14 +121,14 @@ The shape of the constant can be specified explicitly, in which case the number'
    0
 
 
-.. _lang-shapecasting:
+.. _lang-shapelike:
 
 Shape casting
 =============
 
-Shapes can be *cast* from other objects, which are called *shape-castable*. Casting is a convenient way to specify a shape indirectly, for example, by a range of numbers representable by values with that shape.
+Shapes can be *cast* from other objects, which are called *shape-like*. Casting is a convenient way to specify a shape indirectly, for example, by a range of numbers representable by values with that shape.
 
-Casting to a shape can be done explicitly with ``Shape.cast``, but is usually implicit, since shape-castable objects are accepted anywhere shapes are.
+Casting to a shape can be done explicitly with ``Shape.cast``, but is usually implicit, since shape-like objects are accepted anywhere shapes are.
 
 
 .. _lang-shapeint:
@@ -244,16 +244,16 @@ The :mod:`amaranth.lib.enum` module extends the standard enumerations such that
    The enumeration does not have to subclass :class:`enum.IntEnum` or have :class:`int` as one of its base classes; it only needs to have integers as values of every member. Using enumerations based on :class:`enum.Enum` rather than :class:`enum.IntEnum` prevents unwanted implicit conversion of enum members to integers.
 
 
-.. _lang-valuecasting:
+.. _lang-valuelike:
 
 Value casting
 =============
 
-Like shapes, values may be *cast* from other objects, which are called *value-castable*. Casting to values allows objects that are not provided by Amaranth, such as integers or enumeration members, to be used in Amaranth expressions directly.
+Like shapes, values may be *cast* from other objects, which are called *value-like*. Casting to values allows objects that are not provided by Amaranth, such as integers or enumeration members, to be used in Amaranth expressions directly.
 
 .. TODO: link to ValueCastable
 
-Casting to a value can be done explicitly with ``Value.cast``, but is usually implicit, since value-castable objects are accepted anywhere values are.
+Casting to a value can be done explicitly with ``Value.cast``, but is usually implicit, since value-like objects are accepted anywhere values are.
 
 
 Values from integers
@@ -343,7 +343,7 @@ A *signal* is a value representing a (potentially) varying number. Signals can b
 Signal shapes
 -------------
 
-A signal can be created with an explicitly specified shape (any :ref:`shape-castable <lang-shapecasting>` object); if omitted, the shape defaults to ``unsigned(1)``. Although rarely useful, 0-bit signals are permitted.
+A signal can be created with an explicitly specified shape (any :ref:`shape-like <lang-shapelike>` object); if omitted, the shape defaults to ``unsigned(1)``. Although rarely useful, 0-bit signals are permitted.
 
 .. doctest::
 
@@ -444,7 +444,7 @@ Amaranth provides aggregate data structures in the standard library module :mod:
 Operators
 =========
 
-To describe computations, Amaranth values can be combined with each other or with :ref:`value-castable <lang-valuecasting>` objects using a rich array of arithmetic, bitwise, logical, bit sequence, and other *operators* to form *expressions*, which are themselves values.
+To describe computations, Amaranth values can be combined with each other or with :ref:`value-like <lang-valuelike>` objects using a rich array of arithmetic, bitwise, logical, bit sequence, and other *operators* to form *expressions*, which are themselves values.
 
 
 .. _lang-abstractexpr:
diff --git a/docs/stdlib/data.rst b/docs/stdlib/data.rst
index e063550..4590b23 100644
--- a/docs/stdlib/data.rst
+++ b/docs/stdlib/data.rst
@@ -61,7 +61,7 @@ While this implementation works, it is repetitive, error-prone, hard to read, an
 
     m.d.comb += o_gray.eq((i_color.red + i_color.green + i_color.blue) << 1)
 
-The :class:`View` is :ref:`value-castable <lang-valuecasting>` and can be used anywhere a plain value can be used. For example, it can be assigned to in the usual way:
+The :class:`View` is :ref:`value-like <lang-valuelike>` and can be used anywhere a plain value can be used. For example, it can be assigned to in the usual way:
 
 .. testcode::
 
@@ -135,7 +135,7 @@ In case the data has related operations or transformations, :class:`View` can be
         def brightness(self):
             return (self.red + self.green + self.blue)[-8:]
 
-Here, the ``RGBLayout`` class itself is :ref:`shape-castable <lang-shapecasting>` and can be used anywhere a shape is accepted. When a :class:`Signal` is constructed with this layout, the returned value is wrapped in an ``RGBView``:
+Here, the ``RGBLayout`` class itself is :ref:`shape-like <lang-shapelike>` and can be used anywhere a shape is accepted. When a :class:`Signal` is constructed with this layout, the returned value is wrapped in an ``RGBView``:
 
 .. doctest::
 
diff --git a/docs/stdlib/enum.rst b/docs/stdlib/enum.rst
index 0fa87d9..2267b60 100644
--- a/docs/stdlib/enum.rst
+++ b/docs/stdlib/enum.rst
@@ -59,7 +59,7 @@ The ``shape=`` argument is optional. If not specified, classes from this module
 
 In this way, this module is a drop-in replacement for the standard :mod:`enum` module, and in an Amaranth project, all ``import enum`` statements may be replaced with ``from amaranth.lib import enum``.
 
-Signals with :class:`Enum` or :class:`Flag` based shape are automatically wrapped in the :class:`EnumView` or :class:`FlagView` value-castable wrappers, which ensure type safety. Any :ref:`value-castable <lang-valuecasting>` can also be explicitly wrapped in a view class by casting it to the enum type:
+Signals with :class:`Enum` or :class:`Flag` based shape are automatically wrapped in the :class:`EnumView` or :class:`FlagView` value-like wrappers, which ensure type safety. Any :ref:`value-like <lang-valuelike>` can also be explicitly wrapped in a view class by casting it to the enum type:
 
 .. doctest::
 


Test patch:
diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py
index 2252a38..56dfca3 100644
--- a/tests/test_hdl_ast.py
+++ b/tests/test_hdl_ast.py
@@ -1,5 +1,5 @@
 import warnings
-from enum import Enum
+from enum import Enum, EnumMeta
 
 from amaranth.hdl.ast import *
 from amaranth.lib.enum import Enum as AmaranthEnum
@@ -189,6 +189,44 @@ class ShapeCastableTestCase(FHDLTestCase):
         self.assertEqual(Shape.cast(sc), unsigned(1))
 
 
+class ShapeLikeTestCase(FHDLTestCase):
+    def test_construct(self):
+        with self.assertRaises(TypeError):
+            ShapeLike()
+
+    def test_subclass(self):
+        self.assertTrue(issubclass(Shape, ShapeLike))
+        self.assertTrue(issubclass(MockShapeCastable, ShapeLike))
+        self.assertTrue(issubclass(int, ShapeLike))
+        self.assertTrue(issubclass(range, ShapeLike))
+        self.assertTrue(issubclass(EnumMeta, ShapeLike))
+        self.assertFalse(issubclass(Enum, ShapeLike))
+        self.assertFalse(issubclass(str, ShapeLike))
+        self.assertTrue(issubclass(ShapeLike, ShapeLike))
+
+    def test_isinstance(self):
+        self.assertTrue(isinstance(unsigned(2), ShapeLike))
+        self.assertTrue(isinstance(MockShapeCastable(unsigned(2)), ShapeLike))
+        self.assertTrue(isinstance(2, ShapeLike))
+        self.assertTrue(isinstance(0, ShapeLike))
+        self.assertFalse(isinstance(-1, ShapeLike))
+        self.assertTrue(isinstance(range(10), ShapeLike))
+        self.assertFalse(isinstance("abc", ShapeLike))
+
+    def test_isinstance_enum(self):
+        class EnumA(Enum):
+            A = 1
+            B = 2
+        class EnumB(Enum):
+            A = "a"
+            B = "b"
+        class EnumC(Enum):
+            A = Cat(Const(1, 2), Const(0, 2))
+        self.assertTrue(isinstance(EnumA, ShapeLike))
+        self.assertFalse(isinstance(EnumB, ShapeLike))
+        self.assertTrue(isinstance(EnumC, ShapeLike))
+
+
 class ValueTestCase(FHDLTestCase):
     def test_cast(self):
         self.assertIsInstance(Value.cast(0), Const)
@@ -1300,6 +1338,50 @@ class ValueCastableTestCase(FHDLTestCase):
         self.assertIsInstance(Value.cast(vc), Signal)
 
 
+class ValueLikeTestCase(FHDLTestCase):
+    def test_construct(self):
+        with self.assertRaises(TypeError):
+            ValueLike()
+
+    def test_subclass(self):
+        self.assertTrue(issubclass(Value, ValueLike))
+        self.assertTrue(issubclass(MockValueCastable, ValueLike))
+        self.assertTrue(issubclass(int, ValueLike))
+        self.assertFalse(issubclass(range, ValueLike))
+        self.assertFalse(issubclass(EnumMeta, ValueLike))
+        self.assertTrue(issubclass(Enum, ValueLike))
+        self.assertFalse(issubclass(str, ValueLike))
+        self.assertTrue(issubclass(ValueLike, ValueLike))
+
+    def test_isinstance(self):
+        self.assertTrue(isinstance(Const(0, 2), ValueLike))
+        self.assertTrue(isinstance(MockValueCastable(Const(0, 2)), ValueLike))
+        self.assertTrue(isinstance(2, ValueLike))
+        self.assertTrue(isinstance(-2, ValueLike))
+        self.assertFalse(isinstance(range(10), ValueLike))
+
+    def test_enum(self):
+        class EnumA(Enum):
+            A = 1
+            B = 2
+        class EnumB(Enum):
+            A = "a"
+            B = "b"
+        class EnumC(Enum):
+            A = Cat(Const(1, 2), Const(0, 2))
+        class EnumD(Enum):
+            A = 1
+            B = "a"
+        self.assertTrue(issubclass(EnumA, ValueLike))
+        self.assertFalse(issubclass(EnumB, ValueLike))
+        self.assertTrue(issubclass(EnumC, ValueLike))
+        self.assertFalse(issubclass(EnumD, ValueLike))
+        self.assertTrue(isinstance(EnumA.A, ValueLike))
+        self.assertFalse(isinstance(EnumB.A, ValueLike))
+        self.assertTrue(isinstance(EnumC.A, ValueLike))
+        self.assertFalse(isinstance(EnumD.A, ValueLike))
+
+
 class SampleTestCase(FHDLTestCase):
     @_ignore_deprecated
     def test_const(self):