Skip to content

Commit 6ad0d21

Browse files
wanda-phiwhitequark
authored andcommitted
Implement RFC 37: Make Signature immutable.
Fixes #995.
1 parent b9c2404 commit 6ad0d21

File tree

3 files changed

+51
-175
lines changed

3 files changed

+51
-175
lines changed

amaranth/lib/wiring.py

Lines changed: 21 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,12 @@ class SignatureError(Exception):
151151
class SignatureMembers(Mapping):
152152
def __init__(self, members=()):
153153
self._dict = dict()
154-
self._frozen = False
155-
self += members
154+
for name, member in dict(members).items():
155+
self._check_name(name)
156+
if type(member) is not Member:
157+
raise TypeError(f"Value {member!r} must be a member; "
158+
f"did you mean In({member!r}) or Out({member!r})?")
159+
self._dict[name] = member
156160

157161
def flip(self):
158162
return FlippedSignatureMembers(self)
@@ -179,16 +183,7 @@ def __getitem__(self, name):
179183
return self._dict[name]
180184

181185
def __setitem__(self, name, member):
182-
self._check_name(name)
183-
if name in self._dict:
184-
raise SignatureError(f"Member '{name}' already exists in the signature and cannot "
185-
f"be replaced")
186-
if type(member) is not Member:
187-
raise TypeError(f"Assigned value {member!r} must be a member; "
188-
f"did you mean In({member!r}) or Out({member!r})?")
189-
if self._frozen:
190-
raise SignatureError("Cannot add members to a frozen signature")
191-
self._dict[name] = member
186+
raise SignatureError("Members cannot be added to a signature once constructed")
192187

193188
def __delitem__(self, name):
194189
raise SignatureError("Members cannot be removed from a signature")
@@ -199,21 +194,6 @@ def __iter__(self):
199194
def __len__(self):
200195
return len(self._dict)
201196

202-
def __iadd__(self, members):
203-
for name, member in dict(members).items():
204-
self[name] = member
205-
return self
206-
207-
@property
208-
def frozen(self):
209-
return self._frozen
210-
211-
def freeze(self):
212-
self._frozen = True
213-
for member in self.values():
214-
if member.is_signature:
215-
member.signature.freeze()
216-
217197
def flatten(self, *, path=()):
218198
for name, member in self.items():
219199
yield ((*path, name), member)
@@ -244,8 +224,7 @@ def create_dimensions(dimensions, *, path, src_loc_at):
244224
return attrs
245225

246226
def __repr__(self):
247-
frozen_repr = ".freeze()" if self._frozen else ""
248-
return f"SignatureMembers({self._dict}){frozen_repr}"
227+
return f"SignatureMembers({self._dict})"
249228

250229

251230
@final
@@ -277,17 +256,6 @@ def __iter__(self):
277256
def __len__(self):
278257
return self.__unflipped.__len__()
279258

280-
def __iadd__(self, members):
281-
self.__unflipped.__iadd__({name: member.flip() for name, member in members.items()})
282-
return self
283-
284-
@property
285-
def frozen(self):
286-
return self.__unflipped.frozen
287-
288-
def freeze(self):
289-
self.__unflipped.freeze()
290-
291259
# These methods do not access instance variables and so their implementation can be shared
292260
# between the normal and the flipped member collections.
293261
flatten = SignatureMembers.flatten
@@ -358,12 +326,6 @@ def flip(self):
358326
def members(self):
359327
return self.__members
360328

361-
@members.setter
362-
def members(self, new_members):
363-
# The setter is called when `sig.members += ...` is used.
364-
if new_members is not self.__members:
365-
raise AttributeError("property 'members' of 'Signature' object cannot be set")
366-
367329
def __eq__(self, other):
368330
other_unflipped = other.flip() if type(other) is FlippedSignature else other
369331
if type(self) is type(other_unflipped) is Signature:
@@ -374,14 +336,6 @@ def __eq__(self, other):
374336
# usually be overridden in a derived class.
375337
return self is other
376338

377-
@property
378-
def frozen(self):
379-
return self.members.frozen
380-
381-
def freeze(self):
382-
self.members.freeze()
383-
return self
384-
385339
def flatten(self, obj):
386340
for name, member in self.members.items():
387341
path = (name,)
@@ -544,11 +498,6 @@ def flip(self):
544498
def members(self):
545499
return FlippedSignatureMembers(self.__unflipped.members)
546500

