Repository: Qiskit/qiskit (python)

Issue:
Default values for `approximation_degree` differ in `transpile` and `generate_preset_pass_manager`
### Environment

- **Qiskit version**: 0.45
- **Python version**: 
- **Operating system**: 


### What is happening?

The `transpile` function has a default value of `approximation_degree=1.0` while the `generate_preset_pass_manager` function has `approximation_degree=None`. This can lead to drastically different default behavior when transpiling with these two functions on certain backends which was found by some recent researchers using the pass manager based code.

### How can we reproduce the issue?

To quote the example I was given:
> we had circuits with ~700 gates when  using v1 backends and/or transpile, but this dropped down to ~200 gates when using the preset pm.
on top of this, the approximation is also dependent on the actual 2Q unitary.

### What should happen?

The default values should be consistent between `transpile` and the preset pass manager.

### Any suggestions?

I believe the intended behavior is what is done in `transpile` (no approximation), so the preset pass manager should be updated to have a default kwarg value of `approximation_degree=1.0` to match

Pull request:
Fix `approximation_degree` default in `generate_preset_pass_maanger`
### Summary

This brings it inline with `transpile`, as was done in gh-8595.

<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->

### Details and comments

I'm proposing this as a candidate for 1.0 despite missing the deadline (needs another maintainer's sign-off) because this is really surprising behaviour, and we decided way back in #8595 that the default of "approximate away literally all of my Trotterisation" was a sufficiently poor default we ought to make the change as a sort-of bugfix.  It's even more surprising for `generate_preset_pass_manager` to do this if `transpile` isn't.

Fix #11693.

Patch:
diff --git a/qiskit/__init__.py b/qiskit/__init__.py
index 4109b14c0..72adbc105 100644
--- a/qiskit/__init__.py
+++ b/qiskit/__init__.py
@@ -14,9 +14,36 @@
 
 """Main Qiskit public functionality."""
 
+import importlib.metadata
+import importlib.util
+import os
 import sys
 import warnings
 
+try:
+    importlib.metadata.version("qiskit-terra")
+except importlib.metadata.PackageNotFoundError:
+    # All good!
+    pass
+else:
+    # 'qiskit.tools' is present in all 0.x series of Qiskit and not in Qiskit 1.0+.  If a dev has an
+    # editable install and switches from 0.x branches to 1.0+ branches, they might have an empty
+    # `qiskit/tools` folder in their path, which will appear as a "namespace package" with no valid
+    # location.  We catch that case as "not actually having Qiskit 0.x" as a convenience to devs.
+    _has_tools = getattr(importlib.util.find_spec("qiskit.tools"), "has_location", False)
+    _suppress_error = os.environ.get("QISKIT_SUPPRESS_1_0_IMPORT_ERROR", False) == "1"
+    if not _suppress_error and _has_tools:
+        raise ImportError(
+            "Qiskit is installed in an invalid environment that has both Qiskit >=1.0"
+            " and an earlier version."
+            " You should create a new virtual environment, and ensure that you do not mix"
+            " dependencies between Qiskit <1.0 and >=1.0."
+            " Any packages that depend on 'qiskit-terra' are not compatible with Qiskit 1.0 and"
+            " will need to be updated."
+            " Qiskit unfortunately cannot enforce this requirement during environment resolution."
+            " See https://qisk.it/packaging-1-0 for more detail."
+        )
+
 import qiskit._accelerate
 
 
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index dd211f84a..1f82cdba5 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -505,9 +505,11 @@ class QuantumCircuit:
         #   copy.deepcopy(memo).
         cls = self.__class__
         result = cls.__new__(cls)
-        for k in self.__dict__.keys() - {"_data"}:
+        for k in self.__dict__.keys() - {"_data", "_builder_api"}:
             setattr(result, k, copy.deepcopy(self.__dict__[k], memo))
 
