https://github.com/python/cpython/commit/7641c5d69152eafc6f0fb865fdb7b97b33967ba2
commit: 7641c5d69152eafc6f0fb865fdb7b97b33967ba2
branch: main
author: Bénédikt Tran <[email protected]>
committer: picnixz <[email protected]>
date: 2026-10-19T13:02:04-07:00
summary:

gh-145019: improve `SyntaxError` when `match` patterns bind different names 
(#145939)

Co-authored-by: AN Long <[email protected]>
Co-authored-by: Petr Viktorin <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-03-14-11-20-52.gh-issue-145019.ls2EXo.rst
M Include/internal/pycore_global_objects_fini_generated.h
M Include/internal/pycore_global_strings.h
M Include/internal/pycore_runtime_init_generated.h
M Include/internal/pycore_unicodeobject_generated.h
M Lib/test/test_syntax.py
M Python/codegen.c

diff --git a/Include/internal/pycore_global_objects_fini_generated.h 
b/Include/internal/pycore_global_objects_fini_generated.h
index 0e2fab84aec0ae..e75376f008dccd 100644
--- a/Include/internal/pycore_global_objects_fini_generated.h
+++ b/Include/internal/pycore_global_objects_fini_generated.h
@@ -673,6 +673,7 @@ _PyStaticObjects_CheckAll(PyInterpreterState *interp) {
     _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(nlocals), 
"nlocals", 7);
     _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(node_depth), 
"node_depth", 10);
     _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(node_offset), 
"node_offset", 11);
+    _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(nothing), 
"nothing", 7);
     _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(ns), "ns", 2);
     _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(nstype), 
"nstype", 6);
     _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(nt), "nt", 2);
diff --git a/Include/internal/pycore_global_strings.h 
b/Include/internal/pycore_global_strings.h
index cbfa80f76b64cd..467a108e119995 100644
--- a/Include/internal/pycore_global_strings.h
+++ b/Include/internal/pycore_global_strings.h
@@ -685,6 +685,7 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(nlocals)
         STRUCT_FOR_ID(node_depth)
         STRUCT_FOR_ID(node_offset)
+        STRUCT_FOR_ID(nothing)
         STRUCT_FOR_ID(ns)
         STRUCT_FOR_ID(nstype)
         STRUCT_FOR_ID(nt)
