Repository: quantumlib/Cirq

Issue:
eval(repr(measurement_gate)) fails for keys of the type `'a:b:c'`
**Description of the issue**
repr(measurement_gate) prints the wrong repr when using a nested measurement key of the form `a:b:c`. This is not catched in the json serialization tests because we explicitly call `MeasurementKey.parse_serialized(key)` in the `_from_json_dict_` function of `MeasurementGate` class. 

**How to reproduce the issue**

```python
In [1]: import cirq
   ...: g = cirq.MeasurementGate(2, cirq.MeasurementKey.parse_serialized('a:b:c'))
   ...: print(g, repr(g))
   ...: eval(repr(g))
cirq.MeasurementGate(2, 'a:b:c', ()) cirq.MeasurementGate(2, 'a:b:c', ())
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-7-0da6052a742e> in <module>
      2 g = cirq.MeasurementGate(2, cirq.MeasurementKey.parse_serialized('a:b:c'))
      3 print(g, repr(g))
----> 4 eval(repr(g))

<string> in <module>

~/opt/anaconda3/envs/cirq/lib/python3.8/site-packages/cirq/ops/measurement_gate.py in __init__(self, num_qubits, key, invert_mask, qid_shape)
     66         if len(self._qid_shape) != num_qubits:
     67             raise ValueError('len(qid_shape) != num_qubits')
---> 68         self.key = key  # type: ignore
     69         self.invert_mask = invert_mask or ()
     70         if self.invert_mask is not None and len(self.invert_mask) > self.num_qubits():

~/opt/anaconda3/envs/cirq/lib/python3.8/site-packages/cirq/ops/measurement_gate.py in key(self, key)
     80             self.mkey = key
     81         else:
---> 82             self.mkey = value.MeasurementKey(name=key)
     83
     84     def _qid_shape_(self) -> Tuple[int, ...]:

~/opt/anaconda3/envs/cirq/lib/python3.8/site-packages/cirq/value/measurement_key.py in __init__(self, name, path)

~/opt/anaconda3/envs/cirq/lib/python3.8/site-packages/cirq/value/measurement_key.py in __post_init__(self)
     45             raise ValueError("Measurement key name cannot be empty")
     46         if MEASUREMENT_KEY_SEPARATOR in self.name:
---> 47             raise ValueError(
     48                 f'Invalid key name: {self.name}\n{MEASUREMENT_KEY_SEPARATOR} is not allowed in '
     49                 'MeasurementKey. If this is a nested key string, use '

ValueError: Invalid key name: a:b:c
: is not allowed in MeasurementKey. If this is a nested key string, use `MeasurementKey.parse_serialized` for correct behavior.

```

**Cirq version**
0.12.0.dev



Discussion:
`eval(repr(measurement_key))` is also broken. 

```python
In [1]: import cirq
   ...: key = cirq.MeasurementKey(name = 'a')
   ...: print(key, repr(key))
   ...: eval(repr(key))
a cirq.MeasurementKey(name=a)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-12cbe8275992> in <module>
      2 key = cirq.MeasurementKey(name = 'a')
      3 print(key, repr(key))
----> 4 eval(repr(key))

<string> in <module>

NameError: name 'a' is not defined
```

