Skip to content

Commit c1a2ef5

Browse files
[3.11] gh-106922: Fix error location for constructs with spaces and parentheses (GH-108959) (#109148)
Co-authored-by: Pablo Galindo Salgado <[email protected]>
1 parent b55cf2c commit c1a2ef5

File tree

4 files changed

+68
-3
lines changed

4 files changed

+68
-3
lines changed

Lib/test/test_traceback.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,24 @@ def f_with_binary_operator():
566566
result_lines = self.get_exception(f_with_binary_operator)
567567
self.assertEqual(result_lines, expected_error.splitlines())
568568

569+
def test_caret_for_binary_operators_with_spaces_and_parenthesis(self):
570+
def f_with_binary_operator():
571+
a = 1
572+
b = ""
573+
return ( a ) + b
574+
575+
lineno_f = f_with_binary_operator.__code__.co_firstlineno
576+
expected_error = (
577+
'Traceback (most recent call last):\n'
578+
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
579+
' callable()\n'
580+
f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
581+
' return ( a ) + b\n'
582+
' ~~~~~~~~~~^~~\n'
583+
)
584+
result_lines = self.get_exception(f_with_binary_operator)
585+
self.assertEqual(result_lines, expected_error.splitlines())
586+
569587
def test_caret_for_subscript(self):
570588
def f_with_subscript():
571589
some_dict = {'x': {'y': None}}
@@ -600,6 +618,24 @@ def f_with_subscript():
600618
result_lines = self.get_exception(f_with_subscript)
601619
self.assertEqual(result_lines, expected_error.splitlines())
602620

621+
def test_caret_for_subscript_with_spaces_and_parenthesis(self):
622+
def f_with_binary_operator():
623+
a = []
624+
b = c = 1
625+
return b [ a ] + c
626+
627+
lineno_f = f_with_binary_operator.__code__.co_firstlineno
628+
expected_error = (
629+
'Traceback (most recent call last):\n'
630+
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
631+
' callable()\n'
632+
f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
633+
' return b [ a ] + c\n'
634+
' ~~~~~~^^^^^^^^^\n'
635+
)
636+
result_lines = self.get_exception(f_with_binary_operator)
637+
self.assertEqual(result_lines, expected_error.splitlines())
638+
603639
def test_traceback_specialization_with_syntax_error(self):
604640
bytecode = compile("1 / 0 / 1 / 2\n", TESTFN, "exec")
605641

Lib/traceback.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -603,11 +603,21 @@ def _extract_caret_anchors_from_line_segment(segment):
603603
and not operator_str[operator_offset + 1].isspace()
604604
):
605605
right_anchor += 1
606+
607+
while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch in ")#"):
608+
left_anchor += 1
609+
right_anchor += 1
606610
return _Anchors(normalize(left_anchor), normalize(right_anchor))
607611
case ast.Subscript():
608-
subscript_start = normalize(expr.value.end_col_offset)
609-
subscript_end = normalize(expr.slice.end_col_offset + 1)
610-
return _Anchors(subscript_start, subscript_end)
612+
left_anchor = normalize(expr.value.end_col_offset)
613+
right_anchor = normalize(expr.slice.end_col_offset + 1)
614+
while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch != "["):
615+
left_anchor += 1
616+
while right_anchor < len(segment) and ((ch := segment[right_anchor]).isspace() or ch != "]"):
617+
right_anchor += 1
618+
if right_anchor < len(segment):
619+
right_anchor += 1
620+
return _Anchors(left_anchor, right_anchor)
611621

612622
return None
613623

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix caret placement for error locations for subscript and binary operations
2+
that involve non-semantic parentheses and spaces. Patch by Pablo Galindo

Python/traceback.c

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

624+
// Keep going if the current char is not ')'
625+
if (i+1 < right->col_offset && (segment_str[i] == ')')) {
626+
continue;
627+
}
628+
624629
// Set the error characters
625630
*primary_error_char = "~";
626631
*secondary_error_char = "^";
@@ -631,6 +636,18 @@ extract_anchors_from_expr(const char *segment_str, expr_ty expr, Py_ssize_t *lef
631636
case Subscript_kind: {
632637
*left_anchor = expr->v.Subscript.value->end_col_offset;
633638
*right_anchor = expr->v.Subscript.slice->end_col_offset + 1;
639+
Py_ssize_t str_len = strlen(segment_str);
640+
641+
// Move right_anchor and left_anchor forward to the first non-whitespace character that is not ']' and '['
642+
while (*left_anchor < str_len && (IS_WHITESPACE(segment_str[*left_anchor]) || segment_str[*left_anchor] != '[')) {
643+
++*left_anchor;
644+
}
645+
while (*right_anchor < str_len && (IS_WHITESPACE(segment_str[*right_anchor]) || segment_str[*right_anchor] != ']')) {
646+
++*right_anchor;
647+
}
648+
if (*right_anchor < str_len){
649+
*right_anchor += 1;
650+
}
634651

635652
// Set the error characters
636653
*primary_error_char = "~";

0 commit comments

Comments
 (0)