mirror of https://github.com/python/cpython
bpo-44026: Idle - display interpreter's 'did you mean' hints (GH-25912)
A C function accessible by the default exception handler, but not by python code, finds the existing name closest to the name causing a name or attribute error. For such errors, call the default handler after capturing stderr and retrieve its message line. Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>
This commit is contained in:
parent
b2ec37a722
commit
092f9ddb5e
|
@ -4,6 +4,9 @@ Released on 2021-10-04?
|
|||
=========================
|
||||
|
||||
|
||||
bpo-44026: Include interpreter's typo fix suggestions in message line
|
||||
for NameErrors and AttributeErrors. Patch by E. Paine.
|
||||
|
||||
bpo-37903: Add mouse actions to the shell sidebar. Left click and
|
||||
optional drag selects one or more lines of text, as with the
|
||||
editor line number sidebar. Right click after selecting text lines
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"Test run, coverage 49%."
|
||||
"Test run, coverage 54%."
|
||||
|
||||
from idlelib import run
|
||||
import io
|
||||
|
@ -12,7 +12,7 @@ from idlelib.idle_test.mock_idle import Func
|
|||
idlelib.testing = True # Use {} for executing test user code.
|
||||
|
||||
|
||||
class PrintExceptionTest(unittest.TestCase):
|
||||
class ExceptionTest(unittest.TestCase):
|
||||
|
||||
def test_print_exception_unhashable(self):
|
||||
class UnhashableException(Exception):
|
||||
|
@ -28,8 +28,7 @@ class PrintExceptionTest(unittest.TestCase):
|
|||
raise ex1
|
||||
except UnhashableException:
|
||||
with captured_stderr() as output:
|
||||
with mock.patch.object(run,
|
||||
'cleanup_traceback') as ct:
|
||||
with mock.patch.object(run, 'cleanup_traceback') as ct:
|
||||
ct.side_effect = lambda t, e: t
|
||||
run.print_exception()
|
||||
|
||||
|
@ -38,6 +37,46 @@ class PrintExceptionTest(unittest.TestCase):
|
|||
self.assertIn('UnhashableException: ex2', tb[3])
|
||||
self.assertIn('UnhashableException: ex1', tb[10])
|
||||
|
||||
data = (('1/0', ZeroDivisionError, "division by zero\n"),
|
||||
('abc', NameError, "name 'abc' is not defined. "
|
||||
"Did you mean: 'abs'?\n"),
|
||||
('int.reel', AttributeError,
|
||||
"type object 'int' has no attribute 'reel'. "
|
||||
"Did you mean: 'real'?\n"),
|
||||
)
|
||||
|
||||
def test_get_message(self):
|
||||
for code, exc, msg in self.data:
|
||||
with self.subTest(code=code):
|
||||
try:
|
||||
eval(compile(code, '', 'eval'))
|
||||
except exc:
|
||||
typ, val, tb = sys.exc_info()
|
||||
actual = run.get_message_lines(typ, val, tb)[0]
|
||||
expect = f'{exc.__name__}: {msg}'
|
||||
self.assertEqual(actual, expect)
|
||||
|
||||
@mock.patch.object(run, 'cleanup_traceback',
|
||||
new_callable=lambda: (lambda t, e: None))
|
||||
def test_get_multiple_message(self, mock):
|
||||
d = self.data
|
||||
data2 = ((d[0], d[1]), (d[1], d[2]), (d[2], d[0]))
|
||||
subtests = 0
|
||||
for (code1, exc1, msg1), (code2, exc2, msg2) in data2:
|
||||
with self.subTest(codes=(code1,code2)):
|
||||
try:
|
||||
eval(compile(code1, '', 'eval'))
|
||||
except exc1:
|
||||
try:
|
||||
eval(compile(code2, '', 'eval'))
|
||||
except exc2:
|
||||
with captured_stderr() as output:
|
||||
run.print_exception()
|
||||
actual = output.getvalue()
|
||||
self.assertIn(msg1, actual)
|
||||
self.assertIn(msg2, actual)
|
||||
subtests += 1
|
||||
self.assertEqual(subtests, len(data2)) # All subtests ran?
|
||||
|
||||
# StdioFile tests.
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ Simplified, pyshell.ModifiedInterpreter spawns a subprocess with
|
|||
f'''{sys.executable} -c "__import__('idlelib.run').run.main()"'''
|
||||
'.run' is needed because __import__ returns idlelib, not idlelib.run.
|
||||
"""
|
||||
import contextlib
|
||||
import functools
|
||||
import io
|
||||
import linecache
|
||||
|
@ -211,6 +212,19 @@ def show_socket_error(err, address):
|
|||
parent=root)
|
||||
root.destroy()
|
||||
|
||||
|
||||
def get_message_lines(typ, exc, tb):
|
||||
"Return line composing the exception message."
|
||||
if typ in (AttributeError, NameError):
|
||||
# 3.10+ hints are not directly accessible from python (#44026).
|
||||
err = io.StringIO()
|
||||
with contextlib.redirect_stderr(err):
|
||||
sys.__excepthook__(typ, exc, tb)
|
||||
return [err.getvalue().split("\n")[-2] + "\n"]
|
||||
else:
|
||||
return traceback.format_exception_only(typ, exc)
|
||||
|
||||
|
||||
def print_exception():
|
||||
import linecache
|
||||
linecache.checkcache()
|
||||
|
@ -241,7 +255,7 @@ def print_exception():
|
|||
"debugger_r.py", "bdb.py")
|
||||
cleanup_traceback(tbe, exclude)
|
||||
traceback.print_list(tbe, file=efile)
|
||||
lines = traceback.format_exception_only(typ, exc)
|
||||
lines = get_message_lines(typ, exc, tb)
|
||||
for line in lines:
|
||||
print(line, end='', file=efile)
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Include interpreter's typo fix suggestions in message line for
|
||||
NameErrors and AttributeErrors. Patch by E. Paine.
|
Loading…
Reference in New Issue