bpo-44589: raise a SyntaxError when mapping patterns have duplicate literal keys (GH-27131)

This commit is contained in:
Jack DeVries 2021-07-14 20:38:42 -04:00 committed by GitHub
parent 3b8075f907
commit 2693132292
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 82 additions and 19 deletions

View File

@ -968,9 +968,9 @@ Syntax:
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:
@ -982,7 +982,8 @@ 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

View File

@ -2901,6 +2901,40 @@ class TestSyntaxErrors(unittest.TestCase):
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):
@ -3008,17 +3042,6 @@ 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"

View File

@ -0,0 +1,2 @@
Mapping patterns in ``match`` statements with two or more equal literal
keys will now raise a :exc:`SyntaxError` at compile-time.

View File

@ -6176,20 +6176,53 @@ compiler_pattern_mapping(struct compiler *c, pattern_ty p, pattern_context *pc)
}
// 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 (!MATCH_VALUE_EXPR(key)) {
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;
}
}
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;
}
VISIT(c, expr, key);
if (!compiler_visit_expr(c, key)) {
goto error;
}
}
// 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:
@ -6224,6 +6257,10 @@ compiler_pattern_mapping(struct compiler *c, pattern_ty p, pattern_context *pc)
pc->on_top--;
ADDOP(c, POP_TOP);
return 1;
error:
Py_DECREF(seen);
return 0;
}
static int