Repository: Qiskit/qiskit-terra (python)

Issue:
Conditional Measurement is not conditional when exported to OpenQASM 3
### Environment

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


### What is happening?

When exporting a circuit with a conditional measurement to OpenQASM 3 the measurement is not conditional.

### How can we reproduce the issue?

```
cr = ClassicalRegister(3)
qr = QuantumRegister(3)
qc = QuantumCircuit(qr, cr)

# Prepare |011>
qc.x(qr[:2])
# Measure "001"
qc.measure(qr[0], cr[0])
# Conditionally measure "011", but the second bit is not reported
# due to conditional measurement phase 1 limitations. The measurement
# value is still available for runtime conditional executions.
qc.measure(qr[1], cr[1]).c_if(cr[0], 1)
# Conditionally prepare |111> based on the conditional measurement result
qc.x(qr[2]).c_if(cr[1], 1)
# Perform a final un-conditional measurement who's value will be determined
# by the previous conditional measurement which is due to the usage of the gate
# conditioned on the measurement result.
qc.measure(qr[2], cr[2])
```

Gives

```
OPENQASM 3;
bit[3] c0;
x $3;
x $4;
c0[0] = measure $3;
c0[1] = measure $4;
if (c0[1] == 1) {
  x $5;
}
c0[2] = measure $5;
```

### What should happen?

Should give

```
OPENQASM 3;
bit[3] c0;
x $3;
x $4;
c0[0] = measure $3;

if (c0[1] == 1) {
   c0[1] = measure $4;
}
if (c0[1] == 1) {
  x $5;
}
c0[2] = measure $5;

### Any suggestions?

_No response_

Pull request description:
Fix OpenQASM 3 built-ins with classical conditions
### Summary

The `.condition` attribute for `Instruction` instances corresponding to OpenQASM 3 built-ins (such as `reset` and `measure`) would previously be ignored.  This now correctly nests them inside a a branching statement in the exporter, so the condition is preserved.

<!--
⚠️ 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

Fix #8730.

Patch:
diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py
index e0a956d7b..a5bc667c0 100644
--- a/qiskit/qasm3/exporter.py
+++ b/qiskit/qasm3/exporter.py
@@ -779,45 +779,49 @@ class QASM3Builder:
         """Builds a list of call statements"""
         ret = []
         for instruction in instructions:
+            if isinstance(instruction.operation, ForLoopOp):
+                ret.append(self.build_for_loop(instruction))
+                continue
+            if isinstance(instruction.operation, WhileLoopOp):
+                ret.append(self.build_while_loop(instruction))
+                continue
+            if isinstance(instruction.operation, IfElseOp):
+                ret.append(self.build_if_statement(instruction))
+                continue
+            # Build the node, ignoring any condition.
             if isinstance(instruction.operation, Gate):
-                if instruction.operation.condition:
-                    eqcondition = self.build_eqcondition(instruction.operation.condition)
-                    operation_without_condition = instruction.operation.copy()
-                    operation_without_condition.condition = None
-                    true_body = self.build_program_block(
-                        [instruction.replace(operation=operation_without_condition)]
-                    )
-                    ret.append(ast.BranchingStatement(eqcondition, true_body))
-                else:
-                    ret.append(self.build_gate_call(instruction))
+                nodes = [self.build_gate_call(instruction)]
             elif isinstance(instruction.operation, Barrier):
                 operands = [
                     self.build_single_bit_reference(operand) for operand in instruction.qubits
                 ]
-                ret.append(ast.QuantumBarrier(operands))
+                nodes = [ast.QuantumBarrier(operands)]
             elif isinstance(instruction.operation, Measure):
                 measurement = ast.QuantumMeasurement(
                     [self.build_single_bit_reference(operand) for operand in instruction.qubits]
                 )
                 qubit = self.build_single_bit_reference(instruction.clbits[0])
-                ret.append(ast.QuantumMeasurementAssignment(qubit, measurement))
+                nodes = [ast.QuantumMeasurementAssignment(qubit, measurement)]
             elif isinstance(instruction.operation, Reset):
-                for operand in instruction.qubits:
-                    ret.append(ast.QuantumReset(self.build_single_bit_reference(operand)))
+                nodes = [
+                    ast.QuantumReset(self.build_single_bit_reference(operand))
+                    for operand in instruction.qubits
+                ]
             elif isinstance(instruction.operation, Delay):
-                ret.append(self.build_delay(instruction))
-            elif isinstance(instruction.operation, ForLoopOp):
-                ret.append(self.build_for_loop(instruction))
-            elif isinstance(instruction.operation, WhileLoopOp):
-                ret.append(self.build_while_loop(instruction))
-            elif isinstance(instruction.operation, IfElseOp):
-                ret.append(self.build_if_statement(instruction))
+                nodes = [self.build_delay(instruction)]
             elif isinstance(instruction.operation, BreakLoopOp):
-                ret.append(ast.BreakStatement())
+                nodes = [ast.BreakStatement()]
             elif isinstance(instruction.operation, ContinueLoopOp):
-                ret.append(ast.ContinueStatement())
+                nodes = [ast.ContinueStatement()]
+            else:
+                nodes = [self.build_subroutine_call(instruction)]
+
+            if instruction.operation.condition is None:
+                ret.extend(nodes)
             else:
-                ret.append(self.build_subroutine_call(instruction))
+                eqcondition = self.build_eqcondition(instruction.operation.condition)
+                body = ast.ProgramBlock(nodes)
+                ret.append(ast.BranchingStatement(eqcondition, body))
         return ret
 
     def build_if_statement(self, instruction: CircuitInstruction) -> ast.BranchingStatement:
diff --git a/releasenotes/notes/qasm3-fix-conditional-measurement-2d938cad74a9024a.yaml b/releasenotes/notes/qasm3-fix-conditional-measurement-2d938cad74a9024a.yaml
new file mode 100644
index 000000000..faca28264
--- /dev/null
+++ b/releasenotes/notes/qasm3-fix-conditional-measurement-2d938cad74a9024a.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+  - |
+    The OpenQASM 3 exporter (:mod:`qiskit.qasm3`) will now correctly handle
+    OpenQASM built-ins (such as ``reset`` and ``measure``) that have a classical
+    condition applied by :meth:`~.InstructionSet.c_if`.  Previously the condition
+    would have been ignored.


Test patch:
diff --git a/test/python/circuit/test_circuit_qasm3.py b/test/python/circuit/test_circuit_qasm3.py
index 17daa30fb..324dc1d38 100644
--- a/test/python/circuit/test_circuit_qasm3.py
+++ b/test/python/circuit/test_circuit_qasm3.py
@@ -22,7 +22,7 @@ import unittest
 from ddt import ddt, data
 
 from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, transpile
-from qiskit.circuit import Parameter, Qubit, Clbit, Instruction, Gate
+from qiskit.circuit import Parameter, Qubit, Clbit, Instruction, Gate, Delay, Barrier
 from qiskit.test import QiskitTestCase
 from qiskit.qasm3 import Exporter, dumps, dump, QASM3ExporterError
 from qiskit.qasm3.exporter import QASM3Builder
@@ -1696,6 +1696,60 @@ class TestCircuitQASM3ExporterTemporaryCasesWithBadParameterisation(QiskitTestCa
         )
         self.assertEqual(Exporter(includes=[]).dumps(circuit), expected_qasm)
 
+    def test_unusual_conditions(self):
+        """Test that special QASM constructs such as ``measure`` are correctly handled when the
+        Terra instructions have old-style conditions."""
+        qc = QuantumCircuit(3, 2)
+        qc.h(0)
+        qc.measure(0, 0)
+        qc.measure(1, 1).c_if(0, True)
+        qc.reset([0, 1]).c_if(0, True)
+        with qc.while_loop((qc.clbits[0], True)):
+            qc.break_loop().c_if(0, True)
+            qc.continue_loop().c_if(0, True)
+        # Terra forbids delay and barrier from being conditioned through `c_if`, but in theory they
+        # should work fine in a dynamic-circuits sense (although what a conditional barrier _means_
+        # is a whole other kettle of fish).
+        delay = Delay(16, "dt")
+        delay.condition = (qc.clbits[0], True)
+        qc.append(delay, [0], [])
+        barrier = Barrier(2)
+        barrier.condition = (qc.clbits[0], True)
+        qc.append(barrier, [0, 1], [])
+
+        expected = """
+OPENQASM 3;
+include "stdgates.inc";
+bit[2] c;
+qubit[3] _all_qubits;
+let q = _all_qubits[0:2];
+h q[0];
+c[0] = measure q[0];
+if (c[0] == 1) {
+  c[1] = measure q[1];
+}
+if (c[0] == 1) {
+  reset q[0];
+}
+if (c[0] == 1) {
+  reset q[1];
+}
+while (c[0] == 1) {
+  if (c[0] == 1) {
+    break;
+  }
+  if (c[0] == 1) {
+    continue;
+  }
+}
+if (c[0] == 1) {
+  delay[16dt] q[0];
+}
+if (c[0] == 1) {
+  barrier q[0], q[1];
+}"""
+        self.assertEqual(dumps(qc).strip(), expected.strip())
+
 
 @ddt
 class TestQASM3ExporterFailurePaths(QiskitTestCase):