diff --git a/amaranth/lib/wiring.py b/amaranth/lib/wiring.py index bc30bc8..bb8b4cb 100644 --- a/amaranth/lib/wiring.py +++ b/amaranth/lib/wiring.py @@ -353,6 +353,12 @@ class Signature(metaclass=SignatureMeta): def members(self): return self.__members + @members.setter + def members(self, new_members): + # The setter is called when `sig.members += ...` is used. + if new_members is not self.__members: + raise AttributeError("property 'members' of 'Signature' object cannot be set") + def __eq__(self, other): other_unflipped = other.flip() if type(other) is FlippedSignature else other if type(self) is type(other_unflipped) is Signature: @@ -518,6 +524,11 @@ class FlippedSignature: def members(self): return FlippedSignatureMembers(self.__unflipped.members) + @members.setter + def members(self, new_members): + if new_members.flip() is not self.__unflipped.members: + raise AttributeError("property 'members' of 'FlippedSignature' object cannot be set") + def __eq__(self, other): if type(other) is FlippedSignature: # Trivial case. @@ -550,10 +561,16 @@ class FlippedSignature: return getattr(self.__unflipped, name) def __setattr__(self, name, value): - try: # descriptor first - _gettypeattr(self.__unflipped, name).__set__(self, value) - except AttributeError: - setattr(self.__unflipped, name, value) + if name == "members": + # Although `sig.flip().members` does not call `__getattr__` but directly invokes + # the descriptor of the `FlippedSignature.members` property, `sig.flip().members +=` + # does call `__setattr__`, and this must be special-cased for the setter to work. + FlippedSignature.members.__set__(self, value) + else: + try: # descriptor first + _gettypeattr(self.__unflipped, name).__set__(self, value) + except AttributeError: + setattr(self.__unflipped, name, value) def __delattr__(self, name): try: # descriptor first diff --git a/tests/test_lib_wiring.py b/tests/test_lib_wiring.py index f5af0f2..c08f5dd 100644 --- a/tests/test_lib_wiring.py +++ b/tests/test_lib_wiring.py @@ -374,6 +374,18 @@ class SignatureTestCase(unittest.TestCase): r"^Cannot add members to a frozen signature$"): sig.members += {"b": Out(1)} + def test_members_plus_equal(self): + sig = Signature({}) + # This invokes the setter of Signature.members. + sig.members += {"a": In(1)} + self.assertIn("a", sig.members) + + def test_members_equal_wrong(self): + sig = Signature({}) + with self.assertRaisesRegex(AttributeError, + r"property 'members' of 'Signature' object cannot be set"): + sig.members = SignatureMembers({}) + def assertFlattenedSignature(self, actual, expected): for (a_path, a_member, a_value), (b_path, b_member, b_value) in zip(actual, expected): self.assertEqual(a_path, b_path) @@ -595,6 +607,18 @@ class FlippedSignatureTestCase(unittest.TestCase): fsig.x() self.assertEqual(x_type, S) + def test_members_plus_equal(self): + sig = Signature({}) + # This invokes the setter of FlippedSignature.members. + sig.flip().members += {"a": In(1)} + self.assertIn("a", sig.members) + + def test_members_equal_wrong(self): + sig = Signature({}) + with self.assertRaisesRegex(AttributeError, + r"property 'members' of 'FlippedSignature' object cannot be set"): + sig.flip().members = SignatureMembers({}) + class InterfaceTestCase(unittest.TestCase): pass