Skip to content

Commit 6b870d6

Browse files
authored
Fix traceback, syntax errors, and exception handling (RustPython#7015)
* Update codeop from v3.14.3 * Fix traceback, syntax errors, and exception handling - Improve unclosed bracket detection with "'(' was never closed" message - Fix IndentationError location to point to end of line - Implement frame.clear() with proper checks for executing/suspended frames - Fix exception context chaining for propagated exceptions - Add traceback.__dir__() and prevent tb_next deletion - Fix subscript operation source range restoration in compiler - Change "duplicate parameter" to "duplicate argument" error message - Refactor duplicate code in asyncgenerator.rs and frame.rs --------- Co-authored-by: CPython Developers <>
1 parent 234bdda commit 6b870d6

29 files changed

+547
-247
lines changed

Lib/codeop.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
PyCF_ONLY_AST = 0x400
4848
PyCF_ALLOW_INCOMPLETE_INPUT = 0x4000
4949

50-
def _maybe_compile(compiler, source, filename, symbol):
50+
def _maybe_compile(compiler, source, filename, symbol, flags):
5151
# Check for source consisting of only blank lines and comments.
5252
for line in source.split("\n"):
5353
line = line.strip()
@@ -61,10 +61,10 @@ def _maybe_compile(compiler, source, filename, symbol):
6161
with warnings.catch_warnings():
6262
warnings.simplefilter("ignore", (SyntaxWarning, DeprecationWarning))
6363
try:
64-
compiler(source, filename, symbol)
64+
compiler(source, filename, symbol, flags=flags)
6565
except SyntaxError: # Let other compile() errors propagate.
6666
try:
67-
compiler(source + "\n", filename, symbol)
67+
compiler(source + "\n", filename, symbol, flags=flags)
6868
return None
6969
except _IncompleteInputError as e:
7070
return None
@@ -74,14 +74,13 @@ def _maybe_compile(compiler, source, filename, symbol):
7474

7575
return compiler(source, filename, symbol, incomplete_input=False)
7676

77-
def _compile(source, filename, symbol, incomplete_input=True):
78-
flags = 0
77+
def _compile(source, filename, symbol, incomplete_input=True, *, flags=0):
7978
if incomplete_input:
8079
flags |= PyCF_ALLOW_INCOMPLETE_INPUT
8180
flags |= PyCF_DONT_IMPLY_DEDENT
8281
return compile(source, filename, symbol, flags)
8382

84-
def compile_command(source, filename="<input>", symbol="single"):
83+
def compile_command(source, filename="<input>", symbol="single", flags=0):
8584
r"""Compile a command and determine whether it is incomplete.
8685
8786
Arguments:
@@ -100,7 +99,7 @@ def compile_command(source, filename="<input>", symbol="single"):
10099
syntax error (OverflowError and ValueError can be produced by
101100
malformed literals).
102101
"""
103-
return _maybe_compile(_compile, source, filename, symbol)
102+
return _maybe_compile(_compile, source, filename, symbol, flags)
104103

105104
class Compile:
106105
"""Instances of this class behave much like the built-in compile
@@ -152,4 +151,4 @@ def __call__(self, source, filename="<input>", symbol="single"):
152151
syntax error (OverflowError and ValueError can be produced by
153152
malformed literals).
154153
"""
155-
return _maybe_compile(self.compiler, source, filename, symbol)
154+
return _maybe_compile(self.compiler, source, filename, symbol, flags=self.compiler.flags)

Lib/test/test_asyncio/test_tasks.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2947,6 +2947,7 @@ def test_log_destroyed_pending_task(self):
29472947
return super().test_log_destroyed_pending_task()
29482948

29492949

2950+
29502951
@unittest.skipUnless(hasattr(futures, '_CFuture') and
29512952
hasattr(tasks, '_CTask'),
29522953
'requires the C _asyncio module')
@@ -3007,6 +3008,7 @@ def test_log_destroyed_pending_task(self):
30073008
return super().test_log_destroyed_pending_task()
30083009

30093010

3011+
30103012
@unittest.skipUnless(hasattr(futures, '_CFuture'),
30113013
'requires the C _asyncio module')
30123014
class PyTask_CFuture_Tests(BaseTaskTests, test_utils.TestCase):

Lib/test/test_cmd_line_script.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -548,8 +548,6 @@ def test_dash_m_main_traceback(self):
548548
self.assertIn(b'Exception in __main__ module', err)
549549
self.assertIn(b'Traceback', err)
550550

551-
# TODO: RUSTPYTHON
552-
@unittest.expectedFailure
553551
def test_pep_409_verbiage(self):
554552
# Make sure PEP 409 syntax properly suppresses
555553
# the context of an exception

Lib/test/test_code_module.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ def test_console_stderr(self):
9191
else:
9292
raise AssertionError("no console stdout")
9393

94-
@unittest.expectedFailure # TODO: RUSTPYTHON; + 'SyntaxError: invalid syntax']
9594
def test_syntax_error(self):
9695
self.infunc.side_effect = ["def f():",
9796
" x = ?",
@@ -166,7 +165,6 @@ def test_sysexcepthook(self):
166165
' File "<console>", line 2, in f\n',
167166
'ValueError: BOOM!\n'])
168167

