hdl.ast: improve interaction of ValueCastable with custom __getattr__.

Avoid calling `__getattr__("_ValueCastable__lowered_to")` when
a ValueCastable has custom `__getattr__` implementation; this avoids
the need for downstream code to be aware of this implementataion
detail.
This commit is contained in:
whitequark 2021-10-03 20:28:07 +00:00
parent fac1b4b2d1
commit 11914a1e67
2 changed files with 24 additions and 6 deletions

View file

@ -1305,16 +1305,18 @@ class ValueCastable:
def lowermethod(func):
"""Decorator to memoize lowering methods.
Ensures the decorated method is called only once, with subsequent method calls returning the
object returned by the first first method call.
Ensures the decorated method is called only once, with subsequent method calls returning
the object returned by the first first method call.
This decorator is required to decorate the ``as_value`` method of ``ValueCastable`` subclasses.
This is to ensure that nMigen's view of representation of all values stays internally
consistent.
This decorator is required to decorate the ``as_value`` method of ``ValueCastable``
subclasses. This is to ensure that nMigen's view of representation of all values stays
internally consistent.
"""
@functools.wraps(func)
def wrapper_memoized(self, *args, **kwargs):
if not hasattr(self, "_ValueCastable__lowered_to"):
# Use `in self.__dict__` instead of `hasattr` to avoid interfering with custom
# `__getattr__` implementations.
if not "_ValueCastable__lowered_to" in self.__dict__:
self.__lowered_to = func(self, *args, **kwargs)
return self.__lowered_to
wrapper_memoized.__memoized = True

View file

@ -1060,6 +1060,18 @@ class MockValueCastableNoOverride(ValueCastable):
pass
class MockValueCastableCustomGetattr(ValueCastable):
def __init__(self):
pass
@ValueCastable.lowermethod
def as_value(self):
return Const(0)
def __getattr__(self, attr):
assert False
class ValueCastableTestCase(FHDLTestCase):
def test_not_decorated(self):
with self.assertRaisesRegex(TypeError,
@ -1083,6 +1095,10 @@ class ValueCastableTestCase(FHDLTestCase):
sig3 = Value.cast(vc)
self.assertIs(sig1, sig3)
def test_custom_getattr(self):
vc = MockValueCastableCustomGetattr()
vc.as_value() # shouldn't call __getattr__
class SampleTestCase(FHDLTestCase):
def test_const(self):