Repository: amaranth-lang/amaranth
Pull request #1036 (2024-01-16 11:56:32)

Implement RFC 39: Warn on `Case()` and `matches()` with no patterns.

whitequark: This does seem to break `examples/basic/pmux.py` when run with `-Werror`...

Patch:
diff --git a/amaranth/hdl/ast.py b/amaranth/hdl/ast.py
index 75ebbc6..47347ec 100644
--- a/amaranth/hdl/ast.py
+++ b/amaranth/hdl/ast.py
@@ -541,6 +541,8 @@ class Value(metaclass=ABCMeta):
                     continue
                 matches.append(self == pattern)
         if not matches:
+            warnings.warn("The value of Value.matches() with no patterns will change to Const(0) "
+                          "in Amaranth 0.5", SyntaxWarning, stacklevel=2)
             return Const(1)
         elif len(matches) == 1:
             return matches[0]
diff --git a/amaranth/hdl/dsl.py b/amaranth/hdl/dsl.py
index 467fd1e..92dd679 100644
--- a/amaranth/hdl/dsl.py
+++ b/amaranth/hdl/dsl.py
@@ -307,6 +307,9 @@ class Module(_ModuleBuilderRoot, Elaboratable):
         src_loc = tracer.get_src_loc(src_loc_at=1)
         switch_data = self._get_ctrl("Switch")
         new_patterns = ()
+        if not patterns:
+            warnings.warn("The behavior of Case() with no patterns will change to never-active "
+                          "in Amaranth 0.5; use Default() instead", SyntaxWarning, stacklevel=3)
         # This code should accept exactly the same patterns as `v.matches(...)`.
         for pattern in patterns:
             if isinstance(pattern, str) and any(bit not in "01- \t" for bit in pattern):
@@ -353,8 +356,22 @@ class Module(_ModuleBuilderRoot, Elaboratable):
             self._ctrl_context = "Switch"
             self._statements = _outer_case
 
+    @contextmanager
     def Default(self):
-        return self.Case()
+        self._check_context("Default", context="Switch")
+        src_loc = tracer.get_src_loc(src_loc_at=1)
+        switch_data = self._get_ctrl("Switch")
+        try:
+            _outer_case, self._statements = self._statements, []
+            self._ctrl_context = None
+            yield
+            self._flush_ctrl()
+            if () not in switch_data["cases"]:
+                switch_data["cases"][()] = self._statements
+                switch_data["case_src_locs"][()] = src_loc
+        finally:
+            self._ctrl_context = "Switch"
+            self._statements = _outer_case
 
     @contextmanager
     def FSM(self, reset=None, domain="sync", name="fsm"):


Test patch:
diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py
index 1e4d8a4..65e3ce1 100644
--- a/tests/test_hdl_ast.py
+++ b/tests/test_hdl_ast.py
@@ -682,7 +682,9 @@ class OperatorTestCase(FHDLTestCase):
 
     def test_matches(self):
         s = Signal(4)
-        self.assertRepr(s.matches(), "(const 1'd1)")
+        with self.assertWarnsRegex(SyntaxWarning,
+                r"^The value of Value.matches\(\) with no patterns will change to Const\(0\)"):
+            self.assertRepr(s.matches(), "(const 1'd1)")
         self.assertRepr(s.matches(1), """
         (== (sig s) (const 1'd1))
         """)
diff --git a/tests/test_hdl_dsl.py b/tests/test_hdl_dsl.py
index 9fc210f..44001d0 100644
--- a/tests/test_hdl_dsl.py
+++ b/tests/test_hdl_dsl.py
@@ -387,11 +387,13 @@ class DSLTestCase(FHDLTestCase):
 
     def test_Switch_default_Case(self):
         m = Module()
-        with m.Switch(self.w1):
-            with m.Case(3):
-                m.d.comb += self.c1.eq(1)
-            with m.Case():
-                m.d.comb += self.c2.eq(1)
+        with self.assertWarnsRegex(SyntaxWarning,
+                r"^The behavior of Case\(\) with no patterns will change to never-active"):
+            with m.Switch(self.w1):
+                with m.Case(3):
+                    m.d.comb += self.c1.eq(1)
+                with m.Case():
+                    m.d.comb += self.c2.eq(1)
         m._flush()
         self.assertRepr(m._statements, """
         (