Repository: amaranth-lang/amaranth
Pull request #1117 (2024-02-13 04:42:37)

hdl.ast: fix `shift_right` and `as_signed` edge cases. [0.4 backport]

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1117?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`94c504a`)](https://app.codecov.io/gh/amaranth-lang/amaranth/commit/94c504afc7d81738ecdc9523a2615ef43ecbf51a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) 83.97% compared to head [(`8c6d642`)](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1117?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) 83.93%.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           v0.4.x    #1117      +/-   ##
==========================================
- Coverage   83.97%   83.93%   -0.05%     
==========================================
  Files          55       55              
  Lines        8080     8084       +4     
  Branches     1971     1973       +2     
==========================================
  Hits         6785     6785              
- Misses       1081     1086       +5     
+ Partials      214      213       -1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1117?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).


Patch:
diff --git a/amaranth/hdl/ast.py b/amaranth/hdl/ast.py
index 47347ec..9b7b6e7 100644
--- a/amaranth/hdl/ast.py
+++ b/amaranth/hdl/ast.py
@@ -394,6 +394,8 @@ class Value(metaclass=ABCMeta):
         Value, out
             This ``Value`` reinterpreted as a signed integer.
         """
+        if len(self) == 0:
+            raise ValueError("Cannot create a 0-width signed value")
         return Operator("s", [self])
 
     def bool(self):
@@ -589,6 +591,8 @@ class Value(metaclass=ABCMeta):
         if amount < 0:
             return self.shift_left(-amount)
         if self.shape().signed:
+            if amount >= len(self):
+                amount = len(self) - 1
             return self[amount:].as_signed()
         else:
             return self[amount:] # unsigned


Test patch:
diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py
index 65e3ce1..de8288a 100644
--- a/tests/test_hdl_ast.py
+++ b/tests/test_hdl_ast.py
@@ -326,7 +326,7 @@ class ValueTestCase(FHDLTestCase):
         self.assertRepr(Const(256, signed(9)).shift_left(-5),
                         "(s (slice (const 9'sd-256) 5:9))")
         self.assertRepr(Const(256, signed(9)).shift_left(-15),
-                        "(s (slice (const 9'sd-256) 9:9))")
+                        "(s (slice (const 9'sd-256) 8:9))")
 
     def test_shift_left_wrong(self):
         with self.assertRaisesRegex(TypeError,
@@ -350,12 +350,20 @@ class ValueTestCase(FHDLTestCase):
                         "(slice (const 9'd256) 1:9)")
         self.assertRepr(Const(256, unsigned(9)).shift_right(5),
                         "(slice (const 9'd256) 5:9)")
+        self.assertRepr(Const(256, unsigned(9)).shift_right(15),
+                        "(slice (const 9'd256) 9:9)")
         self.assertRepr(Const(256, signed(9)).shift_right(1),
                         "(s (slice (const 9'sd-256) 1:9))")
         self.assertRepr(Const(256, signed(9)).shift_right(5),
                         "(s (slice (const 9'sd-256) 5:9))")
+        self.assertRepr(Const(256, signed(9)).shift_right(7),
+                        "(s (slice (const 9'sd-256) 7:9))")
+        self.assertRepr(Const(256, signed(9)).shift_right(8),
+                        "(s (slice (const 9'sd-256) 8:9))")
+        self.assertRepr(Const(256, signed(9)).shift_right(9),
+                        "(s (slice (const 9'sd-256) 8:9))")
         self.assertRepr(Const(256, signed(9)).shift_right(15),
-                        "(s (slice (const 9'sd-256) 9:9))")
+                        "(s (slice (const 9'sd-256) 8:9))")
 
     def test_shift_right_wrong(self):
         with self.assertRaisesRegex(TypeError,
@@ -480,6 +488,11 @@ class OperatorTestCase(FHDLTestCase):
         self.assertEqual(repr(v), "(s (const 4'd1))")
         self.assertEqual(v.shape(), signed(4))
 
+    def test_as_signed_wrong(self):
+        with self.assertRaisesRegex(ValueError,
+                r"^Cannot create a 0-width signed value$"):
+            Const(0, 0).as_signed()
+
     def test_pos(self):
         self.assertRepr(+Const(10), "(const 4'd10)")