diff --git a/Include/internal/pycore_runtime_init_generated.h 
b/Include/internal/pycore_runtime_init_generated.h
index 34dc6228a0bcb0..17ef3000b512d5 100644
--- a/Include/internal/pycore_runtime_init_generated.h
+++ b/Include/internal/pycore_runtime_init_generated.h
@@ -1960,6 +1960,7 @@ extern "C" {
     INIT_ID(nlocals), \
     INIT_ID(node_depth), \
     INIT_ID(node_offset), \
+    INIT_ID(nothing), \
     INIT_ID(ns), \
     INIT_ID(nstype), \
     INIT_ID(nt), \
diff --git a/Include/internal/pycore_unicodeobject_generated.h 
b/Include/internal/pycore_unicodeobject_generated.h
index f2f3b9de4a0d87..5fff1df282cd4a 100644
--- a/Include/internal/pycore_unicodeobject_generated.h
+++ b/Include/internal/pycore_unicodeobject_generated.h
@@ -637,6 +637,7 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
         offsetof(struct _Py_global_strings, 
identifiers._py_nlocals._ascii.ob_base),
         offsetof(struct _Py_global_strings, 
identifiers._py_node_depth._ascii.ob_base),
         offsetof(struct _Py_global_strings, 
identifiers._py_node_offset._ascii.ob_base),
+        offsetof(struct _Py_global_strings, 
identifiers._py_nothing._ascii.ob_base),
         offsetof(struct _Py_global_strings, identifiers._py_ns._ascii.ob_base),
         offsetof(struct _Py_global_strings, 
identifiers._py_nstype._ascii.ob_base),
         offsetof(struct _Py_global_strings, identifiers._py_nt._ascii.ob_base),
diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py
index 39ab53bb1f8b5f..04c759310d3d95 100644
--- a/Lib/test/test_syntax.py
+++ b/Lib/test/test_syntax.py
@@ -1502,6 +1502,50 @@
    Traceback (most recent call last):
    SyntaxError: invalid syntax
 
+Ensure that alternative patterns bind the same names (when there is
+a single capture in the first pattern that is not uniformly captured,
+the error message is sightly different).
+
+   >>> match 1:
+   ...     case x | 1: pass
+   Traceback (most recent call last):
+   SyntaxError: name capture 'x' makes remaining patterns unreachable
+
+   >>> match 1:
+   ...     case x | y: pass
+   Traceback (most recent call last):
+   SyntaxError: name capture 'x' makes remaining patterns unreachable
+
+   >>> match 1:
+   ...     case 1 | x: pass
+   Traceback (most recent call last):
+   SyntaxError: alternative patterns bind different names (pattern 1 binds 
nothing, pattern 2 binds ['x'])
+
+   >>> match 1:
+   ...     case (x, y) | 1: pass
+   Traceback (most recent call last):
+   SyntaxError: alternative patterns bind different names (pattern 1 binds 
['x', 'y'], pattern 2 binds nothing)
+
+   >>> match 1:
+   ...     case 1 | ("point", {"x": x, "y": y}): pass
+   Traceback (most recent call last):
+   SyntaxError: alternative patterns bind different names (pattern 1 binds 
nothing, pattern 2 binds ['x', 'y'])
+
+   >>> match 1:
+   ...     case ("point", {"x": x, "y": y}) | 1: pass
+   Traceback (most recent call last):
+   SyntaxError: alternative patterns bind different names (pattern 1 binds 
['x', 'y'], pattern 2 binds nothing)
+
+   >>> match 1:
+   ...     case ("user", {"id": id}) | ("admin", {"name": name}): pass
+   Traceback (most recent call last):
+   SyntaxError: alternative patterns bind different names (pattern 1 binds 
['id'], pattern 2 binds ['name'])
+
+   >>> match 1:
+   ...     case ("user", {"id": id}) | ("admin", {"id": id}) | ("other", 
{"ip": ip}): pass
+   Traceback (most recent call last):
+   SyntaxError: alternative patterns bind different names (pattern 1 binds 
['id'], pattern 3 binds ['ip'])
+
 Incomplete dictionary literals
 
    >>> {1:2, 3:4, 5}
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-14-11-20-52.gh-issue-145019.ls2EXo.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-14-11-20-52.gh-issue-145019.ls2EXo.rst
new file mode 100644
index 00000000000000..c249c07b212f77
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-14-11-20-52.gh-issue-145019.ls2EXo.rst
@@ -0,0 +1,2 @@
+Improve :exc:`SyntaxError` when :keyword:`match` alternative patterns bind
+different names. Patch by Bénédikt Tran.
diff --git a/Python/codegen.c b/Python/codegen.c
index 840dd3103a81c5..a975705576a167 100644
--- a/Python/codegen.c
+++ b/Python/codegen.c
@@ -6432,6 +6432,8 @@ codegen_pattern_or(compiler *c, pattern_ty p, 
pattern_context *pc)
     NEW_JUMP_TARGET_LABEL(c, end);
     Py_ssize_t size = asdl_seq_LEN(p->v.MatchOr.patterns);
     assert(size > 1);
+    PyObject *mismatched_names = NULL;
+    Py_ssize_t mismatch_index = 0;
     // We're going to be messing with pc. Keep the original info handy:
     pattern_context old_pc = *pc;
     Py_INCREF(pc->stores);
@@ -6466,6 +6468,8 @@ codegen_pattern_or(compiler *c, pattern_ty p, 
pattern_context *pc)
             control = Py_NewRef(pc->stores);
         }
         else if (nstores != PyList_GET_SIZE(control)) {
+            mismatch_index = i;
+            mismatched_names = Py_NewRef(pc->stores);
             goto diff;
         }
         else if (nstores) {
@@ -6476,6 +6480,8 @@ codegen_pattern_or(compiler *c, pattern_ty p, 
pattern_context *pc)
                 Py_ssize_t istores = PySequence_Index(pc->stores, name);
                 if (istores < 0) {
                     PyErr_Clear();
+                    mismatch_index = i;
+                    mismatched_names = Py_NewRef(pc->stores);
                     goto diff;
                 }
                 if (icontrol != istores) {
@@ -6567,10 +6573,24 @@ codegen_pattern_or(compiler *c, pattern_ty p, 
pattern_context *pc)
     // Pop the copy of the subject:
     ADDOP(c, LOC(p), POP_TOP);
     return SUCCESS;
-diff:
-    _PyCompile_Error(c, LOC(p), "alternative patterns bind different names");
+diff:;
+    int control_is_empty = PyList_GET_SIZE(control) == 0;
+    int pattern_is_empty = (
+        mismatched_names == NULL
+        || PyList_GET_SIZE(mismatched_names) == 0
+    );
+
+    _PyCompile_Error(
+        c, LOC(p),
+        "alternative patterns bind different names "
+        "(pattern 1 binds %S, pattern %zd binds %S)",
+        control_is_empty ? &_Py_ID(nothing) : control,
+        mismatch_index + 1,
+        pattern_is_empty ? &_Py_ID(nothing) : mismatched_names
+    );
 error:
     PyMem_Free(old_pc.fail_pop);
+    Py_XDECREF(mismatched_names);
     Py_DECREF(old_pc.stores);
     Py_XDECREF(control);
     return ERROR;

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to