Skip to content

Commit 03d5c04

Browse files
committed
gh-156047: Prioritize keyword typos on error line
1 parent 20e6c2f commit 03d5c04

2 files changed

Lines changed: 23 additions & 10 deletions

File tree

Lib/test/test_traceback.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,6 +1827,8 @@ class TestKeywordTypoSuggestions(unittest.TestCase):
18271827
("function f():", "def"),
18281828
("func f():", "def"),
18291829
("void f():", "def"),
1830+
# gh-156047: Earlier names must not exhaust the candidate budget.
1831+
("a=b=c=d=e=f=g=h=i=0\ndef fn():\n retrun True", "return"),
18301832
]
18311833

18321834
def test_keyword_suggestions_from_file(self):
@@ -1847,6 +1849,18 @@ def test_keyword_suggestions_from_command_string(self):
18471849
stderr_text = stderr.decode('utf-8')
18481850
self.assertIn(f"Did you mean '{expected_kw}'", stderr_text)
18491851

1852+
def test_keyword_suggestion_before_reported_error_line(self):
1853+
source = "def fn():\n retrun True\n# reported error line\n"
1854+
exc = SyntaxError(
1855+
"invalid syntax",
1856+
("<string>", 3, 1, "# reported error line\n", 3, 2),
1857+
)
1858+
exc._metadata = (0, 0, source)
1859+
1860+
result = ''.join(traceback.format_exception_only(exc))
1861+
1862+
self.assertIn("Did you mean 'return'", result)
1863+
18501864
def test_no_keyword_suggestion_for_comma_errors(self):
18511865
# When the parser identifies a missing comma, don't suggest
18521866
# bogus keyword replacements like 'print' -> 'not'

Lib/traceback.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,7 +1430,6 @@ def _find_keyword_typos(self):
14301430
line, offset, source = self._exc_metadata
14311431
end_line = int(self.lineno) if self.lineno is not None else 0
14321432
lines = None
1433-
from_filename = False
14341433

14351434
if source is None:
14361435
if self.filename:
@@ -1439,8 +1438,6 @@ def _find_keyword_typos(self):
14391438
lines = f.read().splitlines()
14401439
except Exception:
14411440
line, end_line, offset = 0,1,0
1442-
else:
1443-
from_filename = True
14441441
lines = lines if lines is not None else self.text.splitlines()
14451442
else:
14461443
lines = source.splitlines()
@@ -1462,17 +1459,19 @@ def _find_keyword_typos(self):
14621459
return # Original code compiles or is incomplete - can't validate fixes
14631460

14641461
error_lines = error_code.splitlines()
1465-
tokens = tokenize.generate_tokens(io.StringIO(error_code).readline)
1462+
tokens = [
1463+
token
1464+
for token in tokenize.generate_tokens(io.StringIO(error_code).readline)
1465+
if token.type == tokenize.NAME
1466+
]
1467+
# Look for typos on the reported error line first. If the parser only
1468+
# fails after an earlier typo, the remaining lines are still searched.
1469+
the_end = end_line if line == 0 else end_line + 1
1470+
tokens.sort(key=lambda token: token.start[0] + line != the_end)
14661471
tokens_left_to_process = 10
14671472
import difflib
14681473
for token in tokens:
14691474
start, end = token.start, token.end
1470-
if token.type != tokenize.NAME:
1471-
continue
1472-
# Only consider NAME tokens on the same line as the error
1473-
the_end = end_line if line == 0 else end_line + 1
1474-
if from_filename and token.start[0]+line != the_end:
1475-
continue
14761475
wrong_name = token.string
14771476
if wrong_name in keyword.kwlist:
14781477
continue

0 commit comments

Comments
 (0)