]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-44589: raise a SyntaxError when mapping patterns have duplicate literal keys...
authorJack DeVries <58614260+jdevries3133@users.noreply.github.com>
Thu, 15 Jul 2021 00:38:42 +0000 (20:38 -0400)
committerGitHub <noreply@github.com>
Thu, 15 Jul 2021 00:38:42 +0000 (17:38 -0700)
Doc/reference/compound_stmts.rst
Lib/test/test_patma.py
Misc/NEWS.d/next/Core and Builtins/2021-07-13-23-19-41.bpo-44589.59OH8T.rst [new file with mode: 0644]
Python/compile.c

index c405423b75a65b0f6b6738f7988901f4fe8239db..8afcadbfbcf6b069f4d80023c23b8818d70ede43 100644 (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
index a8889cae1091fc73b70249e7d74ed08b280fc9a8..69a648a5a79faeba9475092e0d83bbfdf039db21 100644 (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"
diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-07-13-23-19-41.bpo-44589.59OH8T.rst b/Misc/NEWS.d/next/Core and Builtins/2021-07-13-23-19-41.bpo-44589.59OH8T.rst
new file mode 100644 (file)
index 0000000..23b2472
--- /dev/null
@@ -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.
index 81648c5111f9aacaf0194c77e6b751949b2634b7..50ff9b0666414093f18f961fff0a2c521dfb42df 100644 (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 (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:
@@ -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