At most one double star pattern may be in a mapping pattern. The double star
pattern must be the last subpattern in the mapping pattern.
-Duplicate key values in mapping patterns are disallowed. (If all key patterns
-are literal patterns this is considered a syntax error; otherwise this is a
-runtime error and will raise :exc:`ValueError`.)
+Duplicate keys in mapping patterns are disallowed. Duplicate literal keys will
+raise a :exc:`SyntaxError`. Two keys that otherwise have the same value will
+raise a :exc:`ValueError` at runtime.
The following is the logical flow for matching a mapping pattern against a
subject value:
mapping, the mapping pattern succeeds.
#. If duplicate keys are detected in the mapping pattern, the pattern is
- considered invalid and :exc:`ValueError` is raised.
+ considered invalid. A :exc:`SyntaxError` is raised for duplicate literal
+ values; or a :exc:`ValueError` for named keys of the same value.
.. note:: Key-value pairs are matched using the two-argument form of the mapping
subject's ``get()`` method. Matched key-value pairs must already be present
pass
""")
+ def test_mapping_pattern_duplicate_key(self):
+ self.assert_syntax_error("""
+ match ...:
+ case {"a": _, "a": _}:
+ pass
+ """)
+
+ def test_mapping_pattern_duplicate_key_edge_case0(self):
+ self.assert_syntax_error("""
+ match ...:
+ case {0: _, False: _}:
+ pass
+ """)
+
+ def test_mapping_pattern_duplicate_key_edge_case1(self):
+ self.assert_syntax_error("""
+ match ...:
+ case {0: _, 0.0: _}:
+ pass
+ """)
+
+ def test_mapping_pattern_duplicate_key_edge_case2(self):
+ self.assert_syntax_error("""
+ match ...:
+ case {0: _, -0: _}:
+ pass
+ """)
+
+ def test_mapping_pattern_duplicate_key_edge_case3(self):
+ self.assert_syntax_error("""
+ match ...:
+ case {0: _, 0j: _}:
+ pass
+ """)
class TestTypeErrors(unittest.TestCase):
class TestValueErrors(unittest.TestCase):
- def test_mapping_pattern_checks_duplicate_key_0(self):
- x = {"a": 0, "b": 1}
- w = y = z = None
- with self.assertRaises(ValueError):
- match x:
- case {"a": y, "a": z}:
- w = 0
- self.assertIs(w, None)
- self.assertIs(y, None)
- self.assertIs(z, None)
-
def test_mapping_pattern_checks_duplicate_key_1(self):
class Keys:
KEY = "a"
--- /dev/null
+Mapping patterns in ``match`` statements with two or more equal literal
+keys will now raise a :exc:`SyntaxError` at compile-time.
}
// Collect all of the keys into a tuple for MATCH_KEYS and
// COPY_DICT_WITHOUT_KEYS. They can either be dotted names or literals:
+
+ // Maintaining a set of Constant_kind kind keys allows us to raise a
+ // SyntaxError in the case of duplicates.
+ PyObject *seen = PySet_New(NULL);
+ if (seen == NULL) {
+ return 0;
+ }
+
+ // NOTE: goto error on failure in the loop below to avoid leaking `seen`
for (Py_ssize_t i = 0; i < size; i++) {
expr_ty key = asdl_seq_GET(keys, i);
if (key == NULL) {
const char *e = "can't use NULL keys in MatchMapping "
"(set 'rest' parameter instead)";
SET_LOC(c, ((pattern_ty) asdl_seq_GET(patterns, i)));
- return compiler_error(c, e);
+ compiler_error(c, e);
+ goto error;
+ }
+
+ if (key->kind == Constant_kind) {
+ int in_seen = PySet_Contains(seen, key->v.Constant.value);
+ if (in_seen < 0) {
+ goto error;
+ }
+ if (in_seen) {
+ const char *e = "mapping pattern checks duplicate key (%R)";
+ compiler_error(c, e, key->v.Constant.value);
+ goto error;
+ }
+ if (PySet_Add(seen, key->v.Constant.value)) {
+ goto error;
+ }
}
- if (!MATCH_VALUE_EXPR(key)) {
+
+ else if (key->kind != Attribute_kind) {
const char *e = "mapping pattern keys may only match literals and attribute lookups";
- return compiler_error(c, e);
+ compiler_error(c, e);
+ goto error;
+ }
+ if (!compiler_visit_expr(c, key)) {
+ goto error;
}
- VISIT(c, expr, key);
}
+
+ // all keys have been checked; there are no duplicates
+ Py_DECREF(seen);
+
ADDOP_I(c, BUILD_TUPLE, size);
ADDOP(c, MATCH_KEYS);
// There's now a tuple of keys and a tuple of values on top of the subject:
pc->on_top--;
ADDOP(c, POP_TOP);
return 1;
+
+error:
+ Py_DECREF(seen);
+ return 0;
}
static int