Skip to content

Commit b52e8ce

Browse files
gh-142539: Fix traceback caret location calculation for SyntaxErrors with wide chars (#142540)
1 parent 5b19c75 commit b52e8ce

File tree

3 files changed

+28
-5
lines changed

3 files changed

+28
-5
lines changed

Lib/test/test_traceback.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ def syntax_error_bad_indentation2(self):
8888
def tokenizer_error_with_caret_range(self):
8989
compile("blech ( ", "?", "exec")
9090

91+
def syntax_error_with_caret_wide_char(self):
92+
compile("女女女=1; 女女女/", "?", "exec")
93+
94+
def syntax_error_with_caret_wide_char_range(self):
95+
compile("f(x, 女女女 for 女女女 in range(30), z)", "?", "exec")
96+
9197
def test_caret(self):
9298
err = self.get_exception_format(self.syntax_error_with_caret,
9399
SyntaxError)
@@ -125,6 +131,20 @@ def test_caret(self):
125131
self.assertEqual(err[1].find("("), err[2].find("^")) # in the right place
126132
self.assertEqual(err[2].count("^"), 1)
127133

134+
def test_caret_wide_char(self):
135+
err = self.get_exception_format(self.syntax_error_with_caret_wide_char,
136+
SyntaxError)
137+
self.assertIn("^", err[2])
138+
# "女女女=1; 女女女/" has display width 17
139+
self.assertEqual(err[2].find("^"), 4 + 17)
140+
141+
err = self.get_exception_format(self.syntax_error_with_caret_wide_char_range,
142+
SyntaxError)
143+
self.assertIn("^", err[2])
144+
self.assertEqual(err[2].find("^"), 4 + 5)
145+
# "女女女 for 女女女 in range(30)" has display width 30
146+
self.assertEqual(err[2].count("^"), 30)
147+
128148
def test_nocaret(self):
129149
exc = SyntaxError("error", ("x.py", 23, None, "bad syntax"))
130150
err = traceback.format_exception_only(SyntaxError, exc)

Lib/traceback.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,10 +1464,11 @@ def _format_syntax_error(self, stype, **kwargs):
14641464
# Convert 1-based column offset to 0-based index into stripped text
14651465
colno = offset - 1 - spaces
14661466
end_colno = end_offset - 1 - spaces
1467-
caretspace = ' '
14681467
if colno >= 0:
1469-
# non-space whitespace (likes tabs) must be kept for alignment
1470-
caretspace = ((c if c.isspace() else ' ') for c in ltext[:colno])
1468+
# Calculate display width to account for wide characters
1469+
dp_colno = _display_width(ltext, colno)
1470+
highlighted = ltext[colno:end_colno]
1471+
caret_count = _display_width(highlighted) if highlighted else (end_colno - colno)
14711472
start_color = end_color = ""
14721473
if colorize:
14731474
# colorize from colno to end_colno
@@ -1480,9 +1481,9 @@ def _format_syntax_error(self, stype, **kwargs):
14801481
end_color = theme.reset
14811482
yield ' {}\n'.format(ltext)
14821483
yield ' {}{}{}{}\n'.format(
1483-
"".join(caretspace),
1484+
' ' * dp_colno,
14841485
start_color,
1485-
('^' * (end_colno - colno)),
1486+
'^' * caret_count,
14861487
end_color,
14871488
)
14881489
else:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`traceback`: Fix location of carets in :exc:`SyntaxError`\s when the
2+
source contains wide characters.

0 commit comments

Comments
 (0)