Skip to content

Commit 8e6ae9e

Browse files
wanda-phiwhitequark
authored andcommitted
Implement RFC 38: Component signature immutability.
Fixes #996.
1 parent 6ad0d21 commit 8e6ae9e

File tree

3 files changed

+85
-88
lines changed

3 files changed

+85
-88
lines changed

amaranth/lib/wiring.py

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -806,43 +806,41 @@ def connect_dimensions(dimensions, *, out_path, in_path):
806806

807807

808808
class Component(Elaboratable):
809-
def __init__(self):
810-
for name in self.signature.members:
811-
if hasattr(self, name):
812-
raise NameError(f"Cannot initialize attribute for signature member {name!r} "
813-
f"because an attribute with the same name already exists")
814-
self.__dict__.update(self.signature.members.create(path=()))
815-
816-
@property
817-
def signature(self):
809+
def __init__(self, signature=None):
818810
cls = type(self)
819811
members = {}
820812
for base in reversed(cls.mro()[:cls.mro().index(Component)]):
821813
for name, annot in base.__dict__.get("__annotations__", {}).items():
822814
if name.startswith("_"):
823815
continue
824-
if (annot is Value or annot is Signal or annot is Const or
825-
(isinstance(annot, type) and issubclass(annot, ValueCastable)) or
826-
isinstance(annot, Signature)):
827-
if isinstance(annot, type):
828-
annot_repr = annot.__name__
829-
else:
830-
annot_repr = repr(annot)
831-
# To suppress this warning in the rare cases where it is necessary (and naming
832-
# the field with a leading underscore is infeasible), override the property.
833-
warnings.warn(
834-
message=f"Component '{cls.__module__}.{cls.__qualname__}' has "
835-
f"an annotation '{name}: {annot_repr}', which is not "
836-
f"a signature member; did you mean '{name}: In({annot_repr})' "
837-
f"or '{name}: Out({annot_repr})'?",
838-
category=SyntaxWarning,
839-
stacklevel=2)
840-
elif type(annot) is Member:
816+
if type(annot) is Member:
841817
if name in members:
842818
raise SignatureError(f"Member '{name}' is redefined in {base.__module__}.{base.__qualname__}")
843819
members[name] = annot
844820
if not members:
845-
raise NotImplementedError(
846-
f"Component '{cls.__module__}.{cls.__qualname__}' does not have signature member "
847-
f"annotations")
848-
return Signature(members)
821+
if signature is None:
822+
raise NotImplementedError(
823+
f"Component '{cls.__module__}.{cls.__qualname__}' does not have signature "
824+
f"member annotations")
825+
if isinstance(signature, dict):
826+
signature = Signature(signature)
827+
elif not isinstance(signature, Signature):
828+
raise TypeError(f"Object {signature!r} is not a signature nor a dict")
829+
else:
830+
if signature is not None:
831+
raise TypeError(
832+
f"Signature was passed as an argument, but component "
833+
f"'{cls.__module__}.{cls.__qualname__}' already has signature "
834+
f"member annotations")
835+
signature = Signature(members)
836+
837+
self.__signature = signature
838+
for name in signature.members:
839+
if hasattr(self, name):
840+
raise NameError(f"Cannot initialize attribute for signature member {name!r} "
841+
f"because an attribute with the same name already exists")
842+
self.__dict__.update(signature.members.create(path=()))
843+
844+
@property
845+
def signature(self):
846+
return self.__signature

docs/changes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Implemented RFCs
6464
.. _RFC 34: https://amaranth-lang.org/rfcs/0034-interface-rename.html
6565
.. _RFC 35: https://amaranth-lang.org/rfcs/0035-shapelike-valuelike.html
6666
.. _RFC 37: https://amaranth-lang.org/rfcs/0037-make-signature-immutable.html
67+
.. _RFC 38: https://amaranth-lang.org/rfcs/0038-component-signature-immutability.html
6768

6869

6970
* `RFC 1`_: Aggregate data structure library
@@ -85,6 +86,7 @@ Implemented RFCs
8586
* `RFC 34`_: Rename ``amaranth.lib.wiring.Interface`` to ``PureInterface``
8687
* `RFC 35`_: Add ``ShapeLike``, ``ValueLike``
8788
* `RFC 37`_: Make ``Signature`` immutable
89+
* `RFC 38`_: ``Component.signature`` immutability
8890

8991

9092
Language changes

tests/test_lib_wiring.py

Lines changed: 55 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -711,17 +711,15 @@ def create(self, *, path=None, src_loc_at=0):
711711

