gh-149277: Fix error position for invalid numeric literals - #149456
gh-149277: Fix error position for invalid numeric literals#149456anujbharambe wants to merge 3 commits into
Conversation
|
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
Documentation build overview
18 files changed ·
|
|
I have made the requested changes; please review again. Added |
|
Thanks for making the requested changes! @picnixz: please review the changes made to this pull request. |
| with self.subTest(source=source): | ||
| with self.assertRaises(SyntaxError) as cm: | ||
| compile(source, "<test>", "eval") | ||
| self.assertEqual(cm.exception.offset, expected_offset) |
There was a problem hiding this comment.
Please also test the rendered carret not just the carret offset. Use check_syntax_error for that.
| def test_end_of_numerical_literals_offset(self): | ||
| # gh-149277: verify the error caret points at the first invalid | ||
| # character, not the last valid digit. | ||
| cases = [ | ||
| ("0xfg", 4), | ||
| ("0x9g", 4), | ||
| ("0b1z", 4), | ||
| ("0o7q", 4), | ||
| ("9spam", 2), | ||
| ("0xfspam", 4), | ||
| ("1.0x", 4), | ||
| ("1e3w", 4), | ||
| ("1jz", 3), | ||
| ] | ||
| for source, expected_offset in cases: |
There was a problem hiding this comment.
Use @subTests decorator for cases instead of this loop and subTest().
|
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
| def test_end_of_numerical_literals_offset(self): | ||
| # gh-149277: verify the error caret points at the first invalid | ||
| # character, not the last valid digit. | ||
| cases = [ |
There was a problem hiding this comment.
Nit: the cases here all go through verify_end_of_number. Can we add 0xI/0bz too so we lock the behaviour for the prefix-only path, and maybe assert .msg as well?
skirpichev
left a comment
There was a problem hiding this comment.
@anujbharambe, there is a merge conflict now. Could you fix?
Also, to address @pablogsal review you need more changes. I believe that following patch will be complete:
diff --git a/Parser/lexer/number.c b/Parser/lexer/number.c
index 8bca8cbb9ad..02e11d12616 100644
--- a/Parser/lexer/number.c
+++ b/Parser/lexer/number.c
@@ -80,7 +80,6 @@ verify_end_of_number(struct tok_state *tok, int c, const char *kind) {
}
else /* In future releases, only error will remain. */
if (c < 128 && is_potential_identifier_char(c)) {
- tok_backup(tok, c);
_PyTokenizer_syntaxerror(tok, "invalid %s literal", kind);
return 0;
}
@@ -130,7 +129,6 @@ _PyLexer_scan_number(struct tok_state *tok, struct token *token, int c,
c = tok_nextc(tok);
}
if (!Py_ISXDIGIT(c)) {
- tok_backup(tok, c);
return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok, "invalid hexadecimal literal"));
}
do {
@@ -154,7 +152,6 @@ _PyLexer_scan_number(struct tok_state *tok, struct token *token, int c,
"invalid digit '%c' in octal literal", c));
}
else {
- tok_backup(tok, c);
return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok, "invalid octal literal"));
}
}
@@ -182,7 +179,6 @@ _PyLexer_scan_number(struct tok_state *tok, struct token *token, int c,
return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok, "invalid digit '%c' in binary literal", c));
}
else {
- tok_backup(tok, c);
return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok, "invalid binary literal"));
}
}| def test_end_of_numerical_literals_offset(self): | ||
| # gh-149277: verify the error caret points at the first invalid | ||
| # character, not the last valid digit. | ||
| cases = [ | ||
| ("0xfg", 4), | ||
| ("0x9g", 4), | ||
| ("0b1z", 4), | ||
| ("0o7q", 4), | ||
| ("9spam", 2), | ||
| ("0xfspam", 4), | ||
| ("1.0x", 4), | ||
| ("1e3w", 4), | ||
| ("1jz", 3), | ||
| ] | ||
| for source, expected_offset in cases: | ||
| with self.subTest(source=source): | ||
| with self.assertRaises(SyntaxError) as cm: | ||
| compile(source, "<test>", "eval") | ||
| self.assertEqual(cm.exception.offset, expected_offset) | ||
|
|
There was a problem hiding this comment.
| def test_end_of_numerical_literals_offset(self): | |
| # gh-149277: verify the error caret points at the first invalid | |
| # character, not the last valid digit. | |
| cases = [ | |
| ("0xfg", 4), | |
| ("0x9g", 4), | |
| ("0b1z", 4), | |
| ("0o7q", 4), | |
| ("9spam", 2), | |
| ("0xfspam", 4), | |
| ("1.0x", 4), | |
| ("1e3w", 4), | |
| ("1jz", 3), | |
| ] | |
| for source, expected_offset in cases: | |
| with self.subTest(source=source): | |
| with self.assertRaises(SyntaxError) as cm: | |
| compile(source, "<test>", "eval") | |
| self.assertEqual(cm.exception.offset, expected_offset) | |
| @subTests('source,offset,msg', | |
| [("0xfg", 4, "hexadecimal"), | |
| ("0x9g", 4, "hexadecimal"), | |
| ("0b1z", 4, "binary"), | |
| ("0o7q", 4, "octal"), | |
| ("9spam", 2, " decimal"), | |
| ("0xfspam", 4, "hexadecimal"), | |
| ("1.0x", 4, " decimal"), | |
| ("1e3w", 4, " decimal"), | |
| ("1jz", 3, "imaginary"), | |
| ("0xI", 3, "hexadecimal"), | |
| ("0bz", 3, "binary"), | |
| ]) | |
| def test_end_of_numerical_literals_offset(self, source, offset, msg): | |
| # gh-149277: verify the error caret points at the first invalid | |
| # character, not the last valid digit. | |
| with self.assertRaises(SyntaxError) as cm: | |
| compile(source, "<test>", "eval") | |
| self.assertEqual(cm.exception.offset, offset) | |
| self.assertIn(msg, cm.exception.msg) | |
| def test_end_of_numerical_literals_offset(self): | ||
| # gh-149277: verify the error caret points at the first invalid | ||
| # character, not the last valid digit. | ||
| cases = [ |
There was a problem hiding this comment.
Nit: the cases here all go through verify_end_of_number. Can we add 0xI/0bz too so we lock the behaviour for the prefix-only path, and maybe assert .msg as well?
|
Ok, this still has merge conflicts. Closing. Continued in #155534. @pablogsal, I hope your review was addressed. |
Summary
Fix the
SyntaxErrorcaret position for invalid numeric literals. Previously,the caret pointed at the last valid digit instead of the first invalid character.
For example,
0x9gnow correctly shows:0x9g
^
SyntaxError: invalid hexadecimal literal
Instead of the previous incorrect output:
0x9g
^
SyntaxError: invalid hexadecimal literal
The issue was a spurious
tok_backup(tok, c)call inverify_end_of_number()in the error branch. Since
_PyTokenizer_syntaxerrorcomputes the column offsetfrom
tok->cur, backing up one character caused the caret to point at thepreceding (valid) character. Removing the backup keeps
tok->curat the correctposition.
Fixes #149277