Skip to content

Commit 016af14

Browse files
[3.10] bpo-44589: raise a SyntaxError when mapping patterns have duplicate literal keys (GH-27131) (GH-27157)
(cherry picked from commit 2693132) Co-authored-by: Jack DeVries <[email protected]> Automerge-Triggered-By: GH:brandtbucher
1 parent ff7af22 commit 016af14

File tree

4 files changed

+82
-19
lines changed

4 files changed

+82
-19
lines changed

Doc/reference/compound_stmts.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -968,9 +968,9 @@ Syntax:
968968
At most one double star pattern may be in a mapping pattern. The double star
969969
pattern must be the last subpattern in the mapping pattern.
970970

971-
Duplicate key values in mapping patterns are disallowed. (If all key patterns
972-
are literal patterns this is considered a syntax error; otherwise this is a
973-
runtime error and will raise :exc:`ValueError`.)
971+
Duplicate keys in mapping patterns are disallowed. Duplicate literal keys will
972+
raise a :exc:`SyntaxError`. Two keys that otherwise have the same value will
973+
raise a :exc:`ValueError` at runtime.
974974

975975
The following is the logical flow for matching a mapping pattern against a
976976
subject value:
@@ -982,7 +982,8 @@ subject value:
982982
mapping, the mapping pattern succeeds.
983983

984984
#. If duplicate keys are detected in the mapping pattern, the pattern is
985-
considered invalid and :exc:`ValueError` is raised.
985+
considered invalid. A :exc:`SyntaxError` is raised for duplicate literal
986+
values; or a :exc:`ValueError` for named keys of the same value.
986987

987988
.. note:: Key-value pairs are matched using the two-argument form of the mapping
988989
subject's ``get()`` method. Matched key-value pairs must already be present

Lib/test/test_patma.py

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2901,6 +2901,40 @@ def test_wildcard_makes_remaining_patterns_unreachable_5(self):
29012901
pass
29022902
""")
29032903

2904+
def test_mapping_pattern_duplicate_key(self):
2905+
self.assert_syntax_error("""
2906+
match ...:
2907+
case {"a": _, "a": _}:
2908+
pass
2909+
""")
2910+
2911+
def test_mapping_pattern_duplicate_key_edge_case0(self):
2912+
self.assert_syntax_error("""
2913+
match ...:
2914+
case {0: _, False: _}:
2915+
pass
2916+
""")
2917+
2918+
def test_mapping_pattern_duplicate_key_edge_case1(self):
2919+
self.assert_syntax_error("""
2920+
match ...:
2921+
case {0: _, 0.0: _}:
2922+
pass
2923+
""")
2924+
2925+
def test_mapping_pattern_duplicate_key_edge_case2(self):
2926+
self.assert_syntax_error("""
2927+
match ...:
2928+
case {0: _, -0: _}:
2929+
pass
2930+
""")
2931+
2932+
def test_mapping_pattern_duplicate_key_edge_case3(self):
2933+
self.assert_syntax_error("""
2934+
match ...:
2935+
case {0: _, 0j: _}:
2936+
pass
2937+
""")
29042938

29052939
class TestTypeErrors(unittest.TestCase):
29062940

@@ -3008,17 +3042,6 @@ class Class:
30083042

30093043
class TestValueErrors(unittest.TestCase):
30103044

3011-
def test_mapping_pattern_checks_duplicate_key_0(self):
3012-
x = {"a": 0, "b": 1}
3013-
w = y = z = None
3014-
with self.assertRaises(ValueError):
3015-
match x:
3016-
case {"a": y, "a": z}:
3017-
w = 0
3018-
self.assertIs(w, None)
3019-
self.assertIs(y, None)
3020-
self.assertIs(z, None)
3021-
30223045
def test_mapping_pattern_checks_duplicate_key_1(self):
30233046
class Keys:
30243047
KEY = "a"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Mapping patterns in ``match`` statements with two or more equal literal
2+
keys will now raise a :exc:`SyntaxError` at compile-time.

Python/compile.c

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5998,20 +5998,53 @@ compiler_pattern_mapping(struct compiler *c, pattern_ty p, pattern_context *pc)
59985998
}
59995999
// Collect all of the keys into a tuple for MATCH_KEYS and
60006000
// COPY_DICT_WITHOUT_KEYS. They can either be dotted names or literals:
6001+
6002+
// Maintaining a set of Constant_kind kind keys allows us to raise a
6003+
// SyntaxError in the case of duplicates.
6004+
PyObject *seen = PySet_New(NULL);
6005+
if (seen == NULL) {
6006+
return 0;
6007+
}
6008+
6009+
// NOTE: goto error on failure in the loop below to avoid leaking `seen`
60016010
for (Py_ssize_t i = 0; i < size; i++) {
60026011
expr_ty key = asdl_seq_GET(keys, i);
60036012
if (key == NULL) {
60046013
const char *e = "can't use NULL keys in MatchMapping "
60056014
"(set 'rest' parameter instead)";
60066015
SET_LOC(c, ((pattern_ty) asdl_seq_GET(patterns, i)));
6007-
return compiler_error(c, e);
6016+
compiler_error(c, e);
6017+
goto error;
6018+
}
6019+
6020+
if (key->kind == Constant_kind) {
6021+
int in_seen = PySet_Contains(seen, key->v.Constant.value);
6022+
if (in_seen < 0) {
6023+
goto error;
6024+
}
6025+
if (in_seen) {
6026+
const char *e = "mapping pattern checks duplicate key (%R)";
6027+
compiler_error(c, e, key->v.Constant.value);
6028+
goto error;
6029+
}
6030+
if (PySet_Add(seen, key->v.Constant.value)) {
6031+
goto error;
6032+
}
60086033
}
6009-
if (!MATCH_VALUE_EXPR(key)) {
6034+
6035+
else if (key->kind != Attribute_kind) {
60106036
const char *e = "mapping pattern keys may only match literals and attribute lookups";
6011-
return compiler_error(c, e);
6037+
compiler_error(c, e);
6038+
goto error;
6039+
}
6040+
if (!compiler_visit_expr(c, key)) {
6041+
goto error;
60126042
}
6013-
VISIT(c, expr, key);
60146043
}
6044+
6045+
// all keys have been checked; there are no duplicates
6046+
Py_DECREF(seen);
6047+
60156048
ADDOP_I(c, BUILD_TUPLE, size);
60166049
ADDOP(c, MATCH_KEYS);
60176050
// There's now a tuple of keys and a tuple of values on top of the subject:
@@ -6046,6 +6079,10 @@ compiler_pattern_mapping(struct compiler *c, pattern_ty p, pattern_context *pc)
60466079
pc->on_top--;
60476080
ADDOP(c, POP_TOP);
60486081
return 1;
6082+
6083+
error:
6084+
Py_DECREF(seen);
6085+
return 0;
60496086
}
60506087

60516088
static int

0 commit comments

Comments
 (0)