Skip to content

Commit 86ed570

Browse files
committed
gh-104676: Fix error location for constructs with spaces and parentheses
Signed-off-by: Pablo Galindo <[email protected]>
1 parent 2a3926f commit 86ed570

File tree

3 files changed

+66
-3
lines changed

3 files changed

+66
-3
lines changed

Lib/test/test_traceback.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,24 @@ def f_with_binary_operator():
596596
result_lines = self.get_exception(f_with_binary_operator)
597597
self.assertEqual(result_lines, expected_error.splitlines())
598598

599+
def test_caret_for_binary_operators_with_spaces_and_parenthesis(self):
600+
def f_with_binary_operator():
601+
a = 1
602+
b = ""
603+
return ( a ) + b
604+
605+
lineno_f = f_with_binary_operator.__code__.co_firstlineno
606+
expected_error = (
607+
'Traceback (most recent call last):\n'
608+
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
609+
' callable()\n'
610+
f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
611+
' return ( a ) + b\n'
612+
' ~~~~~~~~~~^~~\n'
613+
)
614+
result_lines = self.get_exception(f_with_binary_operator)
615+
self.assertEqual(result_lines, expected_error.splitlines())
616+
599617
def test_caret_for_subscript(self):
600618
def f_with_subscript():
601619
some_dict = {'x': {'y': None}}
@@ -630,6 +648,24 @@ def f_with_subscript():
630648
result_lines = self.get_exception(f_with_subscript)
631649
self.assertEqual(result_lines, expected_error.splitlines())
632650

651+
def test_caret_for_subscript_with_spaces_and_parenthesis(self):
652+
def f_with_binary_operator():
653+
a = []
654+
b = c = 1
655+
return b [ a ] + c
656+
657+
lineno_f = f_with_binary_operator.__code__.co_firstlineno
658+
expected_error = (
659+
'Traceback (most recent call last):\n'
660+
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
661+
' callable()\n'
662+
f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
663+
' return b [ a ] + c\n'
664+
' ~~~~~~^^^^^^^^^\n'
665+
)
666+
result_lines = self.get_exception(f_with_binary_operator)
667+
self.assertEqual(result_lines, expected_error.splitlines())
668+
633669
def test_traceback_specialization_with_syntax_error(self):
634670
bytecode = compile("1 / 0 / 1 / 2\n", TESTFN, "exec")
635671

Lib/traceback.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -608,11 +608,21 @@ def _extract_caret_anchors_from_line_segment(segment):
608608
and not operator_str[operator_offset + 1].isspace()
609609
):
610610
right_anchor += 1
611+
612+
while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch in ")#"):
613+
left_anchor += 1
614+
right_anchor += 1
611615
return _Anchors(normalize(left_anchor), normalize(right_anchor))
612616
case ast.Subscript():
613-
subscript_start = normalize(expr.value.end_col_offset)
614-
subscript_end = normalize(expr.slice.end_col_offset + 1)
615-
return _Anchors(subscript_start, subscript_end)
617+
left_anchor = normalize(expr.value.end_col_offset)
618+
right_anchor = normalize(expr.slice.end_col_offset + 1)
619+
while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch != "["):
620+
left_anchor += 1
621+
while right_anchor < len(segment) and ((ch := segment[right_anchor]).isspace() or ch != "]"):
622+
right_anchor += 1
623+
if right_anchor < len(segment):
624+
right_anchor += 1
625+
return _Anchors(left_anchor, right_anchor)
616626

617627
return None
618628

Python/traceback.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,11 @@ extract_anchors_from_expr(const char *segment_str, expr_ty expr, Py_ssize_t *lef
616616
++*right_anchor;
617617
}
618618

619+
// Keep going if the current char is in ')\#'
620+
if (i+1 < right->col_offset && (segment_str[i] == ')')) {
621+
continue;
622+
}
623+
619624
// Set the error characters
620625
*primary_error_char = "~";
621626
*secondary_error_char = "^";
@@ -626,6 +631,18 @@ extract_anchors_from_expr(const char *segment_str, expr_ty expr, Py_ssize_t *lef
626631
case Subscript_kind: {
627632
*left_anchor = expr->v.Subscript.value->end_col_offset;
628633
*right_anchor = expr->v.Subscript.slice->end_col_offset + 1;
634+
Py_ssize_t str_len = strlen(segment_str);
635+
636+
// Move right_anchor and left_anchor forward to the first non-whitespace character that is not in ')\#'
637+
while (*left_anchor < str_len && (IS_WHITESPACE(segment_str[*left_anchor]) || segment_str[*left_anchor] != '[')) {
638+
++*left_anchor;
639+
}
640+
while (*right_anchor < str_len && (IS_WHITESPACE(segment_str[*right_anchor]) || segment_str[*right_anchor] != ']')) {
641+
++*right_anchor;
642+
}
643+
if (*right_anchor < str_len){
644+
*right_anchor += 1;
645+
}
629646

630647
// Set the error characters
631648
*primary_error_char = "~";

0 commit comments

Comments
 (0)