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): def lowermethod(func):
"""Decorator to memoize lowering methods. """Decorator to memoize lowering methods.
Ensures the decorated method is called only once, with subsequent method calls returning the Ensures the decorated method is called only once, with subsequent method calls returning
object returned by the first first method call. the object returned by the first first method call.
This decorator is required to decorate the ``as_value`` method of ``ValueCastable`` subclasses. This decorator is required to decorate the ``as_value`` method of ``ValueCastable``
This is to ensure that nMigen's view of representation of all values stays internally subclasses. This is to ensure that nMigen's view of representation of all values stays
consistent. internally consistent.
""" """
@functools.wraps(func) @functools.wraps(func)
def wrapper_memoized(self, *args, **kwargs): 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) self.__lowered_to = func(self, *args, **kwargs)
return self.__lowered_to return self.__lowered_to
wrapper_memoized.__memoized = True wrapper_memoized.__memoized = True

View file

@ -1060,6 +1060,18 @@ class MockValueCastableNoOverride(ValueCastable):
pass 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): class ValueCastableTestCase(FHDLTestCase):
def test_not_decorated(self): def test_not_decorated(self):
with self.assertRaisesRegex(TypeError, with self.assertRaisesRegex(TypeError,
@ -1083,6 +1095,10 @@ class ValueCastableTestCase(FHDLTestCase):
sig3 = Value.cast(vc) sig3 = Value.cast(vc)
self.assertIs(sig1, sig3) self.assertIs(sig1, sig3)
def test_custom_getattr(self):
vc = MockValueCastableCustomGetattr()
vc.as_value() # shouldn't call __getattr__
class SampleTestCase(FHDLTestCase): class SampleTestCase(FHDLTestCase):
def test_const(self): def test_const(self):