Repository: amaranth-lang/amaranth (python)

Issue:
Zero-length submodule name breaks things
The following design:

```
from amaranth import *
from amaranth.back import verilog

class Bar(Elaboratable):
    def elaborate(self, platform):
        m = Module()
        m.d.sync += Signal().eq(1)
        return m

class Foo(Elaboratable):
    def elaborate(self, platform):
        m = Module()
        m.submodules[""] = Bar()
        return m

print(verilog.convert(Foo(), ports=[]))
```

gives the following exception:
```
Traceback (most recent call last):
  File "~/test.py", line 16, in <module>
    print(verilog.convert(Foo(), ports=[]))
  File "~/venv/lib/python3.10/site-packages/amaranth/back/verilog.py", line 61, in convert
    verilog_text, name_map = convert_fragment(fragment, ports, name, emit_src=emit_src, strip_internal_attrs=strip_internal_attrs, **kwargs)
  File "~/venv/lib/python3.10/site-packages/amaranth/back/verilog.py", line 39, in convert_fragment
    rtlil_text, name_map = rtlil.convert_fragment(*args, **kwargs)
  File "~/venv/lib/python3.10/site-packages/amaranth/back/rtlil.py", line 1029, in convert_fragment
    empty_checker=empty_checker).emit()
  File "~/venv/lib/python3.10/site-packages/amaranth/back/rtlil.py", line 308, in emit
    self.emit_submodules()
  File "~/venv/lib/python3.10/site-packages/amaranth/back/rtlil.py", line 516, in emit_submodules
    self.builder.cell(f"\\{dotted_name}", submodule.name[-1], ports={
  File "~/venv/lib/python3.10/site-packages/amaranth/back/rtlil.py", line 166, in cell
    name = self._make_name(name, local=False)
  File "~/venv/lib/python3.10/site-packages/amaranth/back/rtlil.py", line 67, in _make_name
    elif not local and name[0] not in "\\$":
IndexError: string index out of range
```

I suggest that `m.submodules[""] = ...` have the same behavior as `m.submodules += ...`. While it would be silly to directly write the former, it might arise from programmatic name generation, and should either work properly or immediately throw an error.

Pull request description:
Forbid empty string as submodule name
This is semantically ambiguous and breaks the RTLIL emitter.

Fixes #1209.

Patch:
diff --git a/amaranth/hdl/_dsl.py b/amaranth/hdl/_dsl.py
index a73f20f..c66df2e 100644
--- a/amaranth/hdl/_dsl.py
+++ b/amaranth/hdl/_dsl.py
@@ -654,6 +654,8 @@ class Module(_ModuleBuilderRoot, Elaboratable):
         if name == None:
             self._anon_submodules.append((submodule, src_loc))
         else:
+            if name == "":
+                raise NameError("Submodule name must not be empty")
             if name in self._named_submodules:
                 raise NameError(f"Submodule named '{name}' already exists")
             self._named_submodules[name] = (submodule, src_loc)


Test patch:
diff --git a/tests/test_hdl_dsl.py b/tests/test_hdl_dsl.py
index a255ff6..ecbed9d 100644
--- a/tests/test_hdl_dsl.py
+++ b/tests/test_hdl_dsl.py
@@ -887,6 +887,12 @@ class DSLTestCase(FHDLTestCase):
         with self.assertRaisesRegex(NameError, r"^Submodule named 'foo' already exists$"):
             m1.submodules.foo = m2
 
+    def test_submodule_named_empty(self):
+        m1 = Module()
+        m2 = Module()
+        with self.assertRaisesRegex(NameError, r"^Submodule name must not be empty$"):
+            m1.submodules[""] = m2
+
     def test_submodule_get(self):
         m1 = Module()
         m2 = Module()