Skip to content

Commit 7641c5d

Browse files
picnixzaiskencukou
authored
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>
1 parent 1016f73 commit 7641c5d

7 files changed

Lines changed: 72 additions & 2 deletions

File tree

‎Include/internal/pycore_global_objects_fini_generated.h‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Include/internal/pycore_global_strings.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,7 @@ struct _Py_global_strings {
685685
STRUCT_FOR_ID(nlocals)
686686
STRUCT_FOR_ID(node_depth)
687687
STRUCT_FOR_ID(node_offset)
688+
STRUCT_FOR_ID(nothing)
688689
STRUCT_FOR_ID(ns)
689690
STRUCT_FOR_ID(nstype)
690691
STRUCT_FOR_ID(nt)

‎Include/internal/pycore_runtime_init_generated.h‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Include/internal/pycore_unicodeobject_generated.h‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Lib/test/test_syntax.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,6 +1502,50 @@
15021502
Traceback (most recent call last):
15031503
SyntaxError: invalid syntax
15041504
1505+
Ensure that alternative patterns bind the same names (when there is
1506+
a single capture in the first pattern that is not uniformly captured,
1507+
the error message is sightly different).
1508+
1509+
>>> match 1:
1510+
... case x | 1: pass
1511+
Traceback (most recent call last):
1512+
SyntaxError: name capture 'x' makes remaining patterns unreachable
1513+
1514+
>>> match 1:
1515+
... case x | y: pass
1516+
Traceback (most recent call last):
1517+
SyntaxError: name capture 'x' makes remaining patterns unreachable
1518+
1519+
>>> match 1:
1520+
... case 1 | x: pass
1521+
Traceback (most recent call last):
1522+
SyntaxError: alternative patterns bind different names (pattern 1 binds nothing, pattern 2 binds ['x'])
1523+
1524+
>>> match 1:
1525+
... case (x, y) | 1: pass
1526+
Traceback (most recent call last):
1527+
SyntaxError: alternative patterns bind different names (pattern 1 binds ['x', 'y'], pattern 2 binds nothing)
1528+
1529+
>>> match 1:
1530+
... case 1 | ("point", {"x": x, "y": y}): pass
1531+
Traceback (most recent call last):
1532+
SyntaxError: alternative patterns bind different names (pattern 1 binds nothing, pattern 2 binds ['x', 'y'])
1533+
1534+
>>> match 1:
1535+
... case ("point", {"x": x, "y": y}) | 1: pass
1536+
Traceback (most recent call last):
1537+
SyntaxError: alternative patterns bind different names (pattern 1 binds ['x', 'y'], pattern 2 binds nothing)
1538+
1539+
>>> match 1:
1540+
... case ("user", {"id": id}) | ("admin", {"name": name}): pass
1541+
Traceback (most recent call last):
1542+
SyntaxError: alternative patterns bind different names (pattern 1 binds ['id'], pattern 2 binds ['name'])
1543+
1544+
>>> match 1:
1545+
... case ("user", {"id": id}) | ("admin", {"id": id}) | ("other", {"ip": ip}): pass
1546+
Traceback (most recent call last):
1547+
SyntaxError: alternative patterns bind different names (pattern 1 binds ['id'], pattern 3 binds ['ip'])
1548+
15051549
Incomplete dictionary literals
15061550
15071551
>>> {1:2, 3:4, 5}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve :exc:`SyntaxError` when :keyword:`match` alternative patterns bind
2+
different names. Patch by Bénédikt Tran.

‎Python/codegen.c‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6432,6 +6432,8 @@ codegen_pattern_or(compiler *c, pattern_ty p, pattern_context *pc)
64326432
NEW_JUMP_TARGET_LABEL(c, end);
64336433
Py_ssize_t size = asdl_seq_LEN(p->v.MatchOr.patterns);
64346434
assert(size > 1);
6435+
PyObject *mismatched_names = NULL;
6436+
Py_ssize_t mismatch_index = 0;
64356437
// We're going to be messing with pc. Keep the original info handy:
64366438
pattern_context old_pc = *pc;
64376439
Py_INCREF(pc->stores);
@@ -6466,6 +6468,8 @@ codegen_pattern_or(compiler *c, pattern_ty p, pattern_context *pc)
64666468
control = Py_NewRef(pc->stores);
64676469
}
64686470
else if (nstores != PyList_GET_SIZE(control)) {
6471+
mismatch_index = i;
6472+
mismatched_names = Py_NewRef(pc->stores);
64696473
goto diff;
64706474
}
64716475
else if (nstores) {
@@ -6476,6 +6480,8 @@ codegen_pattern_or(compiler *c, pattern_ty p, pattern_context *pc)
64766480
Py_ssize_t istores = PySequence_Index(pc->stores, name);
64776481
if (istores < 0) {
64786482
PyErr_Clear();
6483+
mismatch_index = i;
6484+
mismatched_names = Py_NewRef(pc->stores);
64796485
goto diff;
64806486
}
64816487
if (icontrol != istores) {
@@ -6567,10 +6573,24 @@ codegen_pattern_or(compiler *c, pattern_ty p, pattern_context *pc)
65676573
// Pop the copy of the subject:
65686574
ADDOP(c, LOC(p), POP_TOP);
65696575
return SUCCESS;
6570-
diff:
6571-
_PyCompile_Error(c, LOC(p), "alternative patterns bind different names");
6576+
diff:;
6577+
int control_is_empty = PyList_GET_SIZE(control) == 0;
6578+
int pattern_is_empty = (
6579+
mismatched_names == NULL
6580+
|| PyList_GET_SIZE(mismatched_names) == 0
6581+
);
6582+
6583+
_PyCompile_Error(
6584+
c, LOC(p),
6585+
"alternative patterns bind different names "
6586+
"(pattern 1 binds %S, pattern %zd binds %S)",
6587+
control_is_empty ? &_Py_ID(nothing) : control,
6588+
mismatch_index + 1,
6589+
pattern_is_empty ? &_Py_ID(nothing) : mismatched_names
6590+
);
65726591
error:
65736592
PyMem_Free(old_pc.fail_pop);
6593+
Py_XDECREF(mismatched_names);
65746594
Py_DECREF(old_pc.stores);
65756595
Py_XDECREF(control);
65766596
return ERROR;

0 commit comments

Comments
 (0)