Implement RFC 38: Component signature immutability.

Fixes #996.
This commit is contained in:
Wanda 2023-12-11 20:36:30 +01:00 committed by Catherine
parent 6ad0d21cc9
commit 8e6ae9e6e0
3 changed files with 85 additions and 88 deletions

View file

@ -806,43 +806,41 @@ def connect(m, *args, **kwargs):
class Component(Elaboratable):
def __init__(self):
for name in self.signature.members:
if hasattr(self, name):
raise NameError(f"Cannot initialize attribute for signature member {name!r} "
f"because an attribute with the same name already exists")
self.__dict__.update(self.signature.members.create(path=()))
@property
def signature(self):
def __init__(self, signature=None):
cls = type(self)
members = {}
for base in reversed(cls.mro()[:cls.mro().index(Component)]):
for name, annot in base.__dict__.get("__annotations__", {}).items():
if name.startswith("_"):
continue
if (annot is Value or annot is Signal or annot is Const or
(isinstance(annot, type) and issubclass(annot, ValueCastable)) or
isinstance(annot, Signature)):
if isinstance(annot, type):
annot_repr = annot.__name__
else:
annot_repr = repr(annot)
# To suppress this warning in the rare cases where it is necessary (and naming
# the field with a leading underscore is infeasible), override the property.
warnings.warn(
message=f"Component '{cls.__module__}.{cls.__qualname__}' has "
f"an annotation '{name}: {annot_repr}', which is not "
f"a signature member; did you mean '{name}: In({annot_repr})' "
f"or '{name}: Out({annot_repr})'?",
category=SyntaxWarning,
stacklevel=2)
elif type(annot) is Member:
if type(annot) is Member:
if name in members:
raise SignatureError(f"Member '{name}' is redefined in {base.__module__}.{base.__qualname__}")
members[name] = annot
if not members:
raise NotImplementedError(
f"Component '{cls.__module__}.{cls.__qualname__}' does not have signature member "
f"annotations")
return Signature(members)
if signature is None:
raise NotImplementedError(
f"Component '{cls.__module__}.{cls.__qualname__}' does not have signature "
f"member annotations")
if isinstance(signature, dict):
signature = Signature(signature)
elif not isinstance(signature, Signature):
raise TypeError(f"Object {signature!r} is not a signature nor a dict")
else:
if signature is not None:
raise TypeError(
f"Signature was passed as an argument, but component "
f"'{cls.__module__}.{cls.__qualname__}' already has signature "
f"member annotations")
signature = Signature(members)
self.__signature = signature
for name in signature.members:
if hasattr(self, name):
raise NameError(f"Cannot initialize attribute for signature member {name!r} "
f"because an attribute with the same name already exists")
self.__dict__.update(signature.members.create(path=()))
@property
def signature(self):
return self.__signature

View file

@ -64,6 +64,7 @@ Implemented RFCs
.. _RFC 34: https://amaranth-lang.org/rfcs/0034-interface-rename.html
.. _RFC 35: https://amaranth-lang.org/rfcs/0035-shapelike-valuelike.html
.. _RFC 37: https://amaranth-lang.org/rfcs/0037-make-signature-immutable.html
.. _RFC 38: https://amaranth-lang.org/rfcs/0038-component-signature-immutability.html
* `RFC 1`_: Aggregate data structure library
@ -85,6 +86,7 @@ Implemented RFCs
* `RFC 34`_: Rename ``amaranth.lib.wiring.Interface`` to ``PureInterface``
* `RFC 35`_: Add ``ShapeLike``, ``ValueLike``
* `RFC 37`_: Make ``Signature`` immutable
* `RFC 38`_: ``Component.signature`` immutability
Language changes

View file

@ -711,17 +711,15 @@ class FlippedInterfaceTestCase(unittest.TestCase):
def test_propagate_flipped(self):
class InterfaceWithFlippedSub(Component):
signature = Signature({
"a": In(Signature({
"b": Out(Signature({
"c": Out(1)
})),
"d": In(Signature({
"e": Out(1)
})),
"f": Out(1)
}))
})
a: In(Signature({
"b": Out(Signature({
"c": Out(1)
})),
"d": In(Signature({
"e": Out(1)
})),
"f": Out(1)
}))
def __init__(self):
super().__init__()
@ -984,53 +982,6 @@ class ComponentTestCase(unittest.TestCase):
r"with the same name already exists$"):
C()
def test_missing_in_out_warning(self):
class C1(Component):
prt1 : In(1)
sig2 : Signal
with self.assertWarnsRegex(SyntaxWarning,
r"^Component '.+\.C1' has an annotation 'sig2: Signal', which is not a signature "
r"member; did you mean 'sig2: In\(Signal\)' or 'sig2: Out\(Signal\)'\?$"):
C1().signature
class C2(Component):
prt1 : In(1)
sig2 : Signature({})
with self.assertWarnsRegex(SyntaxWarning,
r"^Component '.+\.C2' has an annotation 'sig2: Signature\({}\)', which is not "
r"a signature member; did you mean 'sig2: In\(Signature\({}\)\)' or "
r"'sig2: Out\(Signature\({}\)\)'\?$"):
C2().signature
class MockValueCastable(ValueCastable):
def shape(self): pass
@ValueCastable.lowermethod
def as_value(self): pass
class C3(Component):
prt1 : In(1)
val2 : MockValueCastable
with self.assertWarnsRegex(SyntaxWarning,
r"^Component '.+\.C3' has an annotation 'val2: MockValueCastable', which is not "
r"a signature member; did you mean 'val2: In\(MockValueCastable\)' or "
r"'val2: Out\(MockValueCastable\)'\?$"):
C3().signature
def test_bug_882(self):
class PageBuffer(Component):
rand: Signature({}).flip()
other: Out(1)
with self.assertWarnsRegex(SyntaxWarning,
r"^Component '.+\.PageBuffer' has an annotation 'rand: Signature\({}\)\.flip\(\)', "
r"which is not a signature member; did you mean "
r"'rand: In\(Signature\({}\)\.flip\(\)\)' or "
r"'rand: Out\(Signature\({}\)\.flip\(\)\)'\?$"):
PageBuffer()
def test_inherit(self):
class A(Component):
clk: In(1)
@ -1054,3 +1005,49 @@ class ComponentTestCase(unittest.TestCase):
with self.assertRaisesRegex(SignatureError,
r"^Member 'a' is redefined in .*<locals>.B$"):
B()
def test_create(self):
class C(Component):
def __init__(self, width):
super().__init__(Signature({
"a": In(width)
}))
c = C(2)
self.assertEqual(c.signature, Signature({"a": In(2)}))
self.assertIsInstance(c.a, Signal)
self.assertEqual(c.a.shape(), unsigned(2))
def test_create_dict(self):
class C(Component):
def __init__(self, width):
super().__init__({
"a": In(width)
})
c = C(2)
self.assertEqual(c.signature, Signature({"a": In(2)}))
self.assertIsInstance(c.a, Signal)
self.assertEqual(c.a.shape(), unsigned(2))
def test_create_wrong(self):
class C(Component):
a: In(2)
def __init__(self, width):
super().__init__(Signature({
"a": In(width)
}))
with self.assertRaisesRegex(TypeError,
r"^Signature was passed as an argument, but component '.*.C' already has signature member annotations$"):
C(2)
def test_create_wrong_type(self):
class C(Component):
def __init__(self, width):
super().__init__(4)
with self.assertRaisesRegex(TypeError,
r"^Object 4 is not a signature nor a dict$"):
C(2)