hdl.ast: recursively cast ValueCastable objects to values.

This commit is contained in:
Catherine 2022-04-05 21:29:36 +00:00
parent 3b799481f7
commit 0723f6bac9
3 changed files with 41 additions and 15 deletions

View file

@ -135,16 +135,22 @@ class Value(metaclass=ABCMeta):
Booleans and integers are wrapped into a :class:`Const`. Enumerations whose members are Booleans and integers are wrapped into a :class:`Const`. Enumerations whose members are
all integers are converted to a :class:`Const` with a shape that fits every member. all integers are converted to a :class:`Const` with a shape that fits every member.
:class:`ValueCastable` objects are recursively cast to an Amaranth value.
""" """
if isinstance(obj, Value): while True:
return obj if isinstance(obj, Value):
if isinstance(obj, int): return obj
return Const(obj) elif isinstance(obj, int):
if isinstance(obj, Enum): return Const(obj)
return Const(obj.value, Shape.cast(type(obj))) elif isinstance(obj, Enum):
if isinstance(obj, ValueCastable): return Const(obj.value, Shape.cast(type(obj)))
return obj.as_value() elif isinstance(obj, ValueCastable):
raise TypeError("Object {!r} cannot be converted to an Amaranth value".format(obj)) new_obj = obj.as_value()
else:
raise TypeError("Object {!r} cannot be converted to an Amaranth value".format(obj))
if new_obj is obj:
raise RecursionError("Value-castable object {!r} casts to itself".format(obj))
obj = new_obj
def __init__(self, *, src_loc_at=0): def __init__(self, *, src_loc_at=0):
super().__init__() super().__init__()
@ -1276,7 +1282,7 @@ class UserValue(Value):
class ValueCastable: class ValueCastable:
"""Base class for classes which can be cast to Values. """Interface of objects can be cast to :class:`Value`s.
A ``ValueCastable`` can be cast to ``Value``, meaning its precise representation does not have A ``ValueCastable`` can be cast to ``Value``, meaning its precise representation does not have
to be immediately known. This is useful in certain metaprogramming scenarios. Instead of to be immediately known. This is useful in certain metaprogramming scenarios. Instead of

View file

@ -220,7 +220,7 @@ Value casting
Like shapes, values may be *cast* from other objects, which are called *value-castable*. Casting 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-castable*. Casting allows objects that are not provided by Amaranth, such as integers or enumeration members, to be used in Amaranth expressions directly.
.. TODO: link to UserValue .. 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-castable objects are accepted anywhere values are.

View file

@ -1060,6 +1060,15 @@ class UserValueTestCase(FHDLTestCase):
self.assertEqual(uv.lower_count, 1) self.assertEqual(uv.lower_count, 1)
class MockValueCastable(ValueCastable):
def __init__(self, dest):
self.dest = dest
@ValueCastable.lowermethod
def as_value(self):
return self.dest
class MockValueCastableChanges(ValueCastable): class MockValueCastableChanges(ValueCastable):
def __init__(self, width=0): def __init__(self, width=0):
self.width = width self.width = width
@ -1097,14 +1106,14 @@ class MockValueCastableCustomGetattr(ValueCastable):
class ValueCastableTestCase(FHDLTestCase): class ValueCastableTestCase(FHDLTestCase):
def test_not_decorated(self): def test_not_decorated(self):
with self.assertRaisesRegex(TypeError, with self.assertRaisesRegex(TypeError,
r"^Class 'MockValueCastableNotDecorated' deriving from `ValueCastable` must decorate the `as_value` " r"^Class 'MockValueCastableNotDecorated' deriving from `ValueCastable` must "
r"method with the `ValueCastable.lowermethod` decorator$"): r"decorate the `as_value` method with the `ValueCastable.lowermethod` decorator$"):
vc = MockValueCastableNotDecorated() vc = MockValueCastableNotDecorated()
def test_no_override(self): def test_no_override(self):
with self.assertRaisesRegex(TypeError, with self.assertRaisesRegex(TypeError,
r"^Class 'MockValueCastableNoOverride' deriving from `ValueCastable` must override the `as_value` " r"^Class 'MockValueCastableNoOverride' deriving from `ValueCastable` must "
r"method$"): r"override the `as_value` method$"):
vc = MockValueCastableNoOverride() vc = MockValueCastableNoOverride()
def test_memoized(self): def test_memoized(self):
@ -1121,6 +1130,17 @@ class ValueCastableTestCase(FHDLTestCase):
vc = MockValueCastableCustomGetattr() vc = MockValueCastableCustomGetattr()
vc.as_value() # shouldn't call __getattr__ vc.as_value() # shouldn't call __getattr__
def test_recurse_bad(self):
vc = MockValueCastable(None)
vc.dest = vc
with self.assertRaisesRegex(RecursionError,
r"^Value-castable object <.+> casts to itself$"):
Value.cast(vc)
def test_recurse(self):
vc = MockValueCastable(MockValueCastable(Signal()))
self.assertIsInstance(Value.cast(vc), Signal)
class SampleTestCase(FHDLTestCase): class SampleTestCase(FHDLTestCase):
def test_const(self): def test_const(self):