Repository: Qiskit/qiskit

Issue:
QASM output of registerless Clbits and Qubits can shadow an existing name
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: 0.19.dev
- **Python version**: all
- **Operating system**: all

### What is the current behavior?

Unbound clbits and qubits are output in QASM into registers invariably called `regless`.  This is a valid identifier for other registers, and the same name is emitted whether or not clashes exist, presumably because `QuantumCircuit` usually keeps names unique when the registers are added to the circuit, so there usually can't be a clash.

### Steps to reproduce the problem

```python
>>> from qiskit.circuit import QuantumCircuit, ClassicalRegister, Clbit, Qubit
>>> cr = ClassicalRegister(2, name='regless')
>>> cb = Clbit()
>>> qb = Qubit()
>>> qc = QuantumCircuit(cr, [cb], [qb])
>>> print(qc.qasm())
OPENQASM 2.0;
include "qelib1.inc";
creg regless[2];
qreg regless[1];
creg regless[1];
```

A similar failure occurs with a `QuantumRegister(..., name="regless")`.

### What is the expected behavior?

The automatically generated `qreg regless[1]` and `creg regless[1]` should have unique names.

### Suggested solutions

As far as I know there are no universally reserved names, but we can just generate a name like `_[cq]regless_<entropy>`, which should be good enough - by `<entropy>` I mostly just mean noise to prevent users from accidentally shadowing the name.  `QuantumCircuit.qasm()` should (I've got to assume!) be deterministic, so the entropy could just be a string like `_qiskitprivate`, or if we really want to make sure users can't create a clash then
```python
entropy = hash(tuple(register.name for registers in self.qregs + self.cregs))
```
That seems like overkill to me, though.


Discussion:

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -18,6 +18,7 @@
 import itertools
 import functools
 import multiprocessing as mp
+import string
 from collections import OrderedDict, defaultdict
 from typing import (
     Union,
@@ -1410,6 +1411,23 @@ def _check_compatible_regs(self, rhs: "QuantumCircuit") -> None:
                             f" registers {element1} and {element2} not compatible"
                         )
 
+    def _unique_register_name(self, prefix: str = "") -> str:
+        """Generate a register name with the given prefix, which is unique within this circuit."""
+        used = {
+            reg.name[len(prefix) :]
+            for reg in itertools.chain(self.qregs, self.cregs)
+            if reg.name.startswith(prefix)
+        }
+        characters = (string.digits + string.ascii_letters) if prefix else string.ascii_letters
+        for parts in itertools.chain.from_iterable(
+            itertools.product(characters, repeat=n) for n in itertools.count(1)
+        ):
+            name = "".join(parts)
+            if name not in used:
+                return prefix + name
+        # This isn't actually reachable because the above loop is infinite.
+        return prefix
+
     def qasm(
         self,
         formatted: bool = False,
@@ -1497,32 +1515,27 @@ def qasm(
         for register in self.cregs:
             string_temp += register.qasm() + "\n"
 
-        qreg_bits = {bit for reg in self.qregs for bit in reg}
-        creg_bits = {bit for reg in self.cregs for bit in reg}
-        regless_qubits = []
-        regless_clbits = []
-
-        if set(self.qubits) != qreg_bits:
-            regless_qubits = [bit for bit in self.qubits if bit not in qreg_bits]
-            string_temp += "qreg %s[%d];\n" % ("regless", len(regless_qubits))
-
-        if set(self.clbits) != creg_bits:
-            regless_clbits = [bit for bit in self.clbits if bit not in creg_bits]
-            string_temp += "creg %s[%d];\n" % ("regless", len(regless_clbits))
-
         bit_labels = {
             bit: "%s[%d]" % (reg.name, idx)
             for reg in self.qregs + self.cregs
             for (idx, bit) in enumerate(reg)
         }
 
-        bit_labels.update(
-            {
-                bit: "regless[%d]" % idx
-                for reg in (regless_qubits, regless_clbits)
-                for idx, bit in enumerate(reg)
-            }
-        )
+        regless_qubits = set(self.qubits) - {bit for reg in self.qregs for bit in reg}
+        regless_clbits = set(self.clbits) - {bit for reg in self.cregs for bit in reg}
+
+        if regless_qubits:
+            register_name = self._unique_register_name("qregless_")
+            string_temp += f"qreg {register_name}[{len(regless_qubits)}];\n"
+            bit_labels.update(
+                {bit: f"{register_name}[{idx}]" for idx, bit in enumerate(regless_qubits)}
+            )
+        if regless_clbits:
+            register_name = self._unique_register_name("cregless_")
+            string_temp += f"creg {register_name}[{len(regless_clbits)}];\n"
+            bit_labels.update(
+                {bit: f"{register_name}[{idx}]" for idx, bit in enumerate(regless_clbits)}
+            )
 
         for instruction, qargs, cargs in self._data:
             if instruction.name == "measure":


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
@@ -13,10 +13,11 @@
 """Test Qiskit's QuantumCircuit class."""
 
 from math import pi
+import re
 
 from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
 from qiskit.test import QiskitTestCase
-from qiskit.circuit import Parameter
+from qiskit.circuit import Parameter, Qubit, Clbit
 from qiskit.qasm.exceptions import QasmError
 
 
@@ -300,3 +301,32 @@ def test_circuit_qasm_with_mcx_gate_variants(self):
 mcx_vchain q[0],q[1],q[2],q[3],q[4],q[5],q[6],q[7],q[8];\n"""
 
         self.assertEqual(qc.qasm(), expected_qasm)
+
+    def test_circuit_qasm_with_registerless_bits(self):
+        """Test that registerless bits do not have naming collisions in their registers."""
+        initial_registers = [QuantumRegister(2), ClassicalRegister(2)]
+        qc = QuantumCircuit(*initial_registers, [Qubit(), Clbit()])
+        # Match a 'qreg identifier[3];'-like QASM register declaration.
+        register_regex = re.compile(r"\s*[cq]reg\s+(\w+)\s*\[\d+\]\s*", re.M)
+        qasm_register_names = set()
+        for statement in qc.qasm().split(";"):
+            match = register_regex.match(statement)
+            if match:
+                qasm_register_names.add(match.group(1))
+        self.assertEqual(len(qasm_register_names), 4)
+
+        # Check that no additional registers were added to the circuit.
+        self.assertEqual(len(qc.qregs), 1)
+        self.assertEqual(len(qc.cregs), 1)
+
+        # Check that the registerless-register names are recalculated after adding more registers,
+        # to avoid naming clashes in this case.
+        generated_names = qasm_register_names - {register.name for register in initial_registers}
+        for generated_name in generated_names:
+            qc.add_register(QuantumRegister(1, name=generated_name))
+        qasm_register_names = set()
+        for statement in qc.qasm().split(";"):
+            match = register_regex.match(statement)
+            if match:
+                qasm_register_names.add(match.group(1))
+        self.assertEqual(len(qasm_register_names), 6)