Skip to content

Commit c6000b1

Browse files
wanda-phiwhitequark
authored andcommitted
lib.data: implement equality for View, reject all other operators.
1 parent 4bfe2cd commit c6000b1

File tree

2 files changed

+106
-1
lines changed

2 files changed

+106
-1
lines changed

amaranth/lib/data.py

+48-1
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,10 @@ class View(ValueCastable):
580580
an Amaranth value instead of a constant integer. The returned element is chosen dynamically
581581
in that case.
582582
583+
A view can only be compared for equality with another view of the same layout,
584+
returning a single-bit value. No other operators are supported on views. If required,
585+
a view can be converted back to its underlying value via :meth:`as_value`.
586+
583587
Custom view classes
584588
###################
585589
@@ -609,7 +613,7 @@ def __init__(self, layout, target):
609613
warnings.warn("View layout includes a field {!r} that will be shadowed by "
610614
"the view attribute '{}.{}.{}'"
611615
.format(name, type(self).__module__, type(self).__qualname__, name),
612-
SyntaxWarning, stacklevel=1)
616+
SyntaxWarning, stacklevel=2)
613617
self.__orig_layout = layout
614618
self.__layout = cast_layout
615619
self.__target = cast_target
@@ -732,6 +736,49 @@ def __getattr__(self, name):
732736
.format(self.__target, name))
733737
return item
734738

739+
def __eq__(self, other):
740+
if not isinstance(other, View) or self.__layout != other.__layout:
741+
raise TypeError(f"View of {self.__layout!r} can only be compared to another view of the same layout, not {other!r}")
742+
return self.__target == other.__target
743+
744+
def __ne__(self, other):
745+
if not isinstance(other, View) or self.__layout != other.__layout:
746+
raise TypeError(f"View of {self.__layout!r} can only be compared to another view of the same layout, not {other!r}")
747+
return self.__target != other.__target
748+
749+
def __add__(self, other):
750+
raise TypeError("Cannot perform arithmetic operations on a View")
751+
752+
__radd__ = __add__
753+
__sub__ = __add__
754+
__rsub__ = __add__
755+
__mul__ = __add__
756+
__rmul__ = __add__
757+
__floordiv__ = __add__
758+
__rfloordiv__ = __add__
759+
__mod__ = __add__
760+
__rmod__ = __add__
761+
__lshift__ = __add__
762+
__rlshift__ = __add__
763+
__rshift__ = __add__
764+
__rrshift__ = __add__
765+
__lt__ = __add__
766+
__le__ = __add__
767+
__gt__ = __add__
768+
__ge__ = __add__
769+
770+
def __and__(self, other):
771+
raise TypeError("Cannot perform bitwise operations on a View")
772+
773+
__rand__ = __and__
774+
__or__ = __and__
775+
__ror__ = __and__
776+
__xor__ = __and__
777+
__rxor__ = __and__
778+
779+
def __repr__(self):
780+
return f"{self.__class__.__name__}({self.__layout!r}, {self.__target!r})"
781+
735782

736783
class _AggregateMeta(ShapeCastable, type):
737784
def __new__(metacls, name, bases, namespace):

tests/test_lib_data.py

+58
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from enum import Enum
2+
import operator
23
from unittest import TestCase
34

45
from amaranth.hdl import *
@@ -632,6 +633,63 @@ def test_bug_837_array_layout_getattr(self):
632633
r"^View of \(sig \$signal\) with an array layout does not have fields$"):
633634
Signal(ArrayLayout(unsigned(1), 1), reset=[0]).reset
634635

636+
def test_eq(self):
637+
s1 = Signal(StructLayout({"a": unsigned(2)}))
638+
s2 = Signal(StructLayout({"a": unsigned(2)}))
639+
s3 = Signal(StructLayout({"a": unsigned(1), "b": unsigned(1)}))
640+
self.assertRepr(s1 == s2, "(== (sig s1) (sig s2))")
641+
self.assertRepr(s1 != s2, "(!= (sig s1) (sig s2))")
642+
with self.assertRaisesRegex(TypeError,
643+
r"^View of .* can only be compared to another view of the same layout, not .*$"):
644+
s1 == s3
645+
with self.assertRaisesRegex(TypeError,
646+
r"^View of .* can only be compared to another view of the same layout, not .*$"):
647+
s1 != s3
648+
with self.assertRaisesRegex(TypeError,
649+
r"^View of .* can only be compared to another view of the same layout, not .*$"):
650+
s1 == Const(0, 2)
651+
with self.assertRaisesRegex(TypeError,
652+
r"^View of .* can only be compared to another view of the same layout, not .*$"):
653+
s1 != Const(0, 2)
654+
655+
def test_operator(self):
656+
s1 = Signal(StructLayout({"a": unsigned(2)}))
657+
s2 = Signal(unsigned(2))
658+
for op in [
659+
operator.__add__,
660+
operator.__sub__,
661+
operator.__mul__,
662+
operator.__floordiv__,
663+
operator.__mod__,
664+
operator.__lshift__,
665+
operator.__rshift__,
666+
operator.__lt__,
667+
operator.__le__,
668+
operator.__gt__,
669+
operator.__ge__,
670+
]:
671+
with self.assertRaisesRegex(TypeError,
672+
r"^Cannot perform arithmetic operations on a View$"):
673+
op(s1, s2)
674+
with self.assertRaisesRegex(TypeError,
675+
r"^Cannot perform arithmetic operations on a View$"):
676+
op(s2, s1)
677+
for op in [
678+
operator.__and__,
679+
operator.__or__,
680+
operator.__xor__,
681+
]:
682+
with self.assertRaisesRegex(TypeError,
683+
r"^Cannot perform bitwise operations on a View$"):
684+
op(s1, s2)
685+
with self.assertRaisesRegex(TypeError,
686+
r"^Cannot perform bitwise operations on a View$"):
687+
op(s2, s1)
688+
689+
def test_repr(self):
690+
s1 = Signal(StructLayout({"a": unsigned(2)}))
691+
self.assertRepr(s1, "View(StructLayout({'a': unsigned(2)}), (sig s1))")
692+
635693

636694
class StructTestCase(FHDLTestCase):
637695
def test_construct(self):

0 commit comments

Comments
 (0)