Repository: Qiskit/qiskit

Issue:
Circuit drawer is illegible for instructions with circuit parameters.
### Environment

- **Qiskit Terra version**: 0.23.3
- **Python version**: 3.7
- **Operating system**: MacOS


### What is happening?

If I try and make an instruction containing a quantum circuit as a parameter, the circuit drawer attempts to draw the contained circuit as the parameter value leading to an illegible drawing. Examples of such instructions are ControlFlowOps, thought these are treated as a special case in the drawer code. 

### How can we reproduce the issue?

```python
from qiskit import QuantumCircuit, transpile
from qiskit.circuit import QuantumCircuit, Instruction
from qiskit.circuit.random import random_circuit

qc = random_circuit(2, 2)

inst = Instruction("block", 2, 0, [qc])
qc = QuantumCircuit(2)
qc.append(inst, range(2))
qc.draw(fold=-1)
```
shows
```
     ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
q_0: ┤0                                                                                                                                                                                                                                              ├
     │  block(          ┌─────────────────────────────────┐
q_0: ──■──┤ U(0.24603,2.9981,1.8017,3.0896) ├
     ┌─┴─┐└────────────────┬────────────────┘
q_1: ┤ H ├─────────────────■─────────────────
     └───┘                                   ) │
q_1: ┤1                                                                                                                              │                                                                                                               ├
     └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```

### What should happen?

Skip attempting to add parameter values to the drawing if parameters are circuits. This would also remove requiring a special case in the drawer code for ControlFlowOps, as they would just be handled by the general case of circuit parameters.

Eg, the above example should draw as
```
     ┌────────┐
q_0: ┤0       ├
     │  block │
q_1: ┤1       ├
     └────────┘
```

### Any suggestions?

Change the `get_param_str` method to also skip QuantumCircuit parameters (it currenlty just skips ndarray parameters).


Discussion:
I agree that this isn't great, but what's the use-case for putting a `QuantumCircuit` in `params`?  That's not really what `params` are ideally meant to be for (I wasn't a fan of us overloading it in `ControlFlowOp` either).

*edit*: fwiw, I think it might be cleaner if we have a list of parameter types that we allow drawing rather than a list that we skip, but it would be tricky to ensure that we don't introduce regressions if we do that.
I am using it for intermediate instructions ins some complicated custom transpilation where i want to store a circuit block in an instruction for use later, and where i might want to transpile/modify that block while it is in the instruction in subsequent passes.

I tried doing this with a ControlFlowOp subclass but that doesnt work because DAGCircuit hard codes allowed ControlFlowOp subclasses. So it was easier to just make a regular Instruction subclass, but that leads to the current issue.

I like the idea of having allowed param values for printing though so they only get printed if numeric (int/float/complex) or string, and disable casting via `str(obj)` for other types
Ok yeah, that sounds fair as a current limitation.  I'm interested in expanding the allowed "block" constructs in the future to include a nestable "basic block", but right now it's not a top priority and if your custom instruction just about works then that's ok.

For the allowed parameter types: off the top of my head, the list of types we'd need to accept would be at least instances of `numbers.Number` (covers Python builtins and Numpy types, and the speed loss isn't important here) and `ParameterExpression`, but it's probably worth us auditing the current instructions defined by Terra to see if there are any more.

An alternative strategy (that I'm not sure how I feel about) would be to attempt to `str` everything, and if the output has more than one line in it, it's skipped.  There's less risk of regression from that, but equally, there's probably a good chance it doesn't solve the problem much better than the current situation.

Resolving patch:
diff --git a/qiskit/visualization/circuit/_utils.py b/qiskit/visualization/circuit/_utils.py
--- a/qiskit/visualization/circuit/_utils.py
+++ b/qiskit/visualization/circuit/_utils.py
@@ -25,10 +25,9 @@
     Gate,
     Instruction,
     Measure,
-    ControlFlowOp,
 )
 from qiskit.circuit.library import PauliEvolutionGate
