hdl.ast: improve interaction of ValueCastable with custom __getattr__.
authorwhitequark <whitequark@whitequark.org>
Sun, 3 Oct 2021 20:28:07 +0000 (20:28 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Fri, 31 Dec 2021 19:48:44 +0000 (19:48 +0000)
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.

nmigen/hdl/ast.py
tests/test_hdl_ast.py

index 5ed3a77dd3cdbb9934dfb57054908c2b11b6a8a0..ae010a0af2e6b8973cea72686fbaaece1569f969 100644 (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
index 3604433c4aeddcaf4b788e1cf980f065f9f1e65c..90f3d26d7d7cbd509ff502bce661203a0891a070 100644 (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):