diff --git a/bpython/repl.py b/bpython/repl.py
index 93ce5cbc..85a6e5b1 100644
--- a/bpython/repl.py
+++ b/bpython/repl.py
@@ -161,14 +161,11 @@ def showsyntaxerror(self, filename: str | None = None, **kwargs) -> None:
exc_type, value, sys.last_traceback = sys.exc_info()
sys.last_type = exc_type
sys.last_value = value
- if filename and exc_type is SyntaxError and value is not None:
- msg = value.args[0]
- args = list(value.args[1])
- # strip linechache line number
+ if filename and isinstance(value, SyntaxError):
+ # Hide the linecache identifier without rebuilding the exception:
+ # compiler errors can have location attributes but only one arg.
if self.bpython_input_re.match(filename):
- args[0] = ""
- value = SyntaxError(msg, tuple(args))
- sys.last_value = value
+ value.filename = ""
exc_formatted = traceback.format_exception_only(exc_type, value)
self.writetb(exc_formatted)
diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py
index 3d40d198..668f3aa0 100644
--- a/bpython/test/test_interpreter.py
+++ b/bpython/test/test_interpreter.py
@@ -1,4 +1,6 @@
import unittest
+import sys
+from unittest.mock import Mock
from curtsies.fmtfuncs import bold, green, magenta, cyan, red, plain
@@ -34,6 +36,57 @@ def test_syntaxerror(self):
self.assertMultiLineEqual(str(plain("").join(a)), str(expected))
self.assertEqual(plain("").join(a), expected)
+ def test_semantic_syntaxerrors_leave_interpreter_usable(self):
+ for source in ("nonlocal a", "return", "break", "continue"):
+ with self.subTest(source=source):
+ i = Interpreter()
+ callback = Mock()
+ i.syntaxerror_callback = callback
+ self.assertFalse(i.runsource(source))
+ callback.assert_called_once_with()
+ output = str(plain("").join(i.a))
+ self.assertIn("SyntaxError", output)
+ self.assertIn('""', output)
+ self.assertNotIn("")
+ self.assertFalse(i.runsource("answer = 6 * 7"))
+ self.assertEqual(i.locals["answer"], 42)
+
+ def test_syntaxerror_keeps_original_exception_and_details(self):
+ i = Interpreter()
+ error = SyntaxError("message")
+ error.filename = ""
+ error.lineno = 3
+ error.offset = 2
+ error.end_lineno = 3
+ error.end_offset = 5
+ error.text = "bad source"
+ error.add_note("additional information")
+ try:
+ raise error
+ except SyntaxError:
+ i.showsyntaxerror(error.filename)
+ self.assertIs(sys.last_value, error)
+ self.assertEqual(error.filename, "")
+ self.assertEqual((error.lineno, error.offset), (3, 2))
+ self.assertEqual((error.end_lineno, error.end_offset), (3, 5))
+ self.assertIn("additional information", str(plain("").join(i.a)))
+
+ def test_explicit_syntaxerror_filename(self):
+ i = Interpreter()
+ i.runsource("1.1.1.1", filename="example.py")
+ self.assertIn('"example.py"', str(plain("").join(i.a)))
+ self.assertEqual(sys.last_value.filename, "example.py")
+
+ def test_indentationerror_filename(self):
+ i = Interpreter()
+ i.runsource(" answer = 42")
+ output = str(plain("").join(i.a))
+ self.assertIn("IndentationError", output)
+ self.assertIn('""', output)
+ self.assertIsInstance(sys.last_value, IndentationError)
+
def test_traceback(self):
i = Interpreter()