Repository: Qiskit/qiskit

Issue:
Composing circuit with a switch statement causes a bug in the QASM3 exporter
### Environment

- **Qiskit Terra version**: 0.25.0
- **Python version**: 3.9
- **Operating system**: OSX


### What is happening?

When composing a circuit with another circuit containing a switch statement and then exporting to OpenQASM 3. I receive an error 
```
KeyError: "'ClassicalRegister(3, 'random')' is not defined in the current context"
```

This may be related to #9746.

### How can we reproduce the issue?

Run
```python
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister

qr = QuantumRegister(3)
cr_random = ClassicalRegister(3, "random")
cr_result = ClassicalRegister(3, "result")
qc = QuantumCircuit(qr, cr_random, cr_result)

# build a random state
qc.h(qr)
# measure the state
qc.measure(qr, cr_random)

with qc.switch(cr_random) as case:
    with case(1):
        qc.x(qr[0])
    with case(2):
        qc.x(qr[1])
    with case(3):
        qc.x(qr[0])
        qc.x(qr[1])
    with case(4):
        qc.x(qr[2])
    with case(5):
        qc.x(qr[0])
        qc.x(qr[2])
    with case(6):
        qc.x(qr[1])
        qc.x(qr[2])
    with case(7):
        qc.x(qr)
    with case(case.DEFAULT):
        pass

qc.measure(qr, cr_result)

composed = QuantumCircuit(3, 6)
composed.compose(qc, inplace=True)

from qiskit import qasm3
print(qc_qasm3 := qasm3.dumps(composed, disable_constants=True, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1))
```

### What should happen?

QASM3 should be exported.

### Any suggestions?

_No response_


