From 3fa9afcdd32dac6323f78ca96359b1c1fc4750fc Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 3 Oct 2021 20:28:07 +0000 Subject: [PATCH] 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. --- nmigen/hdl/ast.py | 14 ++++++++------ tests/test_hdl_ast.py | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/nmigen/hdl/ast.py b/nmigen/hdl/ast.py index 5ed3a77..ae010a0 100644 --- a/nmigen/hdl/ast.py +++ b/nmigen/hdl/ast.py @@ -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 diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py index 3604433..90f3d26 100644 --- a/tests/test_hdl_ast.py +++ b/tests/test_hdl_ast.py @@ -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): -- 2.30.2