712712
def test_propagate_flipped(self):
713713
class InterfaceWithFlippedSub(Component):
714-
signature = Signature({
715-
"a": In(Signature({
716-
"b": Out(Signature({
717-
"c": Out(1)
718-
})),
719-
"d": In(Signature({
720-
"e": Out(1)
721-
})),
722-
"f": Out(1)
723-
}))
724-
})
714+
a: In(Signature({
715+
"b": Out(Signature({
716+
"c": Out(1)
717+
})),
718+
"d": In(Signature({
719+
"e": Out(1)
720+
})),
721+
"f": Out(1)
722+
}))
725723

726724
def __init__(self):
727725
super().__init__()
@@ -984,53 +982,6 @@ def __init__(self):
984982
r"with the same name already exists$"):
985983
C()
986984

987-
def test_missing_in_out_warning(self):
988-
class C1(Component):
989-
prt1 : In(1)
990-
sig2 : Signal
991-
992-
with self.assertWarnsRegex(SyntaxWarning,
993-
r"^Component '.+\.C1' has an annotation 'sig2: Signal', which is not a signature "
994-
r"member; did you mean 'sig2: In\(Signal\)' or 'sig2: Out\(Signal\)'\?$"):
995-
C1().signature
996-
997-
class C2(Component):
998-
prt1 : In(1)
999-
sig2 : Signature({})
1000-
1001-
with self.assertWarnsRegex(SyntaxWarning,
1002-
r"^Component '.+\.C2' has an annotation 'sig2: Signature\({}\)', which is not "
1003-
r"a signature member; did you mean 'sig2: In\(Signature\({}\)\)' or "
1004-
r"'sig2: Out\(Signature\({}\)\)'\?$"):
1005-
C2().signature
1006-
1007-
class MockValueCastable(ValueCastable):
1008-
def shape(self): pass
1009-
@ValueCastable.lowermethod
1010-
def as_value(self): pass
1011-
1012-
class C3(Component):
1013-
prt1 : In(1)
1014-
val2 : MockValueCastable
1015-
1016-
with self.assertWarnsRegex(SyntaxWarning,
1017-
r"^Component '.+\.C3' has an annotation 'val2: MockValueCastable', which is not "
1018-
r"a signature member; did you mean 'val2: In\(MockValueCastable\)' or "
1019-
r"'val2: Out\(MockValueCastable\)'\?$"):
1020-
C3().signature
1021-
1022-
def test_bug_882(self):
1023-
class PageBuffer(Component):
1024-
rand: Signature({}).flip()
1025-
other: Out(1)
1026-
1027-
with self.assertWarnsRegex(SyntaxWarning,
1028-
r"^Component '.+\.PageBuffer' has an annotation 'rand: Signature\({}\)\.flip\(\)', "
1029-
r"which is not a signature member; did you mean "
1030-
r"'rand: In\(Signature\({}\)\.flip\(\)\)' or "
1031-
r"'rand: Out\(Signature\({}\)\.flip\(\)\)'\?$"):
1032-
PageBuffer()
1033-
1034985
def test_inherit(self):
1035986
class A(Component):
1036987
clk: In(1)
@@ -1054,3 +1005,49 @@ class B(A):
10541005
with self.assertRaisesRegex(SignatureError,
10551006
r"^Member 'a' is redefined in .*<locals>.B$"):
10561007
B()
1008+
1009+
def test_create(self):
1010+
class C(Component):
1011+
def __init__(self, width):
1012+
super().__init__(Signature({
1013+
"a": In(width)
1014+
}))
1015+
1016+
c = C(2)
1017+
self.assertEqual(c.signature, Signature({"a": In(2)}))
1018+
self.assertIsInstance(c.a, Signal)
1019+
self.assertEqual(c.a.shape(), unsigned(2))
1020+
1021+
def test_create_dict(self):
1022+
class C(Component):
1023+
def __init__(self, width):
1024+
super().__init__({
1025+
"a": In(width)
1026+
})
1027+
1028+
c = C(2)
1029+
self.assertEqual(c.signature, Signature({"a": In(2)}))
1030+
self.assertIsInstance(c.a, Signal)
1031+
self.assertEqual(c.a.shape(), unsigned(2))
1032+
1033+
def test_create_wrong(self):
1034+
class C(Component):
1035+
a: In(2)
1036+
1037+
def __init__(self, width):
1038+
super().__init__(Signature({
1039+
"a": In(width)
1040+
}))
1041+
1042+
with self.assertRaisesRegex(TypeError,
1043+
r"^Signature was passed as an argument, but component '.*.C' already has signature member annotations$"):
1044+
C(2)
1045+
1046+
def test_create_wrong_type(self):
1047+
class C(Component):
1048+
def __init__(self, width):
1049+
super().__init__(4)
1050+
1051+
with self.assertRaisesRegex(TypeError,
1052+
r"^Object 4 is not a signature nor a dict$"):
1053+
C(2)

0 commit comments

Comments
 (0)