+        result._builder_api = _OuterCircuitScopeInterface(result)
+
         # Avoids pulling self._data into a Python list
         # like we would when pickling.
         result._data = self._data.copy()
diff --git a/qiskit/qasm3/ast.py b/qiskit/qasm3/ast.py
index e81fcb1d9..9b8271ce7 100644
--- a/qiskit/qasm3/ast.py
+++ b/qiskit/qasm3/ast.py
@@ -596,11 +596,31 @@ class DefaultCase(Expression):
     """An object representing the `default` special label in switch statements."""
 
 
-class SwitchStatement(Statement):
-    """AST node for the proposed 'switch-case' extension to OpenQASM 3."""
+class SwitchStatementPreview(Statement):
+    """AST node for the proposed 'switch-case' extension to OpenQASM 3, before the syntax was
+    stabilized.  This corresponds to the :attr:`.ExperimentalFeatures.SWITCH_CASE_V1` logic.
+
+    The stabilized form of the syntax instead uses :class:`.SwitchStatement`."""
 
     def __init__(
         self, target: Expression, cases: Iterable[Tuple[Iterable[Expression], ProgramBlock]]
     ):
         self.target = target
         self.cases = [(tuple(values), case) for values, case in cases]
+
+
+class SwitchStatement(Statement):
+    """AST node for the stable 'switch' statement of OpenQASM 3.
+
+    The only real difference from an AST form is that the default is required to be separate; it
+    cannot be joined with other cases (even though that's meaningless, the V1 syntax permitted it)."""
+
+    def __init__(
+        self,
+        target: Expression,
+        cases: Iterable[Tuple[Iterable[Expression], ProgramBlock]],
+        default: Optional[ProgramBlock] = None,
+    ):
+        self.target = target
+        self.cases = [(tuple(values), case) for values, case in cases]
+        self.default = default
diff --git a/qiskit/qasm3/experimental.py b/qiskit/qasm3/experimental.py
index f52bca12c..8e925acb5 100644
--- a/qiskit/qasm3/experimental.py
+++ b/qiskit/qasm3/experimental.py
@@ -27,4 +27,44 @@ class ExperimentalFeatures(enum.Flag):
     SWITCH_CASE_V1 = enum.auto()
     """Support exporting switch-case statements as proposed by
     https://github.com/openqasm/openqasm/pull/463 at `commit bfa787aa3078
-    <https://github.com/openqasm/openqasm/pull/463/commits/bfa787aa3078>`__."""
+    <https://github.com/openqasm/openqasm/pull/463/commits/bfa787aa3078>`__.
+
+    These have the output format:
+
+    .. code-block::
+
+        switch (i) {
+            case 0:
+            case 1:
+                x $0;
+            break;
+
+            case 2: {
+                z $0;
+            }
+            break;
+
+            default: {
+                cx $0, $1;
+            }
+            break;
+        }
+
+    This differs from the syntax of the ``switch`` statement as stabilized.  If this flag is not
+    passed, then the parser will instead output using the stabilized syntax, which would render the
+    same example above as:
+
+    .. code-block::
+
+        switch (i) {
+            case 0, 1 {
+                x $0;
+            }
+            case 2 {
+                z $0;
+            }
+            default {
+                cx $0, $1;
+            }
+        }
+    """
diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py
index c1398aff7..d8d3a4208 100644
--- a/qiskit/qasm3/exporter.py
+++ b/qiskit/qasm3/exporter.py
@@ -833,14 +833,6 @@ class QASM3Builder:
 
     def build_switch_statement(self, instruction: CircuitInstruction) -> Iterable[ast.Statement]:
         """Build a :obj:`.SwitchCaseOp` into a :class:`.ast.SwitchStatement`."""
-        if ExperimentalFeatures.SWITCH_CASE_V1 not in self.experimental:
-            raise QASM3ExporterError(
-                "'switch' statements are not stabilized in OpenQASM 3 yet."
-                " To enable experimental support, set the flag"
-                " 'ExperimentalFeatures.SWITCH_CASE_V1' in the 'experimental' keyword"
-                " argument of the exporter."
-            )
-
         real_target = self.build_expression(expr.lift(instruction.operation.target))
         global_scope = self.global_scope()
         target = self._reserve_variable_name(
@@ -850,21 +842,48 @@ class QASM3Builder:
             ast.ClassicalDeclaration(ast.IntType(), target, None)
         )
 
-        def case(values, case_block):
-            values = [
-                ast.DefaultCase() if v is CASE_DEFAULT else self.build_integer(v) for v in values
+        if ExperimentalFeatures.SWITCH_CASE_V1 in self.experimental:
+            # In this case, defaults can be folded in with other cases (useless as that is).
+
+            def case(values, case_block):
+                values = [
+                    ast.DefaultCase() if v is CASE_DEFAULT else self.build_integer(v)
+                    for v in values
+                ]
+                self.push_scope(case_block, instruction.qubits, instruction.clbits)
+                case_body = self.build_program_block(case_block.data)
+                self.pop_scope()
+                return values, case_body
+
+            return [
+                ast.AssignmentStatement(target, real_target),
+                ast.SwitchStatementPreview(
+                    target,
+                    (
+                        case(values, block)
+                        for values, block in instruction.operation.cases_specifier()
+                    ),
+                ),
             ]
-            self.push_scope(case_block, instruction.qubits, instruction.clbits)
-            case_body = self.build_program_block(case_block.data)
+
+        # Handle the stabilised syntax.
+        cases = []
+        default = None
+        for values, block in instruction.operation.cases_specifier():
+            self.push_scope(block, instruction.qubits, instruction.clbits)
+            case_body = self.build_program_block(block.data)
             self.pop_scope()
-            return values, case_body
+            if CASE_DEFAULT in values:
+                # Even if it's mixed in with other cases, we can skip them and only output the
+                # `default` since that's valid and execution will be the same; the evaluation of
+                # case labels can't have side effects.
+                default = case_body
+                continue
+            cases.append(([self.build_integer(value) for value in values], case_body))
 
         return [
             ast.AssignmentStatement(target, real_target),
-            ast.SwitchStatement(
-                target,
-                (case(values, block) for values, block in instruction.operation.cases_specifier()),
-            ),
+            ast.SwitchStatement(target, cases, default=default),
         ]
 
     def build_while_loop(self, instruction: CircuitInstruction) -> ast.WhileLoopStatement:
diff --git a/qiskit/qasm3/printer.py b/qiskit/qasm3/printer.py
index fee68824f..94d12a7ec 100644
--- a/qiskit/qasm3/printer.py
+++ b/qiskit/qasm3/printer.py
@@ -18,7 +18,6 @@ from typing import Sequence
 
 from . import ast
 from .experimental import ExperimentalFeatures
-from .exceptions import QASM3ExporterError
 
 # Precedence and associativity table for prefix, postfix and infix operators.  The rules are a
 # lookup table of two-tuples; the "binding power" of the operator to the left and to the right.
@@ -504,13 +503,33 @@ class BasicPrinter:
         self._end_line()
 
     def _visit_SwitchStatement(self, node: ast.SwitchStatement) -> None:
-        if ExperimentalFeatures.SWITCH_CASE_V1 not in self._experimental:
-            raise QASM3ExporterError(
-                "'switch' statements are not stabilised in OpenQASM 3 yet."
-                " To enable experimental support, set the flag"
-                " 'ExperimentalFeatures.SWITCH_CASE_V1' in the 'experimental' keyword"
-                " argument of the printer."
-            )
+        self._start_line()
+        self.stream.write("switch (")
+        self.visit(node.target)
+        self.stream.write(") {")
+        self._end_line()
+        self._current_indent += 1
+        for labels, case in node.cases:
+            if not labels:
+                continue
+            self._start_line()
+            self.stream.write("case ")
+            self._visit_sequence(labels, separator=", ")
+            self.stream.write(" ")
+            self.visit(case)
+            self._end_line()
+        if node.default is not None:
+            self._start_line()
+            self.stream.write("default ")
+            self.visit(node.default)
+            self._end_line()
+        self._current_indent -= 1
+        self._start_line()
+        self.stream.write("}")
+        self._end_line()
+
+    def _visit_SwitchStatementPreview(self, node: ast.SwitchStatementPreview) -> None:
+        # This is the pre-release syntax, which had lots of extra `break` statements in it.
         self._start_line()
         self.stream.write("switch (")
         self.visit(node.target)
diff --git a/qiskit/transpiler/preset_passmanagers/__init__.py b/qiskit/transpiler/preset_passmanagers/__init__.py
index 9b6efe661..6a8dab2ba 100644
--- a/qiskit/transpiler/preset_passmanagers/__init__.py
+++ b/qiskit/transpiler/preset_passmanagers/__init__.py
@@ -84,7 +84,7 @@ def generate_preset_pass_manager(
     routing_method=None,
     translation_method=None,
     scheduling_method=None,
-    approximation_degree=None,
+    approximation_degree=1.0,
     seed_transpiler=None,
     unitary_synthesis_method="default",
     unitary_synthesis_plugin_config=None,
diff --git a/releasenotes/notes/preset-pass-manager-approximation-degree-503198b1974adf79.yaml b/releasenotes/notes/preset-pass-manager-approximation-degree-503198b1974adf79.yaml
new file mode 100644
index 000000000..01cf21e33
--- /dev/null
+++ b/releasenotes/notes/preset-pass-manager-approximation-degree-503198b1974adf79.yaml
@@ -0,0 +1,12 @@
+---
+upgrade:
+  - |
+    The implicit use of ``approximation_degree!=1.0`` by default in the
+    :func:`~.generate_preset_pass_manager` function has been
+    disabled.  The previous default could cause undue and unexpected approximations, especially in
+    workloads involving Trotterization or similar runs of operations that are close, but decidedly
+    not equal, to the identity.
+
+    This change brings the inner pass-manager generation defaults in line with :func:`.transpile`,
+    which was always the intention.  See `#8595 <https://github.com/Qiskit/qiskit/pull/8595>`__ for
+    more detail.
diff --git a/releasenotes/notes/qasm3-stable-switch-61a10036d1587778.yaml b/releasenotes/notes/qasm3-stable-switch-61a10036d1587778.yaml
new file mode 100644
index 000000000..9cf905563
--- /dev/null
+++ b/releasenotes/notes/qasm3-stable-switch-61a10036d1587778.yaml
@@ -0,0 +1,16 @@
+---
+features:
+  - |
+    The OpenQASM 3 exporter (see :func:`~.qasm3.dump` and :func:`~.qasm3.dumps` functions in
+    :mod:`qiskit.qasm3`) now supports the stabilised syntax of the ``switch`` statement in OpenQASM 3
+    by default.  The pre-certification syntax of the ``switch`` statement is still available by
+    using the :attr:`.ExperimentalFeatures.SWITCH_CASE_V1` flag in the ``experimental`` argument of
+    the exporter.  There is no feature flag required for the stabilised syntax, but if you are
+    interfacing with other tooling that is not yet updated, you may need to pass the experimental
+    flag.
+
+    The syntax of the stabilised form is slightly different with regards to terminating ``break``
+    statements (no longer required nor permitted), and multiple cases are now combined into a single
+    ``case`` line, rather than using C-style fall-through.  For more detail, see `the OpenQASM 3
+    documentation on the switch-case construct
+    <https://openqasm.com/language/classical.html#the-switch-statement>`__.


Test patch:
diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py
index f17a61d2e..d94c935af 100644
--- a/test/python/circuit/test_circuit_operations.py
+++ b/test/python/circuit/test_circuit_operations.py
@@ -12,6 +12,8 @@
 
 
 """Test Qiskit's QuantumCircuit class."""
