Skip to content
Merged
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve :exc:`SyntaxError` when :keyword:`match` alternative patterns bind
different names. Patch by Bénédikt Tran.
24 changes: 22 additions & 2 deletions Python/codegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
Loading