bpo-44589: raise a SyntaxError when mapping patterns have duplicate literal keys (GH-27131)
This commit is contained in:
parent
3b8075f907
commit
2693132292
@ -968,9 +968,9 @@ Syntax:
|
|||||||
At most one double star pattern may be in a mapping pattern. The double star
|
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.
|
pattern must be the last subpattern in the mapping pattern.
|
||||||
|
|
||||||
Duplicate key values in mapping patterns are disallowed. (If all key patterns
|
Duplicate keys in mapping patterns are disallowed. Duplicate literal keys will
|
||||||
are literal patterns this is considered a syntax error; otherwise this is a
|
raise a :exc:`SyntaxError`. Two keys that otherwise have the same value will
|
||||||
runtime error and will raise :exc:`ValueError`.)
|
raise a :exc:`ValueError` at runtime.
|
||||||
|
|
||||||
The following is the logical flow for matching a mapping pattern against a
|
The following is the logical flow for matching a mapping pattern against a
|
||||||
subject value:
|
subject value:
|
||||||
@ -982,7 +982,8 @@ subject value:
|
|||||||
mapping, the mapping pattern succeeds.
|
mapping, the mapping pattern succeeds.
|
||||||
|
|
||||||
#. If duplicate keys are detected in the mapping pattern, the pattern is
|
#. 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
|
.. 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
|
subject's ``get()`` method. Matched key-value pairs must already be present
|
||||||
|
@ -2901,6 +2901,40 @@ class TestSyntaxErrors(unittest.TestCase):
|
|||||||
pass
|
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 TestTypeErrors(unittest.TestCase):
|
||||||
|
|
||||||
@ -3008,17 +3042,6 @@ class TestTypeErrors(unittest.TestCase):
|
|||||||
|
|
||||||
class TestValueErrors(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):
|
def test_mapping_pattern_checks_duplicate_key_1(self):
|
||||||
class Keys:
|
class Keys:
|
||||||
KEY = "a"
|
KEY = "a"
|
||||||
|
@ -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.
|
@ -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
|
// Collect all of the keys into a tuple for MATCH_KEYS and
|
||||||
// COPY_DICT_WITHOUT_KEYS. They can either be dotted names or literals:
|
// 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++) {
|
for (Py_ssize_t i = 0; i < size; i++) {
|
||||||
expr_ty key = asdl_seq_GET(keys, i);
|
expr_ty key = asdl_seq_GET(keys, i);
|
||||||
if (key == NULL) {
|
if (key == NULL) {
|
||||||
const char *e = "can't use NULL keys in MatchMapping "
|
const char *e = "can't use NULL keys in MatchMapping "
|
||||||
"(set 'rest' parameter instead)";
|
"(set 'rest' parameter instead)";
|
||||||
SET_LOC(c, ((pattern_ty) asdl_seq_GET(patterns, i)));
|
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";
|
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_I(c, BUILD_TUPLE, size);
|
||||||
ADDOP(c, MATCH_KEYS);
|
ADDOP(c, MATCH_KEYS);
|
||||||
// There's now a tuple of keys and a tuple of values on top of the subject:
|
// 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--;
|
pc->on_top--;
|
||||||
ADDOP(c, POP_TOP);
|
ADDOP(c, POP_TOP);
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
error:
|
||||||
|
Py_DECREF(seen);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
Loading…
x
Reference in New Issue
Block a user