Discussion:
I have verified that using `append` also does not work and results in the same error.
I believe what needs to happen is to remap the `ClassicalRegister` in the `SwitchCaseOp` [target](https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/circuit/controlflow/switch_case.py#L67) analogously to the `condition` in the `IfElseOp` [here](https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/circuit/quantumcircuit.py#L981) 
> I believe what needs to happen is to remap the `ClassicalRegister` in the `SwitchCaseOp` [target](https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/circuit/controlflow/switch_case.py#L67) analogously to the `condition` in the `IfElseOp` [here](https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/circuit/quantumcircuit.py#L981)

this may be obvious, but I just realized that this only addresses the issue with `compose`. For `append` to work, a dual fix is also needed for the `DAGCircuit` in [substitute_node_with_dag ](https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/dagcircuit/dagcircuit.py#L1216)to map the target cbits when the operation has only a `target` instead of a `condition`. 
The surface-level problem in `compose` is not too difficult to fix, but that alone won't be enough in the current form of qasm3-runner to make things work.  There's further demons lurking in _nested_ control flow, I'm fairly sure, but that's a further issue.  For the time being in `append`, I think `circuit_to_instruction` will need to error out if there are ever _any_ classical registers that are used; there's no current way for this to properly make sense as we've got no concept of scoping a classical value.

If I were to patch `compose` to handle the simplest case, the code as given would then fail in the OQ3 exporter. It's not immediately clear to me how we could / if we should fix that entirely from the Terra side in a way that the QSS compiler can ingest. The initial creation of `composed` contains one `ClassicalRegister(6, name="c")`, so when we compose the circuit with two registers onto it, we're going to have overlap between `c` (in `composed`) and `cr_random` (in `qc`).  Qiskit will happily accept this (it's valid in our model), but I don't think qss-compiler supports the OQ3 aliasing semantics needed to represent it.

As a temporary work-around at least, the base of the composition (`composed`, in this case) could be created as
```python
from qiskit.circuit import QuantumCircuit, Qubit, Clbit

base = QuantumCircuit([Qubit() for _ in [None]*n_qubits], [Clbit() for _ in [None]*n_clbits])
```
(see #6496 for an issue that's proposing that these semantics should be the default for `QuantumCircuit(int, int)`, but that would be a breaking change in Terra so it can't happen overnight).  That _may_ be sufficient for your use cases, though it doesn't fully solve everything.

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -877,6 +877,9 @@ def compose(
                 lcr_1: 0 ═══════════                           lcr_1: 0 ═══════════════════════
 
         """
+        # pylint: disable=cyclic-import
+        from qiskit.circuit.controlflow.switch_case import SwitchCaseOp
+
         if inplace and front and self._control_flow_scopes:
             # If we're composing onto ourselves while in a stateful control-flow builder context,
             # there's no clear meaning to composition to the "front" of the circuit.
@@ -955,30 +958,42 @@ def compose(
                 )
             edge_map.update(zip(other.clbits, dest.cbit_argument_conversion(clbits)))
 
+        # Cache for `map_register_to_dest`.
+        _map_register_cache = {}
+
+        def map_register_to_dest(theirs):
+            """Map the target's registers to suitable equivalents in the destination, adding an
+            extra one if there's no exact match."""
+            if theirs.name in _map_register_cache:
+                return _map_register_cache[theirs.name]
+            mapped_bits = [edge_map[bit] for bit in theirs]
+            for ours in dest.cregs:
+                if mapped_bits == list(ours):
+                    mapped_theirs = ours
+                    break
+            else:
+                mapped_theirs = ClassicalRegister(bits=mapped_bits)
+                dest.add_register(mapped_theirs)
+            _map_register_cache[theirs.name] = mapped_theirs
+            return mapped_theirs
+
         mapped_instrs: list[CircuitInstruction] = []
-        condition_register_map = {}
         for instr in other.data:
             n_qargs: list[Qubit] = [edge_map[qarg] for qarg in instr.qubits]
             n_cargs: list[Clbit] = [edge_map[carg] for carg in instr.clbits]
             n_op = instr.operation.copy()
 
-            # Map their registers over to ours, adding an extra one if there's no exact match.
             if getattr(n_op, "condition", None) is not None:
                 target, value = n_op.condition
                 if isinstance(target, Clbit):
                     n_op.condition = (edge_map[target], value)
                 else:
-                    if target.name not in condition_register_map:
-                        mapped_bits = [edge_map[bit] for bit in target]
-                        for our_creg in dest.cregs:
-                            if mapped_bits == list(our_creg):
-                                new_target = our_creg
-                                break
-                        else:
-                            new_target = ClassicalRegister(bits=[edge_map[bit] for bit in target])
-                            dest.add_register(new_target)
-                        condition_register_map[target.name] = new_target
-                    n_op.condition = (condition_register_map[target.name], value)
+                    n_op.condition = (map_register_to_dest(target), value)
+            elif isinstance(n_op, SwitchCaseOp):
+                if isinstance(n_op.target, Clbit):
+                    n_op.target = edge_map[n_op.target]
+                else:
+                    n_op.target = map_register_to_dest(n_op.target)
 
             mapped_instrs.append(CircuitInstruction(n_op, n_qargs, n_cargs))
 


Test patch:
diff --git a/test/python/circuit/test_compose.py b/test/python/circuit/test_compose.py
--- a/test/python/circuit/test_compose.py
+++ b/test/python/circuit/test_compose.py
@@ -29,6 +29,8 @@
     Parameter,
     Gate,
     Instruction,
+    CASE_DEFAULT,
+    SwitchCaseOp,
 )
 from qiskit.circuit.library import HGate, RZGate, CXGate, CCXGate, TwoLocal
 from qiskit.test import QiskitTestCase
@@ -477,6 +479,45 @@ def test_compose_conditional_no_match(self):
         self.assertEqual(z.condition[1], 1)
         self.assertIs(x.condition[0][0], test.clbits[1])
 
+    def test_compose_switch_match(self):
+        """Test that composition containing a `switch` with a register that matches proceeds
+        correctly."""
+        case_0 = QuantumCircuit(1, 2)
+        case_0.x(0)
+        case_1 = QuantumCircuit(1, 2)
+        case_1.z(0)
+        case_default = QuantumCircuit(1, 2)
+        cr = ClassicalRegister(2, "target")
+        right = QuantumCircuit(QuantumRegister(1), cr)
+        right.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [0], [0, 1])
+
+        test = QuantumCircuit(QuantumRegister(3), cr, ClassicalRegister(2)).compose(
+            right, [1], [0, 1]
+        )
+
+        expected = test.copy_empty_like()
+        expected.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [1], [0, 1])
+        self.assertEqual(test, expected)
+
+    def test_compose_switch_no_match(self):
+        """Test that composition containing a `switch` with a register that matches proceeds
+        correctly."""
+        case_0 = QuantumCircuit(1, 2)
+        case_0.x(0)
+        case_1 = QuantumCircuit(1, 2)
+        case_1.z(0)
+        case_default = QuantumCircuit(1, 2)
+        cr = ClassicalRegister(2, "target")
+        right = QuantumCircuit(QuantumRegister(1), cr)
+        right.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [0], [0, 1])
+        test = QuantumCircuit(3, 3).compose(right, [1], [0, 1])
+
+        self.assertEqual(len(test.data), 1)
+        self.assertIsInstance(test.data[0].operation, SwitchCaseOp)
+        target = test.data[0].operation.target
+        self.assertIn(target, test.cregs)
+        self.assertEqual(list(target), test.clbits[0:2])
+
     def test_compose_gate(self):
         """Composing with a gate.