+import copy
+import pickle
 
 import numpy as np
 from ddt import data, ddt
@@ -567,6 +569,57 @@ class TestCircuitOperations(QiskitTestCase):
 
         self.assertEqual(expected, circuit)
 
+    def test_measure_all_after_copy(self):
+        """
+        Test measure_all on a circuit that has been copied.
+        """
+        qc = QuantumCircuit(2)
+        qc.h(1)
+
+        qc2 = qc.copy()
+
+        qc.measure_all()
+        qc2.measure_all()
+
+        expected_cregs = [ClassicalRegister(2, "meas")]
+        self.assertEqual(qc.cregs, expected_cregs)
+        self.assertEqual(qc2.cregs, expected_cregs)
+        self.assertEqual(qc, qc2)
+
+    def test_measure_all_after_deepcopy(self):
+        """
+        Test measure_all on a circuit that has been deep-copied.
+        """
+        qc = QuantumCircuit(2)
+        qc.h(1)
+
+        qc2 = copy.deepcopy(qc)
+
+        qc.measure_all()
+        qc2.measure_all()
+
+        expected_cregs = [ClassicalRegister(2, "meas")]
+        self.assertEqual(qc.cregs, expected_cregs)
+        self.assertEqual(qc2.cregs, expected_cregs)
+        self.assertEqual(qc, qc2)
+
+    def test_measure_all_after_pickle(self):
+        """
+        Test measure_all on a circuit that has been pickled.
+        """
+        qc = QuantumCircuit(2)
+        qc.h(1)
+
+        qc2 = pickle.loads(pickle.dumps(qc))
+
+        qc.measure_all()
+        qc2.measure_all()
+
+        expected_cregs = [ClassicalRegister(2, "meas")]
+        self.assertEqual(qc.cregs, expected_cregs)
+        self.assertEqual(qc2.cregs, expected_cregs)
+        self.assertEqual(qc, qc2)
+
     def test_measure_all_not_add_bits_equal(self):
         """Test measure_all applies measurements to all qubits.
         Does not create a new ClassicalRegister if the existing one is big enough.
@@ -684,9 +737,9 @@ class TestCircuitOperations(QiskitTestCase):
         circuit.rz(theta, qr)
         circuit.measure(qr, cr)
         circuit.remove_final_measurements()
-        copy = circuit.copy()
+        copied = circuit.copy()
 
-        self.assertEqual(copy, circuit)
+        self.assertEqual(copied, circuit)
 
     def test_remove_final_measurements_multiple_measures(self):
         """Test remove_final_measurements only removes measurements at the end of the circuit
