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

Co-authored-by: AN Long <aisk@users.noreply.github.com>
Co-authored-by: Petr Viktorin <encukou@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-10-19 13:02:04 -07:00
1 parent 1016f73534
commit 7641c5d691
7 files changed
+72 -2

No files matched your search

+1
View File
@@ -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);
+1
View File
@@ -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)
+1
View File
@@ -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), \
+1
View File
@@ -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),
+44
View File
@@ -1502,6 +1502,50 @@ Ensure that early = are not matched by the parser as invalid comparisons
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}
@@ -0,0 +1,2 @@
Improve :exc:`SyntaxError` when :keyword:`match` alternative patterns bind
different names. Patch by Bénédikt Tran.
+22 -2
View File
@@ -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;