gh-145019: improve SyntaxError when match patterns bind different names#145939
gh-145019: improve SyntaxError when match patterns bind different names#145939picnixz wants to merge 3 commits intopython:mainfrom
SyntaxError when match patterns bind different names#145939Conversation
…ferent names
Co-authored-by: AN Long <aisk@users.noreply.github.com>
| _PyCompile_Error( | ||
| c, LOC(p), | ||
| "alternative patterns bind different names " | ||
| "(first pattern binds %S, pattern %zd binds %S)", |
There was a problem hiding this comment.
Nit: rather than "first pattern... pattern X...", let's be consistent.
"second/third/etc pattern" is tricky, so:
| "(first pattern binds %S, pattern %zd binds %S)", | |
| "(pattern 1 binds %S, pattern %zd binds %S)", |
| 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']) |
There was a problem hiding this comment.
| 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']) | |
| SyntaxError: alternative patterns bind different names (pattern 1 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 (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']) |
There was a problem hiding this comment.
I actually wondered whether it was better to display the list as is or use some join to have a string. How do you feel about it?
There was a problem hiding this comment.
Currently:l (I think)
- pattern 1 binds ["a", "b"] but pattern k binds ["u", "v"]
Suggestion:
- (formatted) pattern 1 binds "a" and "b" but pattern k binds "u" and "v"
- (minimal) pattern k binds "u' and "v' but pattern 1 does not
- (minimal swapped) pattern 1 binds "a" and "b" but pattern k does not.
- If pattern 1 binds a and b and pattern k binds a and c, only report that patten k does not bind b (or vice-versa)
There was a problem hiding this comment.
I think either current or number 1. Current will be easier :)
There was a problem hiding this comment.
Yes current is simple and easier to parse. It may however be better if we remove common entries (unless it is already done).
|
Thanks Hugo for the review! I am unavailable this week-end so I will address this next week. If you want to apply your changes, feel free to do so! Otherwise I will do it once I have my laptop. |
| _PyCompile_Error(c, LOC(p), "alternative patterns bind different names"); | ||
| diff:; | ||
| PyObject *no_names = NULL; | ||
| if (PyList_GET_SIZE(control) == 0 || !mismatched_names) { |
There was a problem hiding this comment.
Unless I am missing something here the condition if (PyList_GET_SIZE(control) == 0 || !mismatched_names) has a permanently dead second operand. Both paths to goto diff (lines 6309-6311 and 6321-6323) unconditionally execute mismatched_names = Py_NewRef(pc->stores) before jumping, so mismatched_names is always non-NULL at the diff: label.
Similarly, the ternary at line 6428 (mismatched_names == NULL ? no_names : mismatched_names) always evaluates to mismatched_names.
| diff:; | ||
| PyObject *no_names = NULL; | ||
| if (PyList_GET_SIZE(control) == 0 || !mismatched_names) { | ||
| no_names = PyUnicode_FromString("no names"); |
There was a problem hiding this comment.
This guy is only substituted when the first pattern (control) has 0 names. When a later pattern binds nothing (e.g., case (a, b) | 1:), mismatched_names is a valid empty list, so the error message would read:
alternative patterns bind different names (first pattern binds ['a', 'b'], pattern 2 binds [])
Showing [] instead of no names is inconsistent with the reverse case
There was a problem hiding this comment.
Oh good catch. I should have increased test coverage. I will put more cases to exercise this path. Thanks!
| diff:; | ||
| PyObject *no_names = NULL; | ||
| if (PyList_GET_SIZE(control) == 0 || !mismatched_names) { | ||
| no_names = PyUnicode_FromString("no names"); |
There was a problem hiding this comment.
This is freed later before the error: label, but error: itself doesn't free it. Currently safe because the only goto error from diff: is when no_names is NULL. But this is fragile as if future code between allocation and Py_XDECREF(no_names) adds a goto error, it would leak. Other variables (mismatched_names) use the pattern of cleanup in error:. Consider moving no_names to function scope and adding Py_XDECREF(no_names) to the error: block.
Uh oh!
There was an error while loading. Please reload this page.