diff --git a/test/python/qasm3/test_export.py b/test/python/qasm3/test_export.py
index cece6e20e..3bb166799 100644
--- a/test/python/qasm3/test_export.py
+++ b/test/python/qasm3/test_export.py
@@ -2037,22 +2037,279 @@ if (c[0]) {
 }"""
         self.assertEqual(dumps(qc).strip(), expected.strip())
 
+    def test_switch_clbit(self):
+        """Test that a switch statement can be constructed with a bit as a condition."""
+        qubit = Qubit()
+        clbit = Clbit()
+        case1 = QuantumCircuit([qubit, clbit])
+        case1.x(0)
+        case2 = QuantumCircuit([qubit, clbit])
+        case2.z(0)
+        circuit = QuantumCircuit([qubit, clbit])
+        circuit.switch(clbit, [(True, case1), (False, case2)], [0], [0])
+
+        test = dumps(circuit)
+        expected = """\
+OPENQASM 3.0;
+include "stdgates.inc";
+bit _bit0;
+int switch_dummy;
+qubit _qubit0;
+switch_dummy = _bit0;
+switch (switch_dummy) {
+  case 1 {
+    x _qubit0;
+  }
+  case 0 {
+    z _qubit0;
+  }
+}
+"""
+        self.assertEqual(test, expected)
+
+    def test_switch_register(self):
+        """Test that a switch statement can be constructed with a register as a
+        condition."""
+        qubit = Qubit()
+        creg = ClassicalRegister(2, "c")
+        case1 = QuantumCircuit([qubit], creg)
+        case1.x(0)
+        case2 = QuantumCircuit([qubit], creg)
+        case2.y(0)
+        case3 = QuantumCircuit([qubit], creg)
+        case3.z(0)
+
+        circuit = QuantumCircuit([qubit], creg)
+        circuit.switch(creg, [(0, case1), (1, case2), (2, case3)], [0], circuit.clbits)
+
+        test = dumps(circuit)
+        expected = """\
+OPENQASM 3.0;
+include "stdgates.inc";
+bit[2] c;
+int switch_dummy;
+qubit _qubit0;
+switch_dummy = c;
+switch (switch_dummy) {
+  case 0 {
+    x _qubit0;
+  }
+  case 1 {
+    y _qubit0;
+  }
+  case 2 {
+    z _qubit0;
+  }
+}
+"""
+        self.assertEqual(test, expected)
+
+    def test_switch_with_default(self):
+        """Test that a switch statement can be constructed with a default case at the
+        end."""
+        qubit = Qubit()
+        creg = ClassicalRegister(2, "c")
+        case1 = QuantumCircuit([qubit], creg)
+        case1.x(0)
+        case2 = QuantumCircuit([qubit], creg)
+        case2.y(0)
+        case3 = QuantumCircuit([qubit], creg)
+        case3.z(0)
+
+        circuit = QuantumCircuit([qubit], creg)
+        circuit.switch(creg, [(0, case1), (1, case2), (CASE_DEFAULT, case3)], [0], circuit.clbits)
+
+        test = dumps(circuit)
+        expected = """\
+OPENQASM 3.0;
+include "stdgates.inc";
+bit[2] c;
+int switch_dummy;
+qubit _qubit0;
+switch_dummy = c;
+switch (switch_dummy) {
+  case 0 {
+    x _qubit0;
+  }
+  case 1 {
+    y _qubit0;
+  }
+  default {
+    z _qubit0;
+  }
+}
+"""
+        self.assertEqual(test, expected)
+
+    def test_switch_multiple_cases_to_same_block(self):
+        """Test that it is possible to add multiple cases that apply to the same block, if they are
+        given as a compound value.  This is an allowed special case of block fall-through."""
+        qubit = Qubit()
+        creg = ClassicalRegister(2, "c")
+        case1 = QuantumCircuit([qubit], creg)
+        case1.x(0)
+        case2 = QuantumCircuit([qubit], creg)
+        case2.y(0)
+
+        circuit = QuantumCircuit([qubit], creg)
+        circuit.switch(creg, [(0, case1), ((1, 2), case2)], [0], circuit.clbits)
+
+        test = dumps(circuit)
+        expected = """\
+OPENQASM 3.0;
+include "stdgates.inc";
+bit[2] c;
+int switch_dummy;
+qubit _qubit0;
+switch_dummy = c;
+switch (switch_dummy) {
+  case 0 {
+    x _qubit0;
+  }
+  case 1, 2 {
+    y _qubit0;
+  }
+}
+"""
+        self.assertEqual(test, expected)
+
+    def test_multiple_switches_dont_clash_on_dummy(self):
+        """Test that having more than one switch statement in the circuit doesn't cause naming
+        clashes in the dummy integer value used."""
+        qubit = Qubit()
+        creg = ClassicalRegister(2, "switch_dummy")
+        case1 = QuantumCircuit([qubit], creg)
+        case1.x(0)
+        case2 = QuantumCircuit([qubit], creg)
+        case2.y(0)
+
+        circuit = QuantumCircuit([qubit], creg)
+        circuit.switch(creg, [(0, case1), ((1, 2), case2)], [0], circuit.clbits)
+        circuit.switch(creg, [(0, case1), ((1, 2), case2)], [0], circuit.clbits)
+
+        test = dumps(circuit)
+        expected = """\
+OPENQASM 3.0;
+include "stdgates.inc";
+bit[2] switch_dummy;
+int switch_dummy__generated0;
+int switch_dummy__generated1;
+qubit _qubit0;
+switch_dummy__generated0 = switch_dummy;
+switch (switch_dummy__generated0) {
+  case 0 {
+    x _qubit0;
+  }
+  case 1, 2 {
+    y _qubit0;
+  }
+}
+switch_dummy__generated1 = switch_dummy;
+switch (switch_dummy__generated1) {
+  case 0 {
+    x _qubit0;
+  }
+  case 1, 2 {
+    y _qubit0;
+  }
+}
+"""
+        self.assertEqual(test, expected)
+
+    def test_switch_nested_in_if(self):
+        """Test that the switch statement works when in a nested scope, including the dummy
+        classical variable being declared globally.  This isn't necessary in the OQ3 language, but
+        it is universally valid and the IBM QSS stack prefers that.  They're our primary consumers
+        of OQ3 strings, so it's best to play nicely with them."""
+        qubit = Qubit()
+        creg = ClassicalRegister(2, "c")
+        case1 = QuantumCircuit([qubit], creg)
+        case1.x(0)
+        case2 = QuantumCircuit([qubit], creg)
+        case2.y(0)
+
+        body = QuantumCircuit([qubit], creg)
+        body.switch(creg, [(0, case1), ((1, 2), case2)], [0], body.clbits)
+
+        circuit = QuantumCircuit([qubit], creg)
+        circuit.if_else((creg, 1), body.copy(), body, [0], body.clbits)
+
+        test = dumps(circuit)
+        expected = """\
+OPENQASM 3.0;
+include "stdgates.inc";
+bit[2] c;
+int switch_dummy;
+int switch_dummy__generated0;
+qubit _qubit0;
+if (c == 1) {
+  switch_dummy = c;
+  switch (switch_dummy) {
+    case 0 {
+      x _qubit0;
+    }
+    case 1, 2 {
+      y _qubit0;
+    }
+  }
+} else {
+  switch_dummy__generated0 = c;
+  switch (switch_dummy__generated0) {
+    case 0 {
+      x _qubit0;
+    }
+    case 1, 2 {
+      y _qubit0;
+    }
+  }
+}
+"""
+        self.assertEqual(test, expected)
+
+    def test_switch_expr_target(self):
+        """Simple test that the target of `switch` can be `Expr` nodes."""
+        bits = [Qubit(), Clbit()]
+        cr = ClassicalRegister(2, "cr")
+        case0 = QuantumCircuit(1)
+        case0.x(0)
+        case1 = QuantumCircuit(1)
+        case1.x(0)
+        qc = QuantumCircuit(bits, cr)
+        qc.switch(expr.logic_not(bits[1]), [(False, case0)], [0], [])
+        qc.switch(expr.bit_and(cr, 3), [(3, case1)], [0], [])
+
+        expected = """\
+OPENQASM 3.0;
+include "stdgates.inc";
+bit _bit0;
+bit[2] cr;
+int switch_dummy;
+int switch_dummy__generated0;
+qubit _qubit0;
+switch_dummy = !_bit0;
+switch (switch_dummy) {
+  case 0 {
+    x _qubit0;
+  }
+}
+switch_dummy__generated0 = cr & 3;
+switch (switch_dummy__generated0) {
+  case 3 {
+    x _qubit0;
+  }
+}
+"""
+        test = dumps(qc)
+        self.assertEqual(test, expected)
+
 
 class TestExperimentalFeatures(QiskitTestCase):
     """Tests of features that are hidden behind experimental flags."""
 
     maxDiff = None
 
