-
-
Notifications
You must be signed in to change notification settings - Fork 32.5k
bpo-40612: Fix SyntaxError edge cases in traceback formatting #20072
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
87e78b5
b951b0f
ea1c6f2
72e5b93
6df7662
1c7e6bb
31641ff
f93a54c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Fix edge cases in SyntaxError formatting. If the offset is <= 0, no caret is printed. | ||
If the offset is > line length, the caret is printed pointing just after the last character. |
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -554,36 +554,58 @@ parse_syntax_error(PyObject *err, PyObject **message, PyObject **filename, | |||||||||||||||
static void | ||||||||||||||||
print_error_text(PyObject *f, int offset, PyObject *text_obj) | ||||||||||||||||
{ | ||||||||||||||||
const char *text; | ||||||||||||||||
const char *nl; | ||||||||||||||||
|
||||||||||||||||
text = PyUnicode_AsUTF8(text_obj); | ||||||||||||||||
/* Convert text to a char pointer; return if error */ | ||||||||||||||||
const char *text = PyUnicode_AsUTF8(text_obj); | ||||||||||||||||
if (text == NULL) | ||||||||||||||||
return; | ||||||||||||||||
|
||||||||||||||||
if (offset >= 0) { | ||||||||||||||||
if (offset > 0 && (size_t)offset == strlen(text) && text[offset - 1] == '\n') | ||||||||||||||||
offset--; | ||||||||||||||||
for (;;) { | ||||||||||||||||
nl = strchr(text, '\n'); | ||||||||||||||||
if (nl == NULL || nl-text >= offset) | ||||||||||||||||
break; | ||||||||||||||||
offset -= (int)(nl+1-text); | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just fyi this code right here is the one that I mentioned in we-like-parsers#121 (comment) Whereby a SyntaxError with an offset relative to the start of the file will end up pointing to the right place. I'm tempted to say we should eventually just remove it since the new parser will always provide line-relative offsets. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm... Why would the offset in the SyntaxError object ever end up being file-relative? Do you know of any code that produces such SyntaxErrors? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the old parser it was this code: Lines 425 to 431 in 4a12d12
Try this out with the old parser as a quick example: code = """\
a = \\
\\
\\?"""
try:
compile(code, '<stdin>', 'exec')
except SyntaxError as e:
print(e)
print(e.lineno, e.offset) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, you mean the opposite of file. :-) It occurs when it's not read from a file. Also it doesn't occur all the time -- perhaps only when there's a continuation line? E.g. here all is good:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW, your example does show up weird in the old parser:
The new parser seems to solve the dilemma by suppressing the source text (also the offset is set to zero, meaning unknown):
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aah yes, it seems like it's just an issue with line continuations. I thought I had another example but it must have been a misunderstanding because So I guess this code here in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, so I have a serious question here. Is there a regression due to this PR for people using Here is what I did for research. First Python 3.8:
Then the master branch:
To me that looks like in both versions, the C formatter (invoked by |
||||||||||||||||
text = nl+1; | ||||||||||||||||
} | ||||||||||||||||
while (*text == ' ' || *text == '\t' || *text == '\f') { | ||||||||||||||||
text++; | ||||||||||||||||
offset--; | ||||||||||||||||
} | ||||||||||||||||
/* Convert offset from 1-based to 0-based */ | ||||||||||||||||
offset--; | ||||||||||||||||
|
||||||||||||||||
/* Strip leading whitespace from text, adjusting offset as we go */ | ||||||||||||||||
while (*text == ' ' || *text == '\t' || *text == '\f') { | ||||||||||||||||
text++; | ||||||||||||||||
offset--; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
/* Calculate text length excluding trailing newline */ | ||||||||||||||||
Py_ssize_t len = strlen(text); | ||||||||||||||||
if (len > 0 && text[len-1] == '\n') | ||||||||||||||||
len--; | ||||||||||||||||
gvanrossum marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
|
||||||||||||||||
/* Clip offset to at most len */ | ||||||||||||||||
if (offset > len) | ||||||||||||||||
offset = len; | ||||||||||||||||
gvanrossum marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
|
||||||||||||||||
/* Skip past newlines embedded in text */ | ||||||||||||||||
for (;;) { | ||||||||||||||||
const char *nl = strchr(text, '\n'); | ||||||||||||||||
if (nl == NULL) | ||||||||||||||||
break; | ||||||||||||||||
gvanrossum marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
Py_ssize_t inl = nl - text; | ||||||||||||||||
if (inl >= (Py_ssize_t)offset) | ||||||||||||||||
break; | ||||||||||||||||
gvanrossum marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
inl += 1; | ||||||||||||||||
text += inl; | ||||||||||||||||
len -= inl; | ||||||||||||||||
offset -= (int)inl; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
/* Print text */ | ||||||||||||||||
PyFile_WriteString(" ", f); | ||||||||||||||||
PyFile_WriteString(text, f); | ||||||||||||||||
if (*text == '\0' || text[strlen(text)-1] != '\n') | ||||||||||||||||
|
||||||||||||||||
/* Make sure there's a newline at the end */ | ||||||||||||||||
if (text[len] != '\n') | ||||||||||||||||
PyFile_WriteString("\n", f); | ||||||||||||||||
gvanrossum marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
if (offset == -1) | ||||||||||||||||
|
||||||||||||||||
/* Don't print caret if it points to the left of the text */ | ||||||||||||||||
if (offset < 0) | ||||||||||||||||
return; | ||||||||||||||||
|
||||||||||||||||
/* Write caret line */ | ||||||||||||||||
PyFile_WriteString(" ", f); | ||||||||||||||||
while (--offset > 0) | ||||||||||||||||
while (--offset >= 0) | ||||||||||||||||
PyFile_WriteString(" ", f); | ||||||||||||||||
gvanrossum marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
PyFile_WriteString("^\n", f); | ||||||||||||||||
} | ||||||||||||||||
|
Uh oh!
There was an error while loading. Please reload this page.