Skip to content

Commit ced56ad

Browse files
gh-89723: Set sys.last_* for SyntaxErrors in the IDLE Shell
The standard interpreter sets sys.last_type, sys.last_value, sys.last_traceback, and sys.last_exc for an unhandled SyntaxError. IDLE did not, because it catches the error in its GUI process while user code runs in a separate process. Send the exception to the user process and set the attributes there.
1 parent 2303eea commit ced56ad

4 files changed

Lines changed: 47 additions & 0 deletions

File tree

Lib/idlelib/idle_test/test_run.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from idlelib import run
44
import io
5+
import pickle
56
import sys
67
from test.support import captured_output, captured_stderr
78
import unittest
@@ -521,6 +522,32 @@ def test_exceptions(self):
521522
self.assertIs(t, TypeError)
522523
self.assertTrue(isinstance(e.__context__, ZeroDivisionError))
523524

525+
def test_setup_last_exception(self):
526+
# gh-89723: sys.last_* are set for a SyntaxError caught in the GUI.
527+
attrs = 'last_type', 'last_value', 'last_traceback', 'last_exc'
528+
saved = {a: getattr(sys, a) for a in attrs if hasattr(sys, a)}
529+
def restore():
530+
for a in attrs:
531+
if a in saved:
532+
setattr(sys, a, saved[a])
533+
elif hasattr(sys, a):
534+
delattr(sys, a)
535+
self.addCleanup(restore)
536+
537+
try:
538+
compile('1 +', '<pyshell#0>', 'exec')
539+
except SyntaxError as e:
540+
exc = e
541+
# The exception reaches the user process pickled, without a traceback.
542+
exc = pickle.loads(pickle.dumps(exc))
543+
self.assertIsNone(exc.__traceback__)
544+
545+
self.ex.setup_last_exception(exc)
546+
self.assertIs(sys.last_exc, exc)
547+
self.assertIs(sys.last_value, exc)
548+
self.assertIs(sys.last_type, SyntaxError)
549+
self.assertIsNone(sys.last_traceback)
550+
524551

525552
if __name__ == '__main__':
526553
unittest.main(verbosity=2)

Lib/idlelib/pyshell.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,17 @@ def showsyntaxerror(self, filename=None, **kwargs):
732732
tkconsole.colorize_syntax_error(text, pos)
733733
tkconsole.resetoutput()
734734
self.write("SyntaxError: %s\n" % msg)
735+
# The standard interpreter sets sys.last_* for an unhandled
736+
# SyntaxError, but IDLE catches it here in the GUI process, so set
737+
# them in the user process instead (gh-89723).
738+
if self.rpcclt:
739+
self.rpcclt.remotequeue("exec", "setup_last_exception",
740+
(value,), {})
741+
else:
742+
value = value.with_traceback(None)
743+
sys.last_exc = sys.last_value = value
744+
sys.last_type = type
745+
sys.last_traceback = None
735746
tkconsole.showprompt()
736747

737748
def showtraceback(self):

Lib/idlelib/run.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,12 @@ def runcode(self, code):
677677
else:
678678
flush_stdout()
679679

680+
def setup_last_exception(self, exc):
681+
"Set sys.last_* for an exception caught in the GUI process (gh-89723)."
682+
sys.last_exc = sys.last_value = exc
683+
sys.last_type = type(exc)
684+
sys.last_traceback = exc.__traceback__
685+
680686
def interrupt_the_server(self):
681687
if interruptible:
682688
thread.interrupt_main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Set ``sys.last_exc`` and friends in the user process for a ``SyntaxError``
2+
entered in the Shell, as the standard interpreter does. Previously they
3+
were left unset because IDLE catches the error in its GUI process.

0 commit comments

Comments
 (0)