diff --git a/Lib/codeop.py b/Lib/codeop.py
index adf000ba29f..8cac00442d9 100644
--- a/Lib/codeop.py
+++ b/Lib/codeop.py
@@ -47,7 +47,7 @@
PyCF_ONLY_AST = 0x400
PyCF_ALLOW_INCOMPLETE_INPUT = 0x4000
-def _maybe_compile(compiler, source, filename, symbol):
+def _maybe_compile(compiler, source, filename, symbol, flags):
# Check for source consisting of only blank lines and comments.
for line in source.split("\n"):
line = line.strip()
@@ -61,10 +61,10 @@ def _maybe_compile(compiler, source, filename, symbol):
with warnings.catch_warnings():
warnings.simplefilter("ignore", (SyntaxWarning, DeprecationWarning))
try:
- compiler(source, filename, symbol)
+ compiler(source, filename, symbol, flags=flags)
except SyntaxError: # Let other compile() errors propagate.
try:
- compiler(source + "\n", filename, symbol)
+ compiler(source + "\n", filename, symbol, flags=flags)
return None
except _IncompleteInputError as e:
return None
@@ -74,14 +74,13 @@ def _maybe_compile(compiler, source, filename, symbol):
return compiler(source, filename, symbol, incomplete_input=False)
-def _compile(source, filename, symbol, incomplete_input=True):
- flags = 0
+def _compile(source, filename, symbol, incomplete_input=True, *, flags=0):
if incomplete_input:
flags |= PyCF_ALLOW_INCOMPLETE_INPUT
flags |= PyCF_DONT_IMPLY_DEDENT
return compile(source, filename, symbol, flags)
-def compile_command(source, filename="", symbol="single"):
+def compile_command(source, filename="", symbol="single", flags=0):
r"""Compile a command and determine whether it is incomplete.
Arguments:
@@ -100,7 +99,7 @@ def compile_command(source, filename="", symbol="single"):
syntax error (OverflowError and ValueError can be produced by
malformed literals).
"""
- return _maybe_compile(_compile, source, filename, symbol)
+ return _maybe_compile(_compile, source, filename, symbol, flags)
class Compile:
"""Instances of this class behave much like the built-in compile
@@ -152,4 +151,4 @@ def __call__(self, source, filename="", symbol="single"):
syntax error (OverflowError and ValueError can be produced by
malformed literals).
"""
- return _maybe_compile(self.compiler, source, filename, symbol)
+ return _maybe_compile(self.compiler, source, filename, symbol, flags=self.compiler.flags)
diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py
index b0ca67d6716..8a291f1cb7e 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -2947,6 +2947,7 @@ def test_log_destroyed_pending_task(self):
return super().test_log_destroyed_pending_task()
+
@unittest.skipUnless(hasattr(futures, '_CFuture') and
hasattr(tasks, '_CTask'),
'requires the C _asyncio module')
@@ -3007,6 +3008,7 @@ def test_log_destroyed_pending_task(self):
return super().test_log_destroyed_pending_task()
+
@unittest.skipUnless(hasattr(futures, '_CFuture'),
'requires the C _asyncio module')
class PyTask_CFuture_Tests(BaseTaskTests, test_utils.TestCase):
diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
index 3c417d07af6..f977b97bcd3 100644
--- a/Lib/test/test_cmd_line_script.py
+++ b/Lib/test/test_cmd_line_script.py
@@ -548,8 +548,6 @@ def test_dash_m_main_traceback(self):
self.assertIn(b'Exception in __main__ module', err)
self.assertIn(b'Traceback', err)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_pep_409_verbiage(self):
# Make sure PEP 409 syntax properly suppresses
# the context of an exception
diff --git a/Lib/test/test_code_module.py b/Lib/test/test_code_module.py
index 0ed17b390c4..39d85d46274 100644
--- a/Lib/test/test_code_module.py
+++ b/Lib/test/test_code_module.py
@@ -91,7 +91,6 @@ def test_console_stderr(self):
else:
raise AssertionError("no console stdout")
- @unittest.expectedFailure # TODO: RUSTPYTHON; + 'SyntaxError: invalid syntax']
def test_syntax_error(self):
self.infunc.side_effect = ["def f():",
" x = ?",
@@ -166,7 +165,6 @@ def test_sysexcepthook(self):
' File "", line 2, in f\n',
'ValueError: BOOM!\n'])
- @unittest.expectedFailure # TODO: RUSTPYTHON; + 'SyntaxError: invalid syntax\n']
def test_sysexcepthook_syntax_error(self):
self.infunc.side_effect = ["def f():",
" x = ?",
@@ -285,7 +283,6 @@ def test_exit_msg(self):
self.assertEqual(err_msg, ['write', (expected,), {}])
- @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 "", line 1, in \nValueError\n' not found in 'Python on \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 "", line 1, in \nValueError: \n\nnow exiting InteractiveConsole...\n'
def test_cause_tb(self):
self.infunc.side_effect = ["raise ValueError('') from AttributeError",
EOFError('Finished')]
diff --git a/Lib/test/test_codeop.py b/Lib/test/test_codeop.py
index c62e3748e6a..bbc46021406 100644
--- a/Lib/test/test_codeop.py
+++ b/Lib/test/test_codeop.py
@@ -30,8 +30,7 @@ def assertInvalid(self, str, symbol='single', is_syntax=1):
except OverflowError:
self.assertTrue(not is_syntax)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
+ @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: at 0xc99532080 file "", line 1> != at 0xc99532f80 file "", line 1>
def test_valid(self):
av = self.assertValid
@@ -94,8 +93,7 @@ def test_valid(self):
av("def f():\n pass\n#foo\n")
av("@a.b.c\ndef f():\n pass\n")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
+ @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: at 0xc99532080 file "", line 1> != None
def test_incomplete(self):
ai = self.assertIncomplete
@@ -282,13 +280,12 @@ def test_filename(self):
self.assertNotEqual(compile_command("a = 1\n", "abc").co_filename,
compile("a = 1\n", "def", 'single').co_filename)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
+ @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 2
def test_warning(self):
# Test that the warning is only returned once.
with warnings_helper.check_warnings(
('"is" with \'str\' literal', SyntaxWarning),
- ("invalid escape sequence", SyntaxWarning),
+ ('"\\\\e" is an invalid escape sequence', SyntaxWarning),
) as w:
compile_command(r"'\e' is 0")
self.assertEqual(len(w.warnings), 2)
@@ -309,8 +306,7 @@ def test_incomplete_warning(self):
self.assertIncomplete("'\\e' + (")
self.assertEqual(w, [])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
+ @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 1
def test_invalid_warning(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
@@ -325,8 +321,6 @@ def assertSyntaxErrorMatches(self, code, message):
with self.assertRaisesRegex(SyntaxError, message):
compile_command(code, symbol='exec')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_syntax_errors(self):
self.assertSyntaxErrorMatches(
dedent("""\
diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py
index 420cc2510a8..fabe0c971c1 100644
--- a/Lib/test/test_contextlib.py
+++ b/Lib/test/test_contextlib.py
@@ -924,8 +924,6 @@ def __exit__(self, *exc_details):
self.assertIsInstance(inner_exc, ValueError)
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_exit_exception_chaining(self):
# Ensure exception chaining matches the reference behaviour
def raise_exc(exc):
@@ -957,8 +955,6 @@ def suppress_exc(*exc_details):
self.assertIsInstance(inner_exc, ValueError)
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_exit_exception_explicit_none_context(self):
# Ensure ExitStack chaining matches actual nested `with` statements
# regarding explicit __context__ = None.
@@ -1053,8 +1049,6 @@ def gets_the_context_right(exc):
self.assertIsNone(
exc.__context__.__context__.__context__.__context__)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_exit_exception_with_existing_context(self):
# Addresses a lack of test coverage discovered after checking in a
# fix for issue 20317 that still contained debugging code.
diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py
index a224bc68329..c3c303ad9a7 100644
--- a/Lib/test/test_contextlib_async.py
+++ b/Lib/test/test_contextlib_async.py
@@ -650,7 +650,6 @@ async def __aenter__(self):
await stack.enter_async_context(LacksExit())
self.assertFalse(stack._exit_callbacks)
- @unittest.expectedFailure # TODO: RUSTPYTHON
async def test_async_exit_exception_chaining(self):
# Ensure exception chaining matches the reference behaviour
async def raise_exc(exc):
@@ -682,7 +681,6 @@ async def suppress_exc(*exc_details):
self.assertIsInstance(inner_exc, ValueError)
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
- @unittest.expectedFailure # TODO: RUSTPYTHON
async def test_async_exit_exception_explicit_none_context(self):
# Ensure AsyncExitStack chaining matches actual nested `with` statements
# regarding explicit __context__ = None.
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index 10c2eb4c3c4..e7b0e8850a1 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -1745,7 +1745,6 @@ def __del__(self):
f"deallocator {obj_repr}")
self.assertIsNotNone(cm.unraisable.exc_traceback)
- @unittest.expectedFailure # TODO: RUSTPYTHON
def test_unhandled(self):
# Check for sensible reporting of unhandled exceptions
for exc_type in (ValueError, BrokenStrException):
@@ -2283,7 +2282,6 @@ def test_multiline_not_highlighted(self):
class SyntaxErrorTests(unittest.TestCase):
maxDiff = None
- @unittest.expectedFailure # TODO: RUSTPYTHON
@force_not_colorized
def test_range_of_offsets(self):
cases = [
diff --git a/Lib/test/test_future_stmt/test_future.py b/Lib/test/test_future_stmt/test_future.py
index e57c7227cec..f137b2650b8 100644
--- a/Lib/test/test_future_stmt/test_future.py
+++ b/Lib/test/test_future_stmt/test_future.py
@@ -188,6 +188,8 @@ def test_unicode_literals_exec(self):
exec("from __future__ import unicode_literals; x = ''", {}, scope)
self.assertIsInstance(scope["x"], str)
+ # TODO: RUSTPYTHON; barry_as_FLUFL (<> operator) not supported
+ @unittest.expectedFailure
def test_syntactical_future_repl(self):
p = spawn_python('-i')
p.stdin.write(b"from __future__ import barry_as_FLUFL\n")
diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py
index aae5c2b1ce3..77bd5a163ce 100644
--- a/Lib/test/test_grammar.py
+++ b/Lib/test/test_grammar.py
@@ -221,7 +221,6 @@ def test_ellipsis(self):
self.assertTrue(x is Ellipsis)
self.assertRaises(SyntaxError, eval, ".. .")
- @unittest.expectedFailure # TODO: RUSTPYTHON
def test_eof_error(self):
samples = ("def foo(", "\ndef foo(", "def foo(\n")
for s in samples:
diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py
index c2d64813ad7..cfb287279d8 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -568,7 +568,6 @@ def test_stack(self):
self.assertIn('inspect.stack()', record.code_context[0])
self.assertEqual(record.index, 0)
- @unittest.expectedFailure # TODO: RUSTPYTHON
def test_trace(self):
self.assertEqual(len(git.tr), 3)
frame1, frame2, frame3, = git.tr
diff --git a/Lib/test/test_named_expressions.py b/Lib/test/test_named_expressions.py
index 68e4a7704f4..fea86fe4308 100644
--- a/Lib/test/test_named_expressions.py
+++ b/Lib/test/test_named_expressions.py
@@ -44,32 +44,24 @@ def test_named_expression_invalid_06(self):
with self.assertRaisesRegex(SyntaxError, "cannot use assignment expressions with tuple"):
exec(code, {}, {})
- # TODO: RUSTPYTHON: wrong error message
- @unittest.expectedFailure
def test_named_expression_invalid_07(self):
code = """def spam(a = b := 42): pass"""
with self.assertRaisesRegex(SyntaxError, "invalid syntax"):
exec(code, {}, {})
- # TODO: RUSTPYTHON: wrong error message
- @unittest.expectedFailure
def test_named_expression_invalid_08(self):
code = """def spam(a: b := 42 = 5): pass"""
with self.assertRaisesRegex(SyntaxError, "invalid syntax"):
exec(code, {}, {})
- # TODO: RUSTPYTHON: wrong error message
- @unittest.expectedFailure
def test_named_expression_invalid_09(self):
code = """spam(a=b := 'c')"""
with self.assertRaisesRegex(SyntaxError, "invalid syntax"):
exec(code, {}, {})
- # TODO: RUSTPYTHON: wrong error message
- @unittest.expectedFailure
def test_named_expression_invalid_10(self):
code = """spam(x = y := f(x))"""
@@ -103,8 +95,6 @@ def test_named_expression_invalid_13(self):
"positional argument follows keyword argument"):
exec(code, {}, {})
- # TODO: RUSTPYTHON: wrong error message
- @unittest.expectedFailure
def test_named_expression_invalid_14(self):
code = """(x := lambda: y := 1)"""
@@ -120,8 +110,6 @@ def test_named_expression_invalid_15(self):
"cannot use assignment expressions with lambda"):
exec(code, {}, {})
- # TODO: RUSTPYTHON: wrong error message
- @unittest.expectedFailure
def test_named_expression_invalid_16(self):
code = "[i + 1 for i in i := [1,2]]"
diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py
index 5085798b81e..08e69caae1f 100644
--- a/Lib/test/test_syntax.py
+++ b/Lib/test/test_syntax.py
@@ -2293,7 +2293,6 @@ def test_expression_with_assignment(self):
offset=7
)
- @unittest.expectedFailure # TODO: RUSTPYTHON
def test_curly_brace_after_primary_raises_immediately(self):
self._check_error("f{}", "invalid syntax", mode="single")
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 00c2a9b937b..b72d09865d8 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -176,7 +176,6 @@ def test_original_excepthook(self):
self.assertRaises(TypeError, sys.__excepthook__)
- @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError formatting in arbitrary tracebacks
@force_not_colorized
def test_excepthook_bytes_filename(self):
# bpo-37467: sys.excepthook() must not crash if a filename
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 04b77829c1c..8b7938e3283 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -88,7 +88,6 @@ def syntax_error_bad_indentation2(self):
def tokenizer_error_with_caret_range(self):
compile("blech ( ", "?", "exec")
- @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 11 != 14
def test_caret(self):
err = self.get_exception_format(self.syntax_error_with_caret,
SyntaxError)
@@ -201,7 +200,6 @@ def f():
finally:
unlink(TESTFN)
- @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 3 != 4
def test_bad_indentation(self):
err = self.get_exception_format(self.syntax_error_bad_indentation,
IndentationError)
@@ -1797,7 +1795,6 @@ class TestKeywordTypoSuggestions(unittest.TestCase):
("for x im n:\n pass", "in"),
]
- @unittest.expectedFailure # TODO: RUSTPYTHON
def test_keyword_suggestions_from_file(self):
with tempfile.TemporaryDirectory() as script_dir:
for i, (code, expected_kw) in enumerate(self.TYPO_CASES):
@@ -1808,7 +1805,6 @@ def test_keyword_suggestions_from_file(self):
stderr_text = stderr.decode('utf-8')
self.assertIn(f"Did you mean '{expected_kw}'", stderr_text)
- @unittest.expectedFailure # TODO: RUSTPYTHON
def test_keyword_suggestions_from_command_string(self):
for code, expected_kw in self.TYPO_CASES:
with self.subTest(typo=expected_kw):
@@ -3352,7 +3348,6 @@ class MiscTracebackCases(unittest.TestCase):
# Check non-printing functions in traceback module
#
- @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 1 != 0
def test_clear(self):
def outer():
middle()
@@ -3574,7 +3569,6 @@ def format_frame_summary(self, frame_summary, colorize=False):
f' File "{__file__}", line {lno}, in f\n 1/0\n'
)
- @unittest.expectedFailure # TODO: RUSTPYTHON; Actual: _should_show_carets(13, 14, ['# this line will be used during rendering'], None)
def test_summary_should_show_carets(self):
# See: https://github.com/python/cpython/issues/122353
@@ -3731,7 +3725,6 @@ def test_context(self):
self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
self.assertEqual(str(exc_obj), str(exc))
- @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 11 not greater than 1000
def test_long_context_chain(self):
def f():
try:
@@ -4059,7 +4052,6 @@ def test_exception_group_format_exception_onlyi_recursive(self):
self.assertEqual(formatted, expected)
- @unittest.expectedFailure # TODO: RUSTPYTHON; Diff is 2265 characters long. Set self.maxDiff to None to see it.
def test_exception_group_format(self):
teg = traceback.TracebackException.from_exception(self.eg)
@@ -4841,22 +4833,6 @@ class PurePythonSuggestionFormattingTests(
traceback printing in traceback.py.
"""
- @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "'bluch'" not found in "ImportError: cannot import name 'blach'"
- def test_import_from_suggestions_underscored(self):
- return super().test_import_from_suggestions_underscored()
-
- @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "'bluch'" not found in "ImportError: cannot import name 'blech'"
- def test_import_from_suggestions_non_string(self):
- return super().test_import_from_suggestions_non_string()
-
- @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "'bluchin'?" not found in "ImportError: cannot import name 'bluch'"
- def test_import_from_suggestions(self):
- return super().test_import_from_suggestions()
-
- @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'Did you mean' not found in "AttributeError: 'A' object has no attribute 'blich'"
- def test_attribute_error_inside_nested_getattr(self):
- return super().test_attribute_error_inside_nested_getattr()
-
@cpython_only
class CPythonSuggestionFormattingTests(
@@ -4969,7 +4945,6 @@ class MyList(list):
class TestColorizedTraceback(unittest.TestCase):
maxDiff = None
- @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "y = \x1b[31mx['a']['b']\x1b[0m\x1b[1;31m['c']\x1b[0m" not found in 'Traceback (most recent call last):\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4764\x1b[0m, in \x1b[35mtest_colorized_traceback\x1b[0m\n \x1b[31mbar\x1b[0m\x1b[1;31m()\x1b[0m\n \x1b[31m~~~\x1b[0m\x1b[1;31m^^\x1b[0m\n bar = .bar at 0xb57b09180>\n baz1 = .baz1 at 0xb57b09e00>\n baz2 = .baz2 at 0xb57b09cc0>\n e = TypeError("\'NoneType\' object is not subscriptable")\n foo = .foo at 0xb57b08140>\n self = \n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4760\x1b[0m, in \x1b[35mbar\x1b[0m\n return baz1(1,\n 2,3\n ,4)\n baz1 = .baz1 at 0xb57b09e00>\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4757\x1b[0m, in \x1b[35mbaz1\x1b[0m\n return baz2(1,2,3,4)\n args = (1, 2, 3, 4)\n baz2 = .baz2 at 0xb57b09cc0>\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4754\x1b[0m, in \x1b[35mbaz2\x1b[0m\n return \x1b[31m(lambda *args: foo(*args))\x1b[0m\x1b[1;31m(1,2,3,4)\x1b[0m\n \x1b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~\x1b[0m\x1b[1;31m^^^^^^^^^\x1b[0m\n args = (1, 2, 3, 4)\n foo = .foo at 0xb57b08140>\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4754\x1b[0m, in \x1b[35m\x1b[0m\n return (lambda *args: \x1b[31mfoo\x1b[0m\x1b[1;31m(*args)\x1b[0m)(1,2,3,4)\n \x1b[31m~~~\x1b[0m\x1b[1;31m^^^^^^^\x1b[0m\n args = (1, 2, 3, 4)\n foo = .foo at 0xb57b08140>\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4751\x1b[0m, in \x1b[35mfoo\x1b[0m\n y = x[\'a\'][\'b\'][\x1b[1;31m\'c\'\x1b[0m]\n \x1b[1;31m^^^\x1b[0m\n args = (1, 2, 3, 4)\n x = {\'a\': {\'b\': None}}\n\x1b[1;35mTypeError\x1b[0m: \x1b[35m\'NoneType\' object is not subscriptable\x1b[0m\n'
def test_colorized_traceback(self):
def foo(*args):
x = {'a':{'b': None}}
@@ -5002,7 +4977,6 @@ def bar():
self.assertIn("return baz1(1,\n 2,3\n ,4)", lines)
self.assertIn(red + "bar" + reset + boldr + "()" + reset, lines)
- @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ' File \x1b[35m""\x1b[0m, line \x1b[35m1\x1b[0m\n a \x1b[1;31m$\x1b[0m b\n \x1b[1;31m^\x1b[0m\n\x1b[1;35mSyntaxError\x1b[0m: \x1b[35minvalid syntax\x1b[0m\n' not found in 'Traceback (most recent call last):\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4782\x1b[0m, in \x1b[35mtest_colorized_syntax_error\x1b[0m\n \x1b[31mcompile\x1b[0m\x1b[1;31m("a $ b", "", "exec")\x1b[0m\n \x1b[31m~~~~~~~\x1b[0m\x1b[1;31m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\x1b[0m\n e = SyntaxError(\'got unexpected token $\')\n self = \n File \x1b[35m""\x1b[0m, line \x1b[35m1\x1b[0m\n a \x1b[1;31m$\x1b[0m b\n \x1b[1;31m^\x1b[0m\n\x1b[1;35mSyntaxError\x1b[0m: \x1b[35mgot unexpected token $\x1b[0m\n'
def test_colorized_syntax_error(self):
try:
compile("a $ b", "", "exec")
@@ -5053,7 +5027,6 @@ def expected(t, m, fn, l, f, E, e, z):
]
self.assertEqual(actual, expected(**colors))
- @unittest.expectedFailure # TODO: RUSTPYTHON; Diff is 1795 characters long. Set self.maxDiff to None to see it.
def test_colorized_traceback_from_exception_group(self):
def foo():
exceptions = []
diff --git a/Lib/test/test_unpack_ex.py b/Lib/test/test_unpack_ex.py
index 2cadb9c70ba..91ff1121741 100644
--- a/Lib/test/test_unpack_ex.py
+++ b/Lib/test/test_unpack_ex.py
@@ -183,7 +183,7 @@
# ^
# SyntaxError: invalid syntax
- >>> dict(**x for x in [{1:2}]) # TODO: RUSTPYTHON # doctest:+EXPECTED_FAILURE
+ >>> dict(**x for x in [{1:2}])
Traceback (most recent call last):
...
dict(**x for x in [{1:2}])
diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs
index 7058a258a44..f76ccd152e7 100644
--- a/crates/codegen/src/compile.rs
+++ b/crates/codegen/src/compile.rs
@@ -493,56 +493,46 @@ impl Compiler {
slice: &ast::Expr,
ctx: ast::ExprContext,
) -> CompileResult<()> {
- // 1. Check subscripter and index for Load context
- // 2. VISIT value
- // 3. Handle two-element slice specially
- // 4. Otherwise VISIT slice and emit appropriate instruction
-
- // For Load context, some checks are skipped for now
- // if ctx == ast::ExprContext::Load {
- // check_subscripter(value);
- // check_index(value, slice);
- // }
+ // Save full subscript expression range (set by compile_expression before this call)
+ let subscript_range = self.current_source_range;
// VISIT(c, expr, e->v.Subscript.value)
self.compile_expression(value)?;
// Handle two-element non-constant slice with BINARY_SLICE/STORE_SLICE
- if slice.should_use_slice_optimization() && !matches!(ctx, ast::ExprContext::Del) {
+ let use_slice_opt = matches!(ctx, ast::ExprContext::Load | ast::ExprContext::Store)
+ && slice.should_use_slice_optimization();
+ if use_slice_opt {
match slice {
ast::Expr::Slice(s) => self.compile_slice_two_parts(s)?,
_ => unreachable!(
"should_use_slice_optimization should only return true for ast::Expr::Slice"
),
};
- match ctx {
- ast::ExprContext::Load => {
- emit!(self, Instruction::BinarySlice);
- }
- ast::ExprContext::Store => {
- emit!(self, Instruction::StoreSlice);
- }
- _ => unreachable!(),
- }
} else {
// VISIT(c, expr, e->v.Subscript.slice)
self.compile_expression(slice)?;
+ }
- // Emit appropriate instruction based on context
- match ctx {
- ast::ExprContext::Load => emit!(
- self,
- Instruction::BinaryOp {
- op: BinaryOperator::Subscr
- }
- ),
- ast::ExprContext::Store => emit!(self, Instruction::StoreSubscr),
- ast::ExprContext::Del => emit!(self, Instruction::DeleteSubscr),
- ast::ExprContext::Invalid => {
- return Err(self.error(CodegenErrorType::SyntaxError(
- "Invalid expression context".to_owned(),
- )));
+ // Restore full subscript expression range before emitting
+ self.set_source_range(subscript_range);
+
+ match (use_slice_opt, ctx) {
+ (true, ast::ExprContext::Load) => emit!(self, Instruction::BinarySlice),
+ (true, ast::ExprContext::Store) => emit!(self, Instruction::StoreSlice),
+ (true, _) => unreachable!(),
+ (false, ast::ExprContext::Load) => emit!(
+ self,
+ Instruction::BinaryOp {
+ op: BinaryOperator::Subscr
}
+ ),
+ (false, ast::ExprContext::Store) => emit!(self, Instruction::StoreSubscr),
+ (false, ast::ExprContext::Del) => emit!(self, Instruction::DeleteSubscr),
+ (false, ast::ExprContext::Invalid) => {
+ return Err(self.error(CodegenErrorType::SyntaxError(
+ "Invalid expression context".to_owned(),
+ )));
}
}
@@ -6603,7 +6593,8 @@ impl Compiler {
self.compile_expression(left)?;
self.compile_expression(right)?;
- // Perform operation:
+ // Restore full expression range before emitting the operation
+ self.set_source_range(range);
self.compile_op(op, false);
}
ast::Expr::Subscript(ast::ExprSubscript {
@@ -6614,7 +6605,8 @@ impl Compiler {
ast::Expr::UnaryOp(ast::ExprUnaryOp { op, operand, .. }) => {
self.compile_expression(operand)?;
- // Perform operation:
+ // Restore full expression range before emitting the operation
+ self.set_source_range(range);
match op {
ast::UnaryOp::UAdd => emit!(
self,
diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs
index 2b243262b3c..8daa90360e6 100644
--- a/crates/codegen/src/symboltable.rs
+++ b/crates/codegen/src/symboltable.rs
@@ -1012,7 +1012,7 @@ impl SymbolTableBuilder {
if table.symbols.contains_key(parameter.name.as_str()) {
return Err(SymbolTableError {
error: format!(
- "duplicate parameter '{}' in function definition",
+ "duplicate argument '{}' in function definition",
parameter.name
),
location: Some(
diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs
index 11e3bf0d07a..815d45f2cd8 100644
--- a/crates/compiler/src/lib.rs
+++ b/crates/compiler/src/lib.rs
@@ -27,6 +27,8 @@ pub struct ParseError {
pub location: SourceLocation,
pub end_location: SourceLocation,
pub source_path: String,
+ /// Set when the error is an unclosed bracket (converted from EOF).
+ pub is_unclosed_bracket: bool,
}
impl ::core::fmt::Display for ParseError {
@@ -46,26 +48,71 @@ pub enum CompileError {
impl CompileError {
pub fn from_ruff_parse_error(error: parser::ParseError, source_file: &SourceFile) -> Self {
let source_code = source_file.to_source_code();
- let location = source_code.source_location(error.location.start(), PositionEncoding::Utf8);
- let mut end_location =
- source_code.source_location(error.location.end(), PositionEncoding::Utf8);
-
- // If the error range ends at the start of a new line (column 1),
- // adjust it to the end of the previous line
- if end_location.character_offset.get() == 1 && end_location.line > location.line {
- // Get the end of the previous line
- let prev_line_end = error.location.end() - ruff_text_size::TextSize::from(1);
- end_location = source_code.source_location(prev_line_end, PositionEncoding::Utf8);
- // Adjust column to be after the last character
- end_location.character_offset = end_location.character_offset.saturating_add(1);
- }
+ let source_text = source_file.source_text();
+
+ // For EOF errors (unclosed brackets), find the unclosed bracket position
+ // and adjust both the error location and message
+ let mut is_unclosed_bracket = false;
+ let (error_type, location, end_location) = if matches!(
+ &error.error,
+ parser::ParseErrorType::Lexical(parser::LexicalErrorType::Eof)
+ ) {
+ if let Some((bracket_char, bracket_offset)) = find_unclosed_bracket(source_text) {
+ let bracket_text_size = ruff_text_size::TextSize::new(bracket_offset as u32);
+ let loc = source_code.source_location(bracket_text_size, PositionEncoding::Utf8);
+ let end_loc = SourceLocation {
+ line: loc.line,
+ character_offset: loc.character_offset.saturating_add(1),
+ };
+ let msg = format!("'{}' was never closed", bracket_char);
+ is_unclosed_bracket = true;
+ (parser::ParseErrorType::OtherError(msg), loc, end_loc)
+ } else {
+ let loc =
+ source_code.source_location(error.location.start(), PositionEncoding::Utf8);
+ let end_loc =
+ source_code.source_location(error.location.end(), PositionEncoding::Utf8);
+ (error.error, loc, end_loc)
+ }
+ } else if matches!(
+ &error.error,
+ parser::ParseErrorType::Lexical(parser::LexicalErrorType::IndentationError)
+ ) {
+ // For IndentationError, point the offset to the end of the line content
+ // instead of the beginning
+ let loc = source_code.source_location(error.location.start(), PositionEncoding::Utf8);
+ let line_idx = loc.line.to_zero_indexed();
+ let line = source_text.split('\n').nth(line_idx).unwrap_or("");
+ let line_end_col = line.chars().count() + 1; // 1-indexed, past last char
+ let end_loc = SourceLocation {
+ line: loc.line,
+ character_offset: ruff_source_file::OneIndexed::new(line_end_col)
+ .unwrap_or(loc.character_offset),
+ };
+ (error.error, end_loc, end_loc)
+ } else {
+ let loc = source_code.source_location(error.location.start(), PositionEncoding::Utf8);
+ let mut end_loc =
+ source_code.source_location(error.location.end(), PositionEncoding::Utf8);
+
+ // If the error range ends at the start of a new line (column 1),
+ // adjust it to the end of the previous line
+ if end_loc.character_offset.get() == 1 && end_loc.line > loc.line {
+ let prev_line_end = error.location.end() - ruff_text_size::TextSize::from(1);
+ end_loc = source_code.source_location(prev_line_end, PositionEncoding::Utf8);
+ end_loc.character_offset = end_loc.character_offset.saturating_add(1);
+ }
+
+ (error.error, loc, end_loc)
+ };
Self::Parse(ParseError {
- error: error.error,
+ error: error_type,
raw_location: error.location,
location,
end_location,
source_path: source_file.name().to_owned(),
+ is_unclosed_bracket,
})
}
@@ -102,6 +149,106 @@ impl CompileError {
}
}
+/// Find the last unclosed opening bracket in source code.
+/// Returns the bracket character and its byte offset, or None if all brackets are balanced.
+fn find_unclosed_bracket(source: &str) -> Option<(char, usize)> {
+ let mut stack: Vec<(char, usize)> = Vec::new();
+ let mut in_string = false;
+ let mut string_quote = '\0';
+ let mut triple_quote = false;
+ let mut escape_next = false;
+ let mut is_raw_string = false;
+
+ let chars: Vec<(usize, char)> = source.char_indices().collect();
+ let mut i = 0;
+
+ while i < chars.len() {
+ let (byte_offset, ch) = chars[i];
+
+ if escape_next {
+ escape_next = false;
+ i += 1;
+ continue;
+ }
+
+ if in_string {
+ if ch == '\\' && !is_raw_string {
+ escape_next = true;
+ } else if triple_quote {
+ if ch == string_quote
+ && i + 2 < chars.len()
+ && chars[i + 1].1 == string_quote
+ && chars[i + 2].1 == string_quote
+ {
+ in_string = false;
+ i += 3;
+ continue;
+ }
+ } else if ch == string_quote {
+ in_string = false;
+ }
+ i += 1;
+ continue;
+ }
+
+ // Check for comments
+ if ch == '#' {
+ // Skip to end of line
+ while i < chars.len() && chars[i].1 != '\n' {
+ i += 1;
+ }
+ continue;
+ }
+
+ // Check for string start (with optional prefix like r, b, f, u, rb, br, etc.)
+ if ch == '\'' || ch == '"' {
+ // Check up to 2 characters before the quote for string prefix
+ is_raw_string = false;
+ for look_back in 1..=2.min(i) {
+ let prev = chars[i - look_back].1;
+ if matches!(prev, 'r' | 'R') {
+ is_raw_string = true;
+ break;
+ }
+ if !matches!(prev, 'b' | 'B' | 'f' | 'F' | 'u' | 'U') {
+ break;
+ }
+ }
+ string_quote = ch;
+ if i + 2 < chars.len() && chars[i + 1].1 == ch && chars[i + 2].1 == ch {
+ triple_quote = true;
+ in_string = true;
+ i += 3;
+ continue;
+ }
+ triple_quote = false;
+ in_string = true;
+ i += 1;
+ continue;
+ }
+
+ match ch {
+ '(' | '[' | '{' => stack.push((ch, byte_offset)),
+ ')' | ']' | '}' => {
+ let expected = match ch {
+ ')' => '(',
+ ']' => '[',
+ '}' => '{',
+ _ => unreachable!(),
+ };
+ if stack.last().is_some_and(|&(open, _)| open == expected) {
+ stack.pop();
+ }
+ }
+ _ => {}
+ }
+
+ i += 1;
+ }
+
+ stack.last().copied()
+}
+
/// Compile a given source code into a bytecode object.
pub fn compile(
source: &str,
diff --git a/crates/vm/src/builtins/asyncgenerator.rs b/crates/vm/src/builtins/asyncgenerator.rs
index 7523714a3d0..f5b85410eef 100644
--- a/crates/vm/src/builtins/asyncgenerator.rs
+++ b/crates/vm/src/builtins/asyncgenerator.rs
@@ -678,29 +678,19 @@ impl PyAnextAwaitable {
let awaitable = if wrapped.class().is(vm.ctx.types.coroutine_type) {
// Coroutine - get __await__ later
wrapped.clone()
- } else if let Some(generator) = wrapped.downcast_ref::() {
- // Generator with CO_ITERABLE_COROUTINE flag can be awaited
- // (e.g., generators decorated with @types.coroutine)
- if generator
- .as_coro()
- .frame()
- .code
- .flags
- .contains(crate::bytecode::CodeFlags::ITERABLE_COROUTINE)
+ } else {
+ // Check for generator with CO_ITERABLE_COROUTINE flag
+ if let Some(generator) = wrapped.downcast_ref::()
+ && generator
+ .as_coro()
+ .frame()
+ .code
+ .flags
+ .contains(crate::bytecode::CodeFlags::ITERABLE_COROUTINE)
{
// Return the generator itself as the iterator
return Ok(wrapped.clone());
}
- // Fall through: try to get __await__ method for generator subclasses
- if let Some(await_method) = vm.get_method(wrapped.clone(), identifier!(vm, __await__)) {
- await_method?.call((), vm)?
- } else {
- return Err(vm.new_type_error(format!(
- "'{}' object can't be awaited",
- wrapped.class().name()
- )));
- }
- } else {
// Try to get __await__ method
if let Some(await_method) = vm.get_method(wrapped.clone(), identifier!(vm, __await__)) {
await_method?.call((), vm)?
diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs
index 838265d62c9..d6bbbf82754 100644
--- a/crates/vm/src/builtins/frame.rs
+++ b/crates/vm/src/builtins/frame.rs
@@ -6,7 +6,7 @@ use super::{PyCode, PyDictRef, PyIntRef, PyStrRef};
use crate::{
AsObject, Context, Py, PyObjectRef, PyRef, PyResult, VirtualMachine,
class::PyClassImpl,
- frame::{Frame, FrameRef},
+ frame::{Frame, FrameOwner, FrameRef},
function::PySetterValue,
types::Representable,
};
@@ -31,11 +31,6 @@ impl Representable for Frame {
#[pyclass(flags(DISALLOW_INSTANTIATION), with(Py))]
impl Frame {
- #[pymethod]
- const fn clear(&self) {
- // TODO
- }
-
#[pygetset]
fn f_globals(&self) -> PyDictRef {
self.globals.clone()
@@ -151,6 +146,45 @@ impl Frame {
#[pyclass]
impl Py {
+ #[pymethod]
+ // = frame_clear_impl
+ fn clear(&self, vm: &VirtualMachine) -> PyResult<()> {
+ let owner = FrameOwner::from_i8(self.owner.load(core::sync::atomic::Ordering::Acquire));
+ match owner {
+ FrameOwner::Generator => {
+ // Generator frame: check if suspended (lasti > 0 means
+ // FRAME_SUSPENDED). lasti == 0 means FRAME_CREATED and
+ // can be cleared.
+ if self.lasti() != 0 {
+ return Err(vm.new_runtime_error("cannot clear a suspended frame".to_owned()));
+ }
+ }
+ FrameOwner::Thread => {
+ // Thread-owned frame: always executing, cannot clear.
+ return Err(vm.new_runtime_error("cannot clear an executing frame".to_owned()));
+ }
+ FrameOwner::FrameObject => {
+ // Detached frame: safe to clear.
+ }
+ }
+
+ // Clear fastlocals
+ {
+ let mut fastlocals = self.fastlocals.lock();
+ for slot in fastlocals.iter_mut() {
+ *slot = None;
+ }
+ }
+
+ // Clear the evaluation stack
+ self.clear_value_stack();
+
+ // Clear temporary refs
+ self.temporary_refs.lock().clear();
+
+ Ok(())
+ }
+
#[pygetset]
fn f_generator(&self) -> Option {
self.generator.to_owned()
diff --git a/crates/vm/src/builtins/traceback.rs b/crates/vm/src/builtins/traceback.rs
index 675025cd6b6..ff88a03f7de 100644
--- a/crates/vm/src/builtins/traceback.rs
+++ b/crates/vm/src/builtins/traceback.rs
@@ -1,7 +1,7 @@
-use super::PyType;
+use super::{PyList, PyType};
use crate::{
AsObject, Context, Py, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl,
- frame::FrameRef, types::Constructor,
+ frame::FrameRef, function::PySetterValue, types::Constructor,
};
use rustpython_common::lock::PyMutex;
use rustpython_compiler_core::OneIndexed;
@@ -62,12 +62,28 @@ impl PyTraceback {
self.next.lock().as_ref().cloned()
}
+ #[pymethod]
+ fn __dir__(&self, vm: &VirtualMachine) -> PyList {
+ PyList::from(
+ ["tb_frame", "tb_next", "tb_lasti", "tb_lineno"]
+ .iter()
+ .map(|&s| vm.ctx.new_str(s).into())
+ .collect::>(),
+ )
+ }
+
#[pygetset(setter)]
fn set_tb_next(
zelf: &Py,
- value: Option>,
+ value: PySetterValue