Repository: Qiskit/qiskit

Issue:
dynamic circuit fail to transpile without a backend
### Environment

- **Qiskit version**: `1.1.0.dev0+afdd1b7`
- **Python version**: `3.11.7`
- **Operating system**: MacOS


### What is happening?

When transpiling a dynamic circuit without any backend, it fails with `The control-flow construct 'if_else' is not supported by the backend.`
This seems to be the backendless version of https://github.com/Qiskit/qiskit-ibm-runtime/issues/1253

### How can we reproduce the issue?

```python
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile

q = QuantumRegister(1)
c = ClassicalRegister(1)
circuit = QuantumCircuit(q, c)
circuit.h(q)
circuit.measure(q, c)
with circuit.if_test((c, 1)):
    circuit.x(q)

transpile(circuit)
```
```
TranspilerError: "The control-flow construct 'if_else' is not supported by the backend."
```

### What should happen?

transpilation should work with dynamic circuits, just like the non-dynamic circuits:
```python
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile

q = QuantumRegister(1)
c = ClassicalRegister(1)
circuit = QuantumCircuit(q, c)
circuit.h(q)
circuit.measure(q, c)

transpiled = transpile(circuit)
transpiled.draw('mpl')
```
<img width="245" src="https://github.com/Qiskit/qiskit/assets/766693/9f957908-d939-4743-a40a-fc2d04cd3ad7">


### Any suggestions?

_No response_


Discussion:

Resolving patch:
diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py
--- a/qiskit/transpiler/preset_passmanagers/common.py
+++ b/qiskit/transpiler/preset_passmanagers/common.py
@@ -86,9 +86,12 @@ class _InvalidControlFlowForBackend:
     def __init__(self, basis_gates=(), target=None):
         if target is not None:
             self.unsupported = [op for op in CONTROL_FLOW_OP_NAMES if op not in target]
-        else:
-            basis_gates = set(basis_gates) if basis_gates is not None else set()
+        elif basis_gates is not None:
+            basis_gates = set(basis_gates)
             self.unsupported = [op for op in CONTROL_FLOW_OP_NAMES if op not in basis_gates]
+        else:
+            # Pass manager without basis gates or target; assume everything's valid.
+            self.unsupported = []
 
     def message(self, property_set):
         """Create an error message for the given property set."""


Test patch:
diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py
--- a/test/python/compiler/test_transpiler.py
+++ b/test/python/compiler/test_transpiler.py
@@ -1645,6 +1645,27 @@ def test_target_ideal_gates(self, opt_level):
 
         self.assertEqual(Operator.from_circuit(result), Operator.from_circuit(qc))
 
+    @data(0, 1, 2, 3)
+    def test_transpile_control_flow_no_backend(self, opt_level):
+        """Test `transpile` with control flow and no specified hardware constraints."""
+        qc = QuantumCircuit(QuantumRegister(1, "q"), ClassicalRegister(1, "c"))
+        qc.h(0)
+        qc.measure(0, 0)
+        with qc.if_test((qc.clbits[0], False)):
+            qc.x(0)
+        with qc.while_loop((qc.clbits[0], True)):
+            qc.x(0)
+        with qc.for_loop(range(2)):
+            qc.x(0)
+        with qc.switch(qc.cregs[0]) as case:
+            with case(case.DEFAULT):
+                qc.x(0)
+        qc.measure(0, 0)
+
+        transpiled = transpile(qc, optimization_level=opt_level)
+        # There's nothing that can be optimized here.
+        self.assertEqual(qc, transpiled)
+
     @data(0, 1, 2, 3)
     def test_transpile_with_custom_control_flow_target(self, opt_level):
         """Test transpile() with a target and constrol flow ops."""