169-
@unittest.expectedFailure # TODO: RUSTPYTHON; + 'SyntaxError: invalid syntax\n']
170168
def test_sysexcepthook_syntax_error(self):
171169
self.infunc.side_effect = ["def f():",
172170
" x = ?",
@@ -285,7 +283,6 @@ def test_exit_msg(self):
285283
self.assertEqual(err_msg, ['write', (expected,), {}])
286284

287285

288-
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: '\nAttributeError\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n File "<console>", line 1, in <module>\nValueError\n' not found in 'Python <MagicMock name=\'sys.version\' id=\'94615517503920\'> on <MagicMock name=\'sys.platform\' id=\'94615517656384\'>\nType "help", "copyright", "credits" or "license" for more information.\n(InteractiveConsole)\nAttributeError\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n File "<console>", line 1, in <module>\nValueError: \n\nnow exiting InteractiveConsole...\n'
289286
def test_cause_tb(self):
290287
self.infunc.side_effect = ["raise ValueError('') from AttributeError",
291288
EOFError('Finished')]

Lib/test/test_codeop.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ def assertInvalid(self, str, symbol='single', is_syntax=1):
3030
except OverflowError:
3131
self.assertTrue(not is_syntax)
3232

33-
# TODO: RUSTPYTHON
34-
@unittest.expectedFailure
33+
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <code object <module> at 0xc99532080 file "<input>", line 1> != <code object <module> at 0xc99532f80 file "<input>", line 1>
3534
def test_valid(self):
3635
av = self.assertValid
3736

@@ -94,8 +93,7 @@ def test_valid(self):
9493
av("def f():\n pass\n#foo\n")
9594
av("@a.b.c\ndef f():\n pass\n")
9695

97-
# TODO: RUSTPYTHON
98-
@unittest.expectedFailure
96+
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <code object <module> at 0xc99532080 file "<input>", line 1> != None
9997
def test_incomplete(self):
10098
ai = self.assertIncomplete
10199

@@ -282,13 +280,12 @@ def test_filename(self):
282280
self.assertNotEqual(compile_command("a = 1\n", "abc").co_filename,
283281
compile("a = 1\n", "def", 'single').co_filename)
284282

285-
# TODO: RUSTPYTHON
286-
@unittest.expectedFailure
283+
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 2
287284
def test_warning(self):
288285
# Test that the warning is only returned once.
289286
with warnings_helper.check_warnings(
290287
('"is" with \'str\' literal', SyntaxWarning),
291-
("invalid escape sequence", SyntaxWarning),
288+
('"\\\\e" is an invalid escape sequence', SyntaxWarning),
292289
) as w:
293290
compile_command(r"'\e' is 0")
294291
self.assertEqual(len(w.warnings), 2)
@@ -309,8 +306,7 @@ def test_incomplete_warning(self):
309306
self.assertIncomplete("'\\e' + (")
310307
self.assertEqual(w, [])
311308

312-
# TODO: RUSTPYTHON
313-
@unittest.expectedFailure
309+
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 1
314310
def test_invalid_warning(self):
315311
with warnings.catch_warnings(record=True) as w:
316312
warnings.simplefilter('always')
@@ -325,8 +321,6 @@ def assertSyntaxErrorMatches(self, code, message):
325321
with self.assertRaisesRegex(SyntaxError, message):
326322
compile_command(code, symbol='exec')
327323

328-
# TODO: RUSTPYTHON
329-
@unittest.expectedFailure
330324
def test_syntax_errors(self):
331325
self.assertSyntaxErrorMatches(
332326
dedent("""\

Lib/test/test_contextlib.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -924,8 +924,6 @@ def __exit__(self, *exc_details):
924924
self.assertIsInstance(inner_exc, ValueError)
925925
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
926926

927-
# TODO: RUSTPYTHON
928-
@unittest.expectedFailure
929927
def test_exit_exception_chaining(self):
930928
# Ensure exception chaining matches the reference behaviour
931929
def raise_exc(exc):
@@ -957,8 +955,6 @@ def suppress_exc(*exc_details):
957955
self.assertIsInstance(inner_exc, ValueError)
958956
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
959957

960-
# TODO: RUSTPYTHON
961-
@unittest.expectedFailure
962958
def test_exit_exception_explicit_none_context(self):
963959
# Ensure ExitStack chaining matches actual nested `with` statements
964960
# regarding explicit __context__ = None.
@@ -1053,8 +1049,6 @@ def gets_the_context_right(exc):
10531049
self.assertIsNone(
10541050
exc.__context__.__context__.__context__.__context__)
10551051

1056-
# TODO: RUSTPYTHON
1057-
@unittest.expectedFailure
10581052
def test_exit_exception_with_existing_context(self):
10591053
# Addresses a lack of test coverage discovered after checking in a
10601054
# fix for issue 20317 that still contained debugging code.

Lib/test/test_contextlib_async.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,6 @@ async def __aenter__(self):
650650
await stack.enter_async_context(LacksExit())
651651
self.assertFalse(stack._exit_callbacks)
652652

653-
@unittest.expectedFailure # TODO: RUSTPYTHON
654653
async def test_async_exit_exception_chaining(self):
655654
# Ensure exception chaining matches the reference behaviour
656655
async def raise_exc(exc):
@@ -682,7 +681,6 @@ async def suppress_exc(*exc_details):
682681
self.assertIsInstance(inner_exc, ValueError)
683682
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
684683

685-
@unittest.expectedFailure # TODO: RUSTPYTHON
686684
async def test_async_exit_exception_explicit_none_context(self):
687685
# Ensure AsyncExitStack chaining matches actual nested `with` statements
688686
# regarding explicit __context__ = None.

Lib/test/test_exceptions.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,7 +1745,6 @@ def __del__(self):
17451745
f"deallocator {obj_repr}")
17461746
self.assertIsNotNone(cm.unraisable.exc_traceback)
17471747

1748-
@unittest.expectedFailure # TODO: RUSTPYTHON
17491748
def test_unhandled(self):
17501749
# Check for sensible reporting of unhandled exceptions
17511750
for exc_type in (ValueError, BrokenStrException):
@@ -2283,7 +2282,6 @@ def test_multiline_not_highlighted(self):
22832282
class SyntaxErrorTests(unittest.TestCase):
22842283
maxDiff = None
22852284

2286-
@unittest.expectedFailure # TODO: RUSTPYTHON
22872285
@force_not_colorized
22882286
def test_range_of_offsets(self):
22892287
cases = [

Lib/test/test_future_stmt/test_future.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ def test_unicode_literals_exec(self):
188188
exec("from __future__ import unicode_literals; x = ''", {}, scope)
189189
self.assertIsInstance(scope["x"], str)
190190

191+
# TODO: RUSTPYTHON; barry_as_FLUFL (<> operator) not supported
192+
@unittest.expectedFailure
191193
def test_syntactical_future_repl(self):
192194
p = spawn_python('-i')
193195
p.stdin.write(b"from __future__ import barry_as_FLUFL\n")

Lib/test/test_grammar.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,6 @@ def test_ellipsis(self):
221221
self.assertTrue(x is Ellipsis)
222222
self.assertRaises(SyntaxError, eval, ".. .")
223223

224-
@unittest.expectedFailure # TODO: RUSTPYTHON
225224
def test_eof_error(self):
226225
samples = ("def foo(", "\ndef foo(", "def foo(\n")
227226
for s in samples:

0 commit comments

Comments
 (0)