-    def test_switch_forbidden_without_flag(self):
-        """Omitting the feature flag should raise an error."""
-        case = QuantumCircuit(1)
-        circuit = QuantumCircuit(1, 1)
-        circuit.switch(circuit.clbits[0], [((True, False), case)], [0], [])
-        with self.assertRaisesRegex(QASM3ExporterError, "'switch' statements are not stabilized"):
-            dumps(circuit)
-
-    def test_switch_clbit(self):
-        """Test that a switch statement can be constructed with a bit as a condition."""
+    def test_switch_v1_clbit(self):
+        """Test that a prerelease switch statement can be constructed with a bit as a condition."""
         qubit = Qubit()
         clbit = Clbit()
         case1 = QuantumCircuit([qubit, clbit])
@@ -2083,8 +2340,9 @@ switch (switch_dummy) {
 """
         self.assertEqual(test, expected)
 
-    def test_switch_register(self):
-        """Test that a switch statement can be constructed with a register as a condition."""
+    def test_switch_v1_register(self):
+        """Test that a prerelease switch statement can be constructed with a register as a
+        condition."""
         qubit = Qubit()
         creg = ClassicalRegister(2, "c")
         case1 = QuantumCircuit([qubit], creg)
@@ -2122,8 +2380,9 @@ switch (switch_dummy) {
 """
         self.assertEqual(test, expected)
 
-    def test_switch_with_default(self):
-        """Test that a switch statement can be constructed with a default case at the end."""
+    def test_switch_v1_with_default(self):
+        """Test that a prerelease switch statement can be constructed with a default case at the
+        end."""
         qubit = Qubit()
         creg = ClassicalRegister(2, "c")
         case1 = QuantumCircuit([qubit], creg)
@@ -2161,7 +2420,7 @@ switch (switch_dummy) {
 """
         self.assertEqual(test, expected)
 
-    def test_switch_multiple_cases_to_same_block(self):
+    def test_switch_v1_multiple_cases_to_same_block(self):
         """Test that it is possible to add multiple cases that apply to the same block, if they are
         given as a compound value.  This is an allowed special case of block fall-through."""
         qubit = Qubit()
@@ -2197,8 +2456,8 @@ switch (switch_dummy) {
         self.assertEqual(test, expected)
 
     def test_multiple_switches_dont_clash_on_dummy(self):
-        """Test that having more than one switch statement in the circuit doesn't cause naming
-        clashes in the dummy integer value used."""
+        """Test that having more than one prerelease switch statement in the circuit doesn't cause
+        naming clashes in the dummy integer value used."""
         qubit = Qubit()
         creg = ClassicalRegister(2, "switch_dummy")
         case1 = QuantumCircuit([qubit], creg)
@@ -2245,7 +2504,7 @@ switch (switch_dummy__generated1) {
 """
         self.assertEqual(test, expected)
 
-    def test_switch_nested_in_if(self):
+    def test_switch_v1_nested_in_if(self):
         """Test that the switch statement works when in a nested scope, including the dummy
         classical variable being declared globally.  This isn't necessary in the OQ3 language, but
         it is universally valid and the IBM QSS stack prefers that.  They're our primary consumers
@@ -2301,8 +2560,8 @@ if (c == 1) {
 """
         self.assertEqual(test, expected)
 
-    def test_expr_target(self):
-        """Simple test that the target of `switch` can be `Expr` nodes."""
+    def test_switch_v1_expr_target(self):
+        """Simple test that the target of prerelease `switch` can be `Expr` nodes."""
         bits = [Qubit(), Clbit()]
         cr = ClassicalRegister(2, "cr")
         case0 = QuantumCircuit(1)