Repository: Qiskit/qiskit

Issue:
QASM2 exporter when appending empty circuit
### Environment

- **Qiskit Terra version**: qiskit==0.43.1
- **Python version**: 3.10
- **Operating system**: Ubuntu 18.04


### What is happening?

The QASM2 exporter creates an invalid qasm when an empty circuit is appended.

### How can we reproduce the issue?

Running this simple snippet, where a circuit is appended to another generates an invalid QASM2:
```python
from qiskit import QuantumCircuit
q_1 = QuantumCircuit(5, name='legit_circuit')
q_2 = QuantumCircuit(name='empty_circuit')
q_1.append(q_2)
q_1.qasm(filename="c1.qasm")
```
Output:
```qasm
OPENQASM 2.0;
include "qelib1.inc";
gate empty_circuit  {  }
qreg q[5];
empty_circuit ;
```
Where we have an empty instruction.
It is incorrect because if cannot be re-imported:
```python
from qiskit import QuantumCircuit
QuantumCircuit().from_qasm_file("c1.qasm")
```
Failing with error: 
```shell
qasm/qasmparser.py", line 397, in p_id_e
    raise QasmError("Expected an ID, received '" + str(program[1].value) + "'")
qiskit.qasm.exceptions.QasmError: "Expected an ID, received '{'"
```

### What should happen?

I would have expected a valid `qasm` file or an error while exporting.

### Any suggestions?

Probably this corner case should be checked when creating custom gates at the QASM level exporter. Either  an error should be thrown when exporting, or ideally all empty instructions should be removed before exporting.
qasm2 exporter allows for custom instruction, resulting in invalid qasm2 code
### Environment

- **Qiskit Terra version**: master


### What is happening?

@omarcostahamido noticed that it is possible to create a custom instruction in a circuit and then export it as QASM2 code. The result is non-valid QASM2.


### How can we reproduce the issue?

This is the example provided by @omarcostahamido [in Slack](https://qiskit.slack.com/archives/CG8JSE0UB/p1638470791036300):
```
qc = QuantumCircuit(2,2, name='bell')
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
custom_gate = qc.to_instruction()

new_circ = QuantumCircuit(2,2)

new_circ.append(custom_gate, [0, 1],[0, 1])

print(new_circ)
print(new_circ.qasm())
```
```
     ┌───────┐
q_0: ┤0      ├
     │       │
q_1: ┤1      ├
     │  bell │
c_0: ╡0      ╞
     │       │
c_1: ╡1      ╞
     └───────┘
OPENQASM 2.0;
include "qelib1.inc";
gate bell q0,q1 { h q0; cx q0,q1; barrier q0,q1; measure q0; measure q1; }
qreg q[2];
creg c[2];
bell q[0],q[1],c[0],c[1];
```

### What should happen?

When a custom instruction is part of a circuit, exporting that circuit as QASM2 code should raise an error.

### Any suggestions?

_No response_


Discussion:
I think, according to the OpenQASM 2 grammar, `gate empty_circuit  {  }` is invalid.

I'm actually surprised that `QuantumCircuit(name='empty_circuit')` is valid :-/
Other non-valid OpenQASM2 exports:
```
from qiskit import QuantumCircuit
q_1 = QuantumCircuit(5, 1, name='legit_circuit')
q_2 = QuantumCircuit(1, name='with_classical_wire')
q_2.measure_all()
q_1.append(q_2, [0], [0])
print(q_1.qasm())
```

```
OPENQASM 2.0;
include "qelib1.inc";
gate with_classical_wire q0 { barrier q0; measure q0; }
qreg q[5];
creg c[1];
with_classical_wire q[0],c[0];
```
Thank you for creating the issue @1ucian0 😄 

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- 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:


Test patch:
diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py
--- 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."""