Resolving patch:
diff --git a/cirq-core/cirq/ops/measurement_gate.py b/cirq-core/cirq/ops/measurement_gate.py
--- a/cirq-core/cirq/ops/measurement_gate.py
+++ b/cirq-core/cirq/ops/measurement_gate.py
@@ -193,7 +193,7 @@ def _quil_(
     def _op_repr_(self, qubits: Sequence['cirq.Qid']) -> str:
         args = list(repr(q) for q in qubits)
         if self.key != _default_measurement_key(qubits):
-            args.append(f'key={self.key!r}')
+            args.append(f'key={self.mkey!r}')
         if self.invert_mask:
             args.append(f'invert_mask={self.invert_mask!r}')
         arg_list = ', '.join(args)
@@ -206,7 +206,7 @@ def __repr__(self):
         return (
             f'cirq.MeasurementGate('
             f'{self.num_qubits()!r}, '
-            f'{self.key!r}, '
+            f'{self.mkey!r}, '
             f'{self.invert_mask}'
             f'{qid_shape_arg})'
         )


Test patch:
diff --git a/cirq-core/cirq/circuits/circuit_operation_test.py b/cirq-core/cirq/circuits/circuit_operation_test.py
--- a/cirq-core/cirq/circuits/circuit_operation_test.py
+++ b/cirq-core/cirq/circuits/circuit_operation_test.py
@@ -372,7 +372,7 @@ def test_string_format():
             cirq.CNOT(cirq.LineQubit(1), cirq.LineQubit(2)),
         ),
         cirq.Moment(
-            cirq.measure(cirq.LineQubit(0), cirq.LineQubit(1), cirq.LineQubit(2), key='m'),
+            cirq.measure(cirq.LineQubit(0), cirq.LineQubit(1), cirq.LineQubit(2), key=cirq.MeasurementKey(name='m')),
         ),
     ]),
 )"""
diff --git a/cirq-core/cirq/circuits/qasm_output_test.py b/cirq-core/cirq/circuits/qasm_output_test.py
--- a/cirq-core/cirq/circuits/qasm_output_test.py
+++ b/cirq-core/cirq/circuits/qasm_output_test.py
@@ -486,7 +486,7 @@ def filter_unpredictable_numbers(text):
 measure q[4] -> m__x[0];
 measure q[2] -> m_x_a[0];
 
-// Gate: cirq.MeasurementGate(3, 'multi', (False, True))
+// Gate: cirq.MeasurementGate(3, cirq.MeasurementKey(name='multi'), (False, True))
 measure q[1] -> m_multi[0];
 x q[2];  // Invert the following measurement
 measure q[2] -> m_multi[1];
diff --git a/cirq-core/cirq/ops/measurement_gate_test.py b/cirq-core/cirq/ops/measurement_gate_test.py
--- a/cirq-core/cirq/ops/measurement_gate_test.py
+++ b/cirq-core/cirq/ops/measurement_gate_test.py
@@ -18,10 +18,18 @@
 import cirq
 
 
-def test_eval_repr():
+@pytest.mark.parametrize(
+    'key',
+    [
+        'q0_1_0',
+        cirq.MeasurementKey(name='q0_1_0'),
+        cirq.MeasurementKey(path=('a', 'b'), name='c'),
+    ],
+)
+def test_eval_repr(key):
     # Basic safeguard against repr-inequality.
     op = cirq.GateOperation(
-        gate=cirq.MeasurementGate(1, cirq.MeasurementKey(path=(), name='q0_1_0'), ()),
+        gate=cirq.MeasurementGate(1, key),
         qubits=[cirq.GridQubit(0, 1)],
     )
     cirq.testing.assert_equivalent_repr(op)
@@ -271,7 +279,7 @@ def test_op_repr():
     assert repr(cirq.measure(a, b)) == ('cirq.measure(cirq.LineQubit(0), cirq.LineQubit(1))')
     assert repr(cirq.measure(a, b, key='out', invert_mask=(False, True))) == (
         "cirq.measure(cirq.LineQubit(0), cirq.LineQubit(1), "
-        "key='out', "
+        "key=cirq.MeasurementKey(name='out'), "
         "invert_mask=(False, True))"
     )
 
diff --git a/cirq-core/cirq/protocols/json_test_data/MeasurementGate.repr b/cirq-core/cirq/protocols/json_test_data/MeasurementGate.repr
--- a/cirq-core/cirq/protocols/json_test_data/MeasurementGate.repr
+++ b/cirq-core/cirq/protocols/json_test_data/MeasurementGate.repr
@@ -1 +1,5 @@
-[cirq.MeasurementGate(3, 'z', ()), cirq.MeasurementGate(3, 'z', (True, False, True)), cirq.MeasurementGate(3, cirq.MeasurementKey('z', path=('a','b')), (True, False), (1, 2, 3))]
\ No newline at end of file
+[
+    cirq.MeasurementGate(3, 'z', ()),
+    cirq.MeasurementGate(3, 'z', (True, False, True)),
+    cirq.MeasurementGate(3, cirq.MeasurementKey(path=('a', 'b'), name='z'), (True, False), (1, 2, 3)),
+]