-from qiskit.circuit import ClassicalRegister
+from qiskit.circuit import ClassicalRegister, QuantumCircuit
 from qiskit.circuit.tools import pi_check
 from qiskit.converters import circuit_to_dag
 from qiskit.utils import optionals as _optionals
@@ -119,10 +118,11 @@ def get_gate_ctrl_text(op, drawer, style=None, calibrations=None):
 
 def get_param_str(op, drawer, ndigits=3):
     """Get the params as a string to add to the gate text display"""
-    if not hasattr(op, "params") or any(isinstance(param, np.ndarray) for param in op.params):
-        return ""
-
-    if isinstance(op, ControlFlowOp):
+    if (
+        not hasattr(op, "params")
+        or any(isinstance(param, np.ndarray) for param in op.params)
+        or any(isinstance(param, QuantumCircuit) for param in op.params)
+    ):
         return ""
 
     if isinstance(op, Delay):


Test patch:
diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py
--- a/test/python/visualization/test_circuit_text_drawer.py
+++ b/test/python/visualization/test_circuit_text_drawer.py
@@ -22,7 +22,7 @@
 import numpy
 
 from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile
-from qiskit.circuit import Gate, Parameter, Qubit, Clbit
+from qiskit.circuit import Gate, Parameter, Qubit, Clbit, Instruction
 from qiskit.quantum_info.operators import SuperOp
 from qiskit.quantum_info.random import random_unitary
 from qiskit.test import QiskitTestCase
@@ -1839,6 +1839,21 @@ def test_control_gate_label_with_cond_2_low_cregbundle(self):
 class TestTextDrawerParams(QiskitTestCase):
     """Test drawing parameters."""
 
+    def test_text_no_parameters(self):
+        """Test drawing with no parameters"""
+        expected = "\n".join(
+            [
+                "      ┌───┐",
+                "q: |0>┤ X ├",
+                "      └───┘",
+            ]
+        )
+
+        qr = QuantumRegister(1, "q")
+        circuit = QuantumCircuit(qr)
+        circuit.x(0)
+        self.assertEqual(str(_text_circuit_drawer(circuit)), expected)
+
     def test_text_parameters_mix(self):
         """cu3 drawing with parameters"""
         expected = "\n".join(
@@ -1904,6 +1919,39 @@ def test_text_utf8(self):
         circuit.u(0, phi, lam, 0)
         self.assertEqual(circuit.draw(output="text").single_string(), expected)
 
+    def test_text_ndarray_parameters(self):
+        """Test that if params are type ndarray, params are not displayed."""
+        # fmt: off
+        expected = "\n".join(["      ┌─────────┐",
+                              "q: |0>┤ Unitary ├",
+                              "      └─────────┘"])
+        # fmt: on
+        qr = QuantumRegister(1, "q")
+        circuit = QuantumCircuit(qr)
+        circuit.unitary(numpy.array([[0, 1], [1, 0]]), 0)
+        self.assertEqual(str(_text_circuit_drawer(circuit)), expected)
+
+    def test_text_qc_parameters(self):
+        """Test that if params are type QuantumCircuit, params are not displayed."""
+        expected = "\n".join(
+            [
+                "        ┌───────┐",
+                "q_0: |0>┤0      ├",
+                "        │  name │",
+                "q_1: |0>┤1      ├",
+                "        └───────┘",
+            ]
+        )
+
+        my_qc_param = QuantumCircuit(2)
+        my_qc_param.h(0)
+        my_qc_param.cx(0, 1)
+        inst = Instruction("name", 2, 0, [my_qc_param])
+        qr = QuantumRegister(2, "q")
+        circuit = QuantumCircuit(qr)
+        circuit.append(inst, [0, 1])
+        self.assertEqual(str(_text_circuit_drawer(circuit)), expected)
+
 
 class TestTextDrawerVerticalCompressionLow(QiskitTestCase):
     """Test vertical_compression='low'"""