547-
@members.setter
548-
def members(self, new_members):
549-
if new_members.flip() is not self.__unflipped.members:
550-
raise AttributeError("property 'members' of 'FlippedSignature' object cannot be set")
551-
552501
def __eq__(self, other):
553502
if type(other) is FlippedSignature:
554503
# Trivial case.
@@ -561,10 +510,8 @@ def __eq__(self, other):
561510
# in infinite recursion.
562511
return NotImplemented
563512

564-
# These methods do not access instance variables and so their implementation can be shared
513+
# This method does not access instance variables and so its implementation can be shared
565514
# between the normal and the flipped member collections.
566-
frozen = Signature.frozen
567-
freeze = Signature.freeze
568515
is_compliant = Signature.is_compliant
569516

570517
# FIXME: document this logic
@@ -581,16 +528,10 @@ def __getattr__(self, name):
581528
return getattr(self.__unflipped, name)
582529

583530
def __setattr__(self, name, value):
584-
if name == "members":
585-
# Although `sig.flip().members` does not call `__getattr__` but directly invokes
586-
# the descriptor of the `FlippedSignature.members` property, `sig.flip().members +=`
587-
# does call `__setattr__`, and this must be special-cased for the setter to work.
588-
FlippedSignature.members.__set__(self, value)
589-
else:
590-
try: # descriptor first
591-
_gettypeattr(self.__unflipped, name).__set__(self, value)
592-
except AttributeError:
593-
setattr(self.__unflipped, name, value)
531+
try: # descriptor first
532+
_gettypeattr(self.__unflipped, name).__set__(self, value)
533+
except AttributeError:
534+
setattr(self.__unflipped, name, value)
594535

595536
def __delattr__(self, name):
596537
try: # descriptor first
@@ -703,7 +644,7 @@ def connect(m, *args, **kwargs):
703644
reasons_as_string = "".join("\n- " + reason for reason in reasons)
704645
raise ConnectionError(f"Argument {handle!r} does not match its signature:" +
705646
reasons_as_string)
706-
signatures[handle] = obj.signature.freeze()
647+
signatures[handle] = obj.signature
707648

708649
# Collate signatures and build connections.
709650
flattens = {handle: signature.members.flatten()
@@ -875,8 +816,8 @@ def __init__(self):
875816
@property
876817
def signature(self):
877818
cls = type(self)
878-
signature = Signature({})
879-
for base in cls.mro()[:cls.mro().index(Component)]:
819+
members = {}
820+
for base in reversed(cls.mro()[:cls.mro().index(Component)]):
880821
for name, annot in base.__dict__.get("__annotations__", {}).items():
881822
if name.startswith("_"):
882823
continue
@@ -897,9 +838,11 @@ def signature(self):
897838
category=SyntaxWarning,
898839
stacklevel=2)
899840
elif type(annot) is Member:
900-
signature.members[name] = annot
901-
if not signature.members:
841+
if name in members:
842+
raise SignatureError(f"Member '{name}' is redefined in {base.__module__}.{base.__qualname__}")
843+
members[name] = annot
844+
if not members:
902845
raise NotImplementedError(
903846
f"Component '{cls.__module__}.{cls.__qualname__}' does not have signature member "
904847
f"annotations")
905-
return signature
848+
return Signature(members)

docs/changes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Implemented RFCs
6363
.. _RFC 31: https://amaranth-lang.org/rfcs/0031-enumeration-type-safety.html
6464
.. _RFC 34: https://amaranth-lang.org/rfcs/0034-interface-rename.html
6565
.. _RFC 35: https://amaranth-lang.org/rfcs/0035-shapelike-valuelike.html
66+
.. _RFC 37: https://amaranth-lang.org/rfcs/0037-make-signature-immutable.html
6667

6768

6869
* `RFC 1`_: Aggregate data structure library
@@ -83,6 +84,7 @@ Implemented RFCs
8384
* `RFC 31`_: Enumeration type safety
8485
* `RFC 34`_: Rename ``amaranth.lib.wiring.Interface`` to ``PureInterface``
8586
* `RFC 35`_: Add ``ShapeLike``, ``ValueLike``
87+
* `RFC 37`_: Make ``Signature`` immutable
8688

8789

8890
Language changes

0 commit comments

Comments
 (0)