diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index a3d485c998ac91..bf90750abfc305 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -1502,6 +1502,33 @@ Traceback (most recent call last): SyntaxError: invalid syntax +Ensure that alternative patterns bind the same names + + >>> 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 (first pattern binds no names, pattern 2 binds ['x']) + + >>> match 1: + ... case ("user", {"id": id}) | ("admin", {"name": name}): pass + Traceback (most recent call last): + SyntaxError: alternative patterns bind different names (first pattern 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 (first pattern 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 5749b615386717..871b486fc0cf84 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -6270,6 +6270,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); @@ -6304,6 +6306,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) { @@ -6314,6 +6318,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) { @@ -6405,10 +6411,26 @@ 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:; + PyObject *no_names = NULL; + if (PyList_GET_SIZE(control) == 0 || !mismatched_names) { + no_names = PyUnicode_FromString("no names"); + if (no_names == NULL) { + goto error; + } + } + _PyCompile_Error( + c, LOC(p), + "alternative patterns bind different names " + "(first pattern binds %S, pattern %zd binds %S)", + PyList_GET_SIZE(control) == 0 ? no_names : control, + mismatch_index + 1, + mismatched_names == NULL ? no_names : mismatched_names + ); + Py_XDECREF(no_names); error: PyMem_Free(old_pc.fail_pop); + Py_XDECREF(mismatched_names); Py_DECREF(old_pc.stores); Py_XDECREF(control); return ERROR;