Repository: Qiskit/qiskit

Issue:
Unroller raises unclear error when reaching a node without a definition
<!-- ⚠️ 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**: master @ 06dd1552
- **Python version**: 3.7.9
- **Operating system**: macOS Big Sur

### What is the current behavior?

Using the `Unroller` to unroll a circuit can fail, while using `transpile` with `optimization_level=0` works fine.

### Steps to reproduce the problem

```python
from qiskit import transpile
from qiskit.circuit.library import ZGate
from qiskit.transpiler.passes import Unroller

basis = ['u1', 'u2', 'u3', 'cx']
unroller = Unroller(basis)

circuit = ZGate().control().definition  # circuit with CZ gate
unroller(circuit)  # works

circuit = ZGate().control(2).definition  # circuit with CCZ gate
unroller(circuit)  # error, see traceback below
transpile(circuit, basis_gates=basis, optimization_level=0)  # works
```

The error in the unroller is:
```
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/Qiskit/qiskit-terra/qiskit/transpiler/passes/basis/unroller.py in run(self, dag)
     82                 try:
---> 83                     rule = rule[0][0].definition.data
     84                 except (TypeError, AttributeError) as err:

AttributeError: 'NoneType' object has no attribute 'data'

During handling of the above exception, another exception occurred:

QiskitError                               Traceback (most recent call last)
<ipython-input-17-c2529f65b3ed> in <module>
----> 1 Unroller(ubasis)(ZGate().control(2).definition)

~/Qiskit/qiskit-terra/qiskit/transpiler/basepasses.py in __call__(self, circuit, property_set)
    118             self.property_set = property_set_
    119
--> 120         result = self.run(circuit_to_dag(circuit))
    121
    122         result_circuit = circuit

~/Qiskit/qiskit-terra/qiskit/transpiler/passes/basis/unroller.py in run(self, dag)
     83                     rule = rule[0][0].definition.data
     84                 except (TypeError, AttributeError) as err:
---> 85                     raise QiskitError('Error decomposing node {}: {}'.format(node.name, err))
     86
     87             else:

QiskitError: "Error decomposing node p: 'NoneType' object has no attribute 'data'"
```

### What is the expected behavior?

The unroller should be able to unroll any circuit if the basis is sufficient.

### Suggested solutions

Not sure! 



Discussion:
So I took a quick look at this, and I"m not sure if it's something we can solve. The unroller just works with the definition property of a gate and just descends through the definitions until it reaches something in the basis or can't find anything and fails. In the case of the second that's what's happening the first gate in the definition is `P(pi/4)` which is defined in terms of `U` which doesn't have a definition. The unroller doesn't know that `U` is equivalent to `U3` and this problem of having gate definitions only in a single basis was exactly the reasoning behind the introduction of the basis translator. You would hit a similar thing if you used a ['p', 'sx', 'cx'] basis on the first example as the unroller is solely dependent on the basis being unrolled to matching the basis used in the gate definitions.
Of course, that cannot work! Ok maybe we should just update the warning which is not very descriptive.. 😄 
If the issue is just proper documentation of error, I will fix this issue.

Resolving patch:
diff --git a/qiskit/transpiler/passes/basis/unroller.py b/qiskit/transpiler/passes/basis/unroller.py
--- a/qiskit/transpiler/passes/basis/unroller.py
+++ b/qiskit/transpiler/passes/basis/unroller.py
@@ -72,8 +72,10 @@ def run(self, dag):
             try:
                 phase = node.op.definition.global_phase
                 rule = node.op.definition.data
-            except TypeError as err:
-                raise QiskitError(f'Error decomposing node {node.name}: {err}') from err
+            except (TypeError, AttributeError) as err:
+                raise QiskitError(f'Error decomposing node of instruction \'{node.name}\': '
+                                  f'{err}. Unable to define instruction \'{node.name}\' in the'
+                                  f' given basis.') from err
 
             # Isometry gates definitions can have widths smaller than that of the
             # original gate, in which case substitute_node will raise. Fall back
@@ -88,7 +90,9 @@ def run(self, dag):
                     phase += rule[0][0].definition.global_phase
                     rule = rule[0][0].definition.data
                 except (TypeError, AttributeError) as err:
-                    raise QiskitError(f'Error decomposing node {node.name}: {err}') from err
+                    raise QiskitError(f'Error decomposing node of instruction \'{node.name}\': '
+                                      f'{err}. Unable to define instruction \'{rule[0][0].name}\''
+                                      f' in the given basis.') from err
 
             else:
                 if not rule:


Test patch:
diff --git a/test/python/transpiler/test_pass_call.py b/test/python/transpiler/test_pass_call.py
--- a/test/python/transpiler/test_pass_call.py
+++ b/test/python/transpiler/test_pass_call.py
@@ -13,7 +13,10 @@
 """Test calling passes (passmanager-less)"""
 
 from qiskit import QuantumRegister, QuantumCircuit
+from qiskit.circuit.library import ZGate
+from qiskit.transpiler.passes import Unroller
 from qiskit.test import QiskitTestCase
+from qiskit.exceptions import QiskitError
 from qiskit.transpiler import PropertySet
 from ._dummy_passes import PassD_TP_NR_NP, PassE_AP_NR_NP, PassN_AP_NR_NP
 
@@ -84,3 +87,16 @@ def test_analysis_pass_remove_property(self):
         self.assertEqual(property_set, PropertySet({'to none': None}))
         self.assertIsInstance(property_set, dict)
         self.assertEqual(circuit, result)
+
+    def test_error_unknown_defn_unroller_pass(self):
+        """Check for proper error message when unroller cannot find the definition
+        of a gate."""
+        circuit = ZGate().control(2).definition
+        basis = ['u1', 'u2', 'u3', 'cx']
+        unroller = Unroller(basis)
+        with self.assertRaises(QiskitError) as cm:
+            unroller(circuit)
+        exp_msg = ('Error decomposing node of instruction \'p\': \'NoneType\' object has no' +
+                   ' attribute \'global_phase\'. Unable to define instruction \'u\' in the' +
+                   ' given basis.')
+        self.assertEqual(exp_msg, cm.exception.message)