Skip to content

gh-149277: Fix error position for invalid numeric literals - #149456

Closed
anujbharambe wants to merge 3 commits into
python:mainfrom
anujbharambe:fix/invalid-literal-error-position
Closed

gh-149277: Fix error position for invalid numeric literals#149456
anujbharambe wants to merge 3 commits into
python:mainfrom
anujbharambe:fix/invalid-literal-error-position

Conversation

@anujbharambe

@anujbharambe anujbharambe commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix the SyntaxError caret position for invalid numeric literals. Previously,
the caret pointed at the last valid digit instead of the first invalid character.

For example, 0x9g now 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 in verify_end_of_number()
in the error branch. Since _PyTokenizer_syntaxerror computes the column offset
from tok->cur, backing up one character caused the caret to point at the
preceding (valid) character. Removing the backup keeps tok->cur at the correct
position.

Fixes #149277

@picnixz picnixz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests.

@bedevere-app

bedevere-app Bot commented May 6, 2026

Copy link
Copy Markdown

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 I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@read-the-docs-community

Copy link
Copy Markdown

@anujbharambe

Copy link
Copy Markdown
Contributor Author

I have made the requested changes; please review again.

Added test_end_of_numerical_literals_offset in Lib/test/test_grammar.py that verifies SyntaxError.offset points at the first invalid character for various invalid numeric literal cases (hex, binary, octal, decimal, float, scientific notation, and imaginary literals).

@bedevere-app

bedevere-app Bot commented May 6, 2026

Copy link
Copy Markdown

Thanks for making the requested changes!

@picnixz: please review the changes made to this pull request.

@bedevere-app
bedevere-app Bot requested a review from picnixz May 6, 2026 19:51
Comment thread Lib/test/test_grammar.py
with self.subTest(source=source):
with self.assertRaises(SyntaxError) as cm:
compile(source, "<test>", "eval")
self.assertEqual(cm.exception.offset, expected_offset)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also test the rendered carret not just the carret offset. Use check_syntax_error for that.

Comment thread Lib/test/test_grammar.py
Comment on lines +143 to +157
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:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use @subTests decorator for cases instead of this loop and subTest().

@bedevere-app

bedevere-app Bot commented May 9, 2026

Copy link
Copy Markdown

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 I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

Comment thread Lib/test/test_grammar.py
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 = [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 skirpichev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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"));
                     }
                 }

Comment thread Lib/test/test_grammar.py
Comment on lines +143 to +162
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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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)

@skirpichev skirpichev self-assigned this Jul 29, 2026
@skirpichev skirpichev added the pending The issue will be closed if no feedback is provided label Jul 29, 2026
Comment thread Lib/test/test_grammar.py
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 = [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Ok, this still has merge conflicts. Closing.

Continued in #155534. @pablogsal, I hope your review was addressed.

@skirpichev skirpichev closed this Aug 11, 2026
@skirpichev skirpichev removed the pending The issue will be closed if no feedback is provided label Aug 11, 2026
@skirpichev skirpichev removed their assignment Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error display for invalid literal is incorrect

5 participants