diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 5dfea905bdc..8727d78b810 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -5025,7 +5025,9 @@ def _qasm2_define_custom_operation(operation, existing_gate_names, gates_to_defi Returns a potentially new :class:`.Instruction`, which should be used for the :meth:`~.Instruction.qasm` call (it may have been renamed).""" - from qiskit.circuit import library as lib # pylint: disable=cyclic-import + # pylint: disable=cyclic-import + from qiskit.circuit import library as lib + from qiskit.qasm2 import QASM2ExportError if operation.name in existing_gate_names: return operation @@ -5086,6 +5088,17 @@ def _qasm2_define_custom_operation(operation, existing_gate_names, gates_to_defi ) else: parameters_qasm = "" + + if operation.num_qubits == 0: + raise QASM2ExportError( + f"OpenQASM 2 cannot represent '{operation.name}, which acts on zero qubits." + ) + if operation.num_clbits != 0: + raise QASM2ExportError( + f"OpenQASM 2 cannot represent '{operation.name}', which acts on {operation.num_clbits}" + " classical bits." + ) + qubits_qasm = ",".join(f"q{i}" for i in range(parameterized_operation.num_qubits)) parameterized_definition = getattr(parameterized_operation, "definition", None) if parameterized_definition is None: diff --git a/releasenotes/notes/qasm_invalid_custom_instruction-7738db7ba1a1a5cf.yaml b/releasenotes/notes/qasm_invalid_custom_instruction-7738db7ba1a1a5cf.yaml new file mode 100644 index 00000000000..545032c17a1 --- /dev/null +++ b/releasenotes/notes/qasm_invalid_custom_instruction-7738db7ba1a1a5cf.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Qiskit can represent custom instructions that act on zero qubits, or on a non-zero number of + classical bits. These cannot be exported to OpenQASM 2, but previously :meth:`.QuantumCircuit.qasm` + would try, and output invalid OpenQASM 2. Instead, a :exc:`.QASM2ExportError` will now correctly + be raised. See `#7351 `__ and + `#10435 `__. diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py index 32f12e2c89e..83e1d7999a4 100644 --- a/test/python/circuit/test_circuit_qasm.py +++ b/test/python/circuit/test_circuit_qasm.py @@ -657,6 +657,30 @@ def test_circuit_raises_on_single_bit_condition(self): with self.assertRaisesRegex(QasmError, "OpenQASM 2 can only condition on registers"): qc.qasm() + def test_circuit_raises_invalid_custom_gate_no_qubits(self): + """OpenQASM 2 exporter of custom gates with no qubits. + See: https://github.com/Qiskit/qiskit-terra/issues/10435""" + legit_circuit = QuantumCircuit(5, name="legit_circuit") + empty_circuit = QuantumCircuit(name="empty_circuit") + legit_circuit.append(empty_circuit) + + with self.assertRaisesRegex(QasmError, "acts on zero qubits"): + legit_circuit.qasm() + + def test_circuit_raises_invalid_custom_gate_clbits(self): + """OpenQASM 2 exporter of custom instruction. + See: https://github.com/Qiskit/qiskit-terra/issues/7351""" + instruction = QuantumCircuit(2, 2, name="inst") + instruction.cx(0, 1) + instruction.measure([0, 1], [0, 1]) + custom_instruction = instruction.to_instruction() + + qc = QuantumCircuit(2, 2) + qc.append(custom_instruction, [0, 1], [0, 1]) + + with self.assertRaisesRegex(QasmError, "acts on 2 classical bits"): + qc.qasm() + def test_circuit_qasm_with_permutations(self): """Test circuit qasm() method with Permutation gates."""