Skip to content

Commit

Permalink
pythongh-106922: Fix error location for constructs with spaces and pa…
Browse files Browse the repository at this point in the history
…rentheses (python#108959)
  • Loading branch information
pablogsal committed Sep 8, 2023
1 parent aa51182 commit 6275c67
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 3 deletions.
36 changes: 36 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,24 @@ def f_with_binary_operator():
result_lines = self.get_exception(f_with_binary_operator)
self.assertEqual(result_lines, expected_error.splitlines())

def test_caret_for_binary_operators_with_spaces_and_parenthesis(self):
def f_with_binary_operator():
a = 1
b = ""
return ( a ) + b

lineno_f = f_with_binary_operator.__code__.co_firstlineno
expected_error = (
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
' return ( a ) + b\n'
' ~~~~~~~~~~^~~\n'
)
result_lines = self.get_exception(f_with_binary_operator)
self.assertEqual(result_lines, expected_error.splitlines())

def test_caret_for_subscript(self):
def f_with_subscript():
some_dict = {'x': {'y': None}}
Expand Down Expand Up @@ -630,6 +648,24 @@ def f_with_subscript():
result_lines = self.get_exception(f_with_subscript)
self.assertEqual(result_lines, expected_error.splitlines())

def test_caret_for_subscript_with_spaces_and_parenthesis(self):
def f_with_binary_operator():
a = []
b = c = 1
return b [ a ] + c

lineno_f = f_with_binary_operator.__code__.co_firstlineno
expected_error = (
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
' return b [ a ] + c\n'
' ~~~~~~^^^^^^^^^\n'
)
result_lines = self.get_exception(f_with_binary_operator)
self.assertEqual(result_lines, expected_error.splitlines())

def test_traceback_specialization_with_syntax_error(self):
bytecode = compile("1 / 0 / 1 / 2\n", TESTFN, "exec")

Expand Down
16 changes: 13 additions & 3 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,11 +608,21 @@ def _extract_caret_anchors_from_line_segment(segment):
and not operator_str[operator_offset + 1].isspace()
):
right_anchor += 1

while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch in ")#"):
left_anchor += 1
right_anchor += 1
return _Anchors(normalize(left_anchor), normalize(right_anchor))
case ast.Subscript():
subscript_start = normalize(expr.value.end_col_offset)
subscript_end = normalize(expr.slice.end_col_offset + 1)
return _Anchors(subscript_start, subscript_end)
left_anchor = normalize(expr.value.end_col_offset)
right_anchor = normalize(expr.slice.end_col_offset + 1)
while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch != "["):
left_anchor += 1
while right_anchor < len(segment) and ((ch := segment[right_anchor]).isspace() or ch != "]"):
right_anchor += 1
if right_anchor < len(segment):
right_anchor += 1
return _Anchors(left_anchor, right_anchor)

return None

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix caret placement for error locations for subscript and binary operations
that involve non-semantic parentheses and spaces. Patch by Pablo Galindo
17 changes: 17 additions & 0 deletions Python/traceback.c
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,11 @@ extract_anchors_from_expr(const char *segment_str, expr_ty expr, Py_ssize_t *lef
++*right_anchor;
}

// Keep going if the current char is not ')'
if (i+1 < right->col_offset && (segment_str[i] == ')')) {
continue;
}

// Set the error characters
*primary_error_char = "~";
*secondary_error_char = "^";
Expand All @@ -626,6 +631,18 @@ extract_anchors_from_expr(const char *segment_str, expr_ty expr, Py_ssize_t *lef
case Subscript_kind: {
*left_anchor = expr->v.Subscript.value->end_col_offset;
*right_anchor = expr->v.Subscript.slice->end_col_offset + 1;
Py_ssize_t str_len = strlen(segment_str);

// Move right_anchor and left_anchor forward to the first non-whitespace character that is not ']' and '['
while (*left_anchor < str_len && (IS_WHITESPACE(segment_str[*left_anchor]) || segment_str[*left_anchor] != '[')) {
++*left_anchor;
}
while (*right_anchor < str_len && (IS_WHITESPACE(segment_str[*right_anchor]) || segment_str[*right_anchor] != ']')) {
++*right_anchor;
}
if (*right_anchor < str_len){
*right_anchor += 1;
}

// Set the error characters
*primary_error_char = "~";
Expand Down

0 comments on commit 6275c67

Please sign in to comment.