summaryrefslogtreecommitdiffstats
path: root/Lib/idlelib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/idlelib')
-rw-r--r--Lib/idlelib/NEWS.txt3
-rw-r--r--Lib/idlelib/idle_test/test_run.py47
-rw-r--r--Lib/idlelib/run.py16
3 files changed, 61 insertions, 5 deletions
diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt
index ed11426..396820e 100644
--- a/Lib/idlelib/NEWS.txt
+++ b/Lib/idlelib/NEWS.txt
@@ -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
diff --git a/Lib/idlelib/idle_test/test_run.py b/Lib/idlelib/idle_test/test_run.py
index a31671e..ec4637c 100644
--- a/Lib/idlelib/idle_test/test_run.py
+++ b/Lib/idlelib/idle_test/test_run.py
@@ -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.
diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py
index 07e9a2b..3836727 100644
--- a/Lib/idlelib/run.py
+++ b/Lib/idlelib